【问题标题】:Django CheckboxSelectMultiple override 'choices' from ModelFormDjango CheckboxSelectMultiple 覆盖 ModelForm 中的“选择”
【发布时间】:2012-01-31 08:53:08
【问题描述】:

我希望能够在我的 django 表单中提取不同的信息:

这是我的表格:

<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>

class InstanceForm(ModelForm):
    class Meta:
        model = models.BaseAsset
        widgets = {
            'labels': LabelIconCheckboxSelectMultiple()
        }

型号:

class AssetClass(models.Model):
    default_labels = models.ManyToManyField(Label, null=True, blank=True)
    pass

M2M 参考字段

class Label(models.Model):
    explanation = models.CharField(null=True, max_length=63)
    svgpreview  = models.CharField(null=True, max_length=31)
    def __unicode__(self):
        return unicode(self.explanation)
    pass

现在{{ form.as_p }}生成的HTML代码如下:

<li><label for="id_labels_0"><input type="checkbox" name="labels" value="1" id="id_labels_0" /> Consult owner before using</label></li>
<li><label for="id_labels_1"><input type="checkbox" name="labels" value="2" id="id_labels_1" /> This item is broken</label></li>

这意味着它显然使用了模型“标签”的__unicode__ 渲染。如何在 Select 小部件中更改该行为,以便它使用不同的函数来填充它的选择?我正在尝试以合理便携的方式在复选框旁边打印'&lt;img src="{{label.svgpreview}}" alt="{{label.explanation}}"...&gt;'?

【问题讨论】:

    标签: django django-forms


    【解决方案1】:

    这里的 Django 文档对此进行了解释: https://docs.djangoproject.com/en/1.9/ref/forms/fields/#django.forms.ModelChoiceField.to_field_name

    您可以在此处看到 ModelChoiceField 类调用字段上的方法: https://github.com/django/django/blob/1155843a41af589a856efe8e671a796866430049/django/forms/models.py#L1174

    如果您没有显式覆盖 choices,那么您的代码可能如下所示:

    class RectificationAssetMultiField(forms.ModelMultipleChoiceField):
        def label_from_instance(self, obj):
            return '[{0.pk}] {0.label} ({0.location})'.format(obj)
    
    
    class RectificationForm(forms.ModelForm):
        items = RectificationAssetMultiField(
            required=False,
            queryset=InspectionItem.objects.all(),
            widget=forms.CheckboxSelectMultiple,
            label="Non-compliant Assets"
        )
    
        class Meta:
            model = Rectification
            fields = ('ref', 'items', 'status')
    

    请注意,这仅在您不直接设置 choices 时才有效(请参阅上述 URL 中的 _get_choices)。

    如果您想要覆盖选择(以获得比查询集更有效的结果,或者更好地表示为 ValuesList),那么您将拥有如下内容:

    class RectificationAssetMultiField(forms.ModelMultipleChoiceField):
        def label_from_instance(self, obj):
            return '[{0.pk}] {0.label} ({0.location})'.format(obj)
    
    
    class RectificationForm(forms.ModelForm):
        items = RectificationAssetMultiField(
            required=False,
            queryset=InspectionItem.objects.none(),
            widget=forms.CheckboxSelectMultiple,
            label="Non-compliant Assets"
        )
    
        def __init__(self, *args, **kwargs):
            super(RectificationForm, self).__init__(*args, **kwargs)
            self.fields['items'].choices = (InspectionItem.objects
                .active()
                .noncompliant()
                .filter(property_id=self.instance.property_id)
                .values_list('pk', 'label') # pass a key value pair
            )
    
        class Meta:
            model = Rectification
            fields = ('ref', 'items', 'status')
    

    【讨论】:

      【解决方案2】:

      您不必执行“令人毛骨悚然”的实例级覆盖即可充分利用 documented django.forms.models.ModelChoiceField.label_from_instance() 方法。

      以原帖中的 AssetClass 和 Label 对象为基础:

      class AssetSvgMultiField(forms.ModelMultipleChoiceField):
          """
          Custom ModelMultipleChoiceField that labels instances with their svgpreview.
          """
          def label_from_instance(self, obj):
              return obj.svgpreview
      
      
      class InstanceForm(forms.ModelForm):
          default_labels = AssetSvgMultiField(queryset=Label.objects.all())
      
          class Meta:
              model = models.AssetClass
              widgets = {
                  'default_labels': forms.CheckboxSelectMultiple()
              }
      

      【讨论】:

        【解决方案3】:

        阅读django.forms.models.ModelChoiceField给出提示:

        # this method will be used to create object labels by the QuerySetIterator.
        # Override it to customize the label.
        def label_from_instance(self, obj):
            """
            This method is used to convert objects into strings; it's used to
            generate the labels for the choices presented by this object. Subclasses
            can override this method to customize the display of the choices.
            """
            return smart_unicode(obj)
        

        好的,但是如何在 ModelForm 的每个实例中覆盖它 - 这在整个 django.forms 的几个地方都会被覆盖

        考虑以下代码:

        class InstanceForm(ModelForm):
            class Meta:
                model = models.BaseAsset
                widgets = {
                    'labels': forms.CheckboxSelectMultiple()
                }
        
        
            def __init__(self, *args, **kwargs):
                def new_label_from_instance(self, obj):
                    return obj.svgpreview
        
                super(InstanceForm, self).__init__(*args, **kwargs)
                funcType = type(self.fields['labels'].label_from_instance)
                self.fields['labels'].label_from_instance = funcType(new_label_from_instance, self.fields['labels'], forms.models.ModelMultipleChoiceField)
        

        这有点令人毛骨悚然 - 基本上,这是一个更奇怪的实现: Override a method at instance level

        请阅读引用线程中的 cmets 以了解为什么这通常是一个坏主意..

        【讨论】:

          【解决方案4】:

          您将覆盖forms.widgets.CheckboxSelectMultiple 类:

          这是 CheckboxSelectMultiple 类及其渲染函数:

          class CheckboxSelectMultiple(SelectMultiple):
              def render(self, name, value, attrs=None, choices=()):
                  if value is None: value = []
                  has_id = attrs and 'id' in attrs
                  final_attrs = self.build_attrs(attrs, name=name)
                  output = [u'<ul>']
                  # Normalize to strings
                  str_values = set([force_unicode(v) for v in value])
                  for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
                      # If an ID attribute was given, add a numeric index as a suffix,
                      # so that the checkboxes don't all have the same ID attribute.
                      if has_id:
                          final_attrs = dict(final_attrs, id='%s_%s' % (attrs['id'], i))
                          label_for = u' for="%s"' % final_attrs['id']
                      else:
                          label_for = ''
          
                      cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
                      option_value = force_unicode(option_value)
                      rendered_cb = cb.render(name, option_value)
                      option_label = conditional_escape(force_unicode(option_label))
                      output.append(u'<li><label%s>%s %s</label></li>' % (label_for, rendered_cb, option_label))
                  output.append(u'</ul>')
                  return mark_safe(u'\n'.join(output))
          

          那么你会怎么做:

          class MyCheckboxSelectMultiple(CheckboxSelectMultiple):
              def render(self, name, value, attrs=None, choices=()):
                  #put your code to have custom checkbox control with icon
                  #...
                  output.append(u'<li><label%s>%s %s</label></li>' % (label_for, rendered_cb, option_label)) # especially you will be working on this line
                  #...
          

          那么你使用widgets=CheckboxSelectMultiple()的地方会变成widgets=MyCheckboxSelectMultiple()

          【讨论】:

          • 您的回答揭示了内部发生的事情 - 不幸的是,我需要重新定义 self.choices 内部的内容 - 这一直在django.forms.models.ModelChoiceField.. 看到我的自我回答如果你很好奇,它实际上既 hackish 又酷!
          【解决方案5】:

          如果您不喜欢该渲染,请不要使用 {{ form.as_p }}。

          改为循环遍历表单:

          <form action="/contact/" method="post">
              {% for field in form %}
                  <div class="fieldWrapper">
                      {{ field.errors }}
                      {{ field.label_tag }}: {{ field }}
                  </div>
              {% endfor %}
              <p><input type="submit" value="Send message" /></p>
          </form>
          

          然后您就可以随意使用任何您想要的 HTML。

          发件人:https://docs.djangoproject.com/en/dev/topics/forms/#looping-over-the-form-s-fields

          【讨论】:

          • 不幸的是,这种方法并不能解决我的问题 - 我想重新定义通过一个小部件提取的内容,在 {{ field }} 内,几个级别的管道。在我的问题中,我特别询问了如何说服 ModelForm 采用 M2M 关系映射的特定字段,而不是默认的 unicode 渲染。请参阅我的自我回答
          • 是的,我知道你在追求什么,而且我一直在那里,深入了解 Django 的内部结构。但问题是:您知道自己想要什么 HTML,并且拥有呈现该 HTML 所需的所有字段。所以我说只用〜5行模板来渲染它,而不是〜50行python。如果您希望使其更可重用,请在模板标签中拆分模板代码。抱歉,如果我的回复带有对抗性,我只是想分享我的经验。
          • 直接通过模板提取这些信息会有些棘手 - 不牺牲表单本身,它的值标签等。基本上,就我而言,标签列表实际上是动态的,并且这个逻辑真的应该属于 MVC 的控制器部分,而不是被塞进 HTML 模板中——InstanceForm 仍然可以继承自.. 它实际上只是 2 行 python(有点复杂的双气泡 OOP)。查看我的自我回答,我认为它甚至可以包含在文档中 - 为什么要强迫用户依赖 unicode caster?
          猜你喜欢
          • 1970-01-01
          • 2016-03-20
          • 2020-12-03
          • 2017-01-08
          • 2012-03-19
          • 2012-04-10
          • 1970-01-01
          • 2010-10-23
          • 1970-01-01
          相关资源
          最近更新 更多