动态更新 Django 表单中的 MultipleChoiceField 选项属性
Dynamically Update MultipleChoiceField Option Attributes in Django Form
我将 Django Forms 用于我的 Web 应用程序的前端过滤器功能,并且我正在对 Field 进行一些自定义,以便我可以显示带有自定义标签的多个 select 复选框,如下所示:
[x] 有趣的道格 (1)
[ ] 斯基特情人节 (5)
[x]帕蒂蛋黄酱(3)
[ ] 罗杰·克洛茨 (9)
在 select 选择一个选项后,我可以通过重写我的 Forms init 方法来动态更新复选框字段标签(特别是计数),如下所示:
class FiltersForm(forms.Form):
...
studentCheckbox = MyModelMultipleChoiceField(widget=MyMultiSelectWidget, queryset=Student.objects.all(), required=False)
...
def __init__(self, *args, **kwargs):
super(FiltersForm, self).__init__(*args, **kwargs)
students = Student.objects.values(...).annotate(count=Count(...))
self.fields['studentCheckbox'].queryset = Student.objects.all()
# dynamically updating the field's label here to include a count
self.fields['studentCheckbox'].label_from_instance = lambda obj: "%s (%s)" % (students.get(pk=obj.pk)['name'], students.get(pk=obj.pk)['count'])
但不是 "hardcode" 字段标签中的计数,我想动态地将计数设置为每个小部件选项字段上的 'data-count' 属性。在尝试这样做时,我将 forms.ModelMultipleChoiceField
子类化为 MyModelMultipleChoiceField
.
我希望重写MyModelMultipleChoiceField
中的label_from_instance
函数来动态访问obj(通过pk),并在此过程中设置一个data-count属性。但是,出于某种原因,label_from_instance
函数并未从我的表单 init (self.fields['studentCheckbox'].label_from_instance
) 中的 lambda 调用中调用。我还尝试覆盖表单和自定义小部件 (MyMultiSelectWidget) 上的 label_from_instance
函数,但无济于事。
class MyModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
print(obj) # nothing prints
if hasattr(obj, 'count'):
self.widget.attrs.update({obj.pk: {'data-count': obj.count}})
return obj
# I probably don't need to subclass the Widget, but just in case...
# I originally thought I could do something with create_option(count=None), but I need access to the
# obj, as I can't use a lambda with self.fields['studentCheckbox'].widget.count = lambda...
class MyMultiSelectWidget(widgets.SelectMultiple):
def __init__(self, count=None, *args, **kwargs):
super().__init__(*args, **kwargs)
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
options = super(MyMultiSelectWidget, self).create_option(name, value, label, selected, index, subindex=None, attrs=None)
return options
我是 Django 的新手,我觉得我遇到了很多边缘情况,所以我很感激任何帮助!
更新 #1:
我意识到,在我的表单 init 中,我没有 调用 字段的 label_from_instance
函数,而是 用 self.fields['studentCheckbox'].label_from_instance = lambda obj: "%s (%s)" % (students.get(pk=obj.pk)['name'], students.get(pk=obj.pk)['count'])
.
定义
因此,我注释掉了该行,现在调用了被覆盖的函数。虽然我现在可以访问 obj 的计数,但它仍然没有出现在呈现的 HTML 中。更新后的代码如下。
class MyModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
print(obj.count) # this works now
if hasattr(obj, 'count'):
# no error, but not appearing in rendered html
self.widget.attrs.update({obj.pk: {'data-count': obj.count}})
return obj
class FiltersForm(forms.Form):
...
studentCheckbox = MyModelMultipleChoiceField(queryset=Student.objects.all(), required=False)
...
def __init__(self, *args, **kwargs):
super(FiltersForm, self).__init__(*args, **kwargs)
students = Student.objects.annotate(count=Count(...))
# These objects feed into the overridden label_from_instance function
self.fields['studentCheckbox'].queryset = students
#self.fields['studentCheckbox'].label_from_instance = lambda obj: "%s (%s)" % (students.get(pk=obj.pk)['name'], students.get(pk=obj.pk)['count'])
受另一个 post (Django form field choices, adding an attribute) 的回答启发,我终于让它工作了。事实证明我确实需要子类化 SelectMultiple 小部件。然后,我可以简单地在其上设置一个计数 属性,可以通过 <input class="form-check-input" type="checkbox" data-count="{{widget.data.count}}"
.
在模板中访问该计数
class MyModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
print(obj.count) # this works now
if hasattr(obj, 'count'):
self.widget.count = obj.count
# or, alternatively, add to widgets attrs...
# self.widget.custom_attrs.update({obj.pk: {'count': obj.count}})
return "%s (%s)" % (obj, obj.count)
class MyMultiSelectWidget(widgets.SelectMultiple):
def __init__(self, *args, **kwargs):
self.count = None
# self.custom_attrs = {}
super().__init__(*args, **kwargs)
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
index = str(index) if subindex is None else "%s_%s" % (index, subindex)
if attrs is None:
attrs = {}
option_attrs = self.build_attrs(self.attrs, attrs) if self.option_inherits_attrs else {}
if selected:
option_attrs.update(self.checked_attribute)
if 'id' in option_attrs:
option_attrs['id'] = self.id_for_label(option_attrs['id'], index)
# alternatively, setting the attributes here for the option
#if len(self.custom_attrs) > 0:
# if value in self.custom_attrs:
# custom_attr = self.custom_attrs[value]
# for k, v in custom_attr.items():
# option_attrs.update({k: v})
return {
'name': name,
'count': str(self.count),
'value': value,
'label': label,
'selected': selected,
'index': index,
'attrs': option_attrs,
'type': self.input_type,
'template_name': self.option_template_name,
}
class FiltersForm(forms.Form):
...
studentCheckbox = MyModelMultipleChoiceField(queryset=Student.objects.all(), required=False)
...
def __init__(self, *args, **kwargs):
super(FiltersForm, self).__init__(*args, **kwargs)
students = Student.objects.annotate(count=Count(...))
# These objects feed into the overridden label_from_instance function
self.fields['studentCheckbox'].queryset = students
#self.fields['studentCheckbox'].label_from_instance = lambda obj: "%s (%s)" % (students.get(pk=obj.pk)['name'], students.get(pk=obj.pk)['count'])
如果还有其他更优的实现方式,请告诉我!
我将 Django Forms 用于我的 Web 应用程序的前端过滤器功能,并且我正在对 Field 进行一些自定义,以便我可以显示带有自定义标签的多个 select 复选框,如下所示:
[x] 有趣的道格 (1)
[ ] 斯基特情人节 (5)
[x]帕蒂蛋黄酱(3)
[ ] 罗杰·克洛茨 (9)
在 select 选择一个选项后,我可以通过重写我的 Forms init 方法来动态更新复选框字段标签(特别是计数),如下所示:
class FiltersForm(forms.Form):
...
studentCheckbox = MyModelMultipleChoiceField(widget=MyMultiSelectWidget, queryset=Student.objects.all(), required=False)
...
def __init__(self, *args, **kwargs):
super(FiltersForm, self).__init__(*args, **kwargs)
students = Student.objects.values(...).annotate(count=Count(...))
self.fields['studentCheckbox'].queryset = Student.objects.all()
# dynamically updating the field's label here to include a count
self.fields['studentCheckbox'].label_from_instance = lambda obj: "%s (%s)" % (students.get(pk=obj.pk)['name'], students.get(pk=obj.pk)['count'])
但不是 "hardcode" 字段标签中的计数,我想动态地将计数设置为每个小部件选项字段上的 'data-count' 属性。在尝试这样做时,我将 forms.ModelMultipleChoiceField
子类化为 MyModelMultipleChoiceField
.
我希望重写MyModelMultipleChoiceField
中的label_from_instance
函数来动态访问obj(通过pk),并在此过程中设置一个data-count属性。但是,出于某种原因,label_from_instance
函数并未从我的表单 init (self.fields['studentCheckbox'].label_from_instance
) 中的 lambda 调用中调用。我还尝试覆盖表单和自定义小部件 (MyMultiSelectWidget) 上的 label_from_instance
函数,但无济于事。
class MyModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
print(obj) # nothing prints
if hasattr(obj, 'count'):
self.widget.attrs.update({obj.pk: {'data-count': obj.count}})
return obj
# I probably don't need to subclass the Widget, but just in case...
# I originally thought I could do something with create_option(count=None), but I need access to the
# obj, as I can't use a lambda with self.fields['studentCheckbox'].widget.count = lambda...
class MyMultiSelectWidget(widgets.SelectMultiple):
def __init__(self, count=None, *args, **kwargs):
super().__init__(*args, **kwargs)
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
options = super(MyMultiSelectWidget, self).create_option(name, value, label, selected, index, subindex=None, attrs=None)
return options
我是 Django 的新手,我觉得我遇到了很多边缘情况,所以我很感激任何帮助!
更新 #1:
我意识到,在我的表单 init 中,我没有 调用 字段的 label_from_instance
函数,而是 用 self.fields['studentCheckbox'].label_from_instance = lambda obj: "%s (%s)" % (students.get(pk=obj.pk)['name'], students.get(pk=obj.pk)['count'])
.
因此,我注释掉了该行,现在调用了被覆盖的函数。虽然我现在可以访问 obj 的计数,但它仍然没有出现在呈现的 HTML 中。更新后的代码如下。
class MyModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
print(obj.count) # this works now
if hasattr(obj, 'count'):
# no error, but not appearing in rendered html
self.widget.attrs.update({obj.pk: {'data-count': obj.count}})
return obj
class FiltersForm(forms.Form):
...
studentCheckbox = MyModelMultipleChoiceField(queryset=Student.objects.all(), required=False)
...
def __init__(self, *args, **kwargs):
super(FiltersForm, self).__init__(*args, **kwargs)
students = Student.objects.annotate(count=Count(...))
# These objects feed into the overridden label_from_instance function
self.fields['studentCheckbox'].queryset = students
#self.fields['studentCheckbox'].label_from_instance = lambda obj: "%s (%s)" % (students.get(pk=obj.pk)['name'], students.get(pk=obj.pk)['count'])
受另一个 post (Django form field choices, adding an attribute) 的回答启发,我终于让它工作了。事实证明我确实需要子类化 SelectMultiple 小部件。然后,我可以简单地在其上设置一个计数 属性,可以通过 <input class="form-check-input" type="checkbox" data-count="{{widget.data.count}}"
.
class MyModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
print(obj.count) # this works now
if hasattr(obj, 'count'):
self.widget.count = obj.count
# or, alternatively, add to widgets attrs...
# self.widget.custom_attrs.update({obj.pk: {'count': obj.count}})
return "%s (%s)" % (obj, obj.count)
class MyMultiSelectWidget(widgets.SelectMultiple):
def __init__(self, *args, **kwargs):
self.count = None
# self.custom_attrs = {}
super().__init__(*args, **kwargs)
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
index = str(index) if subindex is None else "%s_%s" % (index, subindex)
if attrs is None:
attrs = {}
option_attrs = self.build_attrs(self.attrs, attrs) if self.option_inherits_attrs else {}
if selected:
option_attrs.update(self.checked_attribute)
if 'id' in option_attrs:
option_attrs['id'] = self.id_for_label(option_attrs['id'], index)
# alternatively, setting the attributes here for the option
#if len(self.custom_attrs) > 0:
# if value in self.custom_attrs:
# custom_attr = self.custom_attrs[value]
# for k, v in custom_attr.items():
# option_attrs.update({k: v})
return {
'name': name,
'count': str(self.count),
'value': value,
'label': label,
'selected': selected,
'index': index,
'attrs': option_attrs,
'type': self.input_type,
'template_name': self.option_template_name,
}
class FiltersForm(forms.Form):
...
studentCheckbox = MyModelMultipleChoiceField(queryset=Student.objects.all(), required=False)
...
def __init__(self, *args, **kwargs):
super(FiltersForm, self).__init__(*args, **kwargs)
students = Student.objects.annotate(count=Count(...))
# These objects feed into the overridden label_from_instance function
self.fields['studentCheckbox'].queryset = students
#self.fields['studentCheckbox'].label_from_instance = lambda obj: "%s (%s)" % (students.get(pk=obj.pk)['name'], students.get(pk=obj.pk)['count'])
如果还有其他更优的实现方式,请告诉我!