【问题标题】:Dynamically Update MultipleChoiceField Option Attributes in Django Form在 Django 表单中动态更新 MultipleChoiceField 选项属性
【发布时间】:2020-06-13 02:34:37
【问题描述】:

我将 Django Forms 用于我的 Web 应用程序的前端过滤器功能,并且我正在进行一些字段自定义,以便我可以显示带有自定义标签的多选复选框,如下所示:

[x] 道格搞笑 (1)
[ ] 斯基特瓦伦丁(5)
[x] 帕蒂蛋黄酱(3)
[ ] 罗杰·克洛茨 (9)

选择一个选项后,我可以通过覆盖我的表单 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'])

但不是“硬编码”字段标签中的计数,我想动态地将计数设置为每个小部件选项字段的“数据计数”属性。在我尝试这样做时,我将forms.ModelMultipleChoiceField 子类化为MyModelMultipleChoiceField

我希望重写MyModelMultipleChoiceField中的label_from_instance函数来动态访问obj(通过pk)并在过程中设置data-count属性。然而,出于某种原因,我的表单的 init (self.fields['studentCheckbox'].label_from_instance) 中的 lambda 调用并未调用 label_from_instance 函数。我还尝试在表单和自定义小部件 (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'])

【问题讨论】:

    标签: django django-forms django-widget


    【解决方案1】:

    受另一篇帖子 (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'])
    

    如果还有其他更优化的实现,请告诉我!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-06
      • 2016-10-03
      • 1970-01-01
      • 2011-08-31
      相关资源
      最近更新 更多