Django 多级选择字段的表单渲染

78 阅读2分钟

在一个 Django 应用中,我们需要构建一个表单,其中包含一个多级选择字段。该字段由两级组成:第一级是类别,第二级是专业。我们希望在模板中显示所有类别,并在每个类别下显示其专业。

我们定义了三个模型:

class Category_Specialization (models.Model):
  name = models.CharField(max_length=50)

class Specialization (models.Model):
  specialization_name = models.CharField (max_length=60)
  category = models.ForeignKey(Category_Specialization)

class User (models.Model):
[...]
  specialization = models.ManyToManyField (Specialization)

我们还创建了一个表单来显示用户专业的多选框:

class SpecializationForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(UserServiceForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_class = 'form-horizontal'
        self.helper.render_hidden_fields = True
        self.helper.form_tag = False
        self.helper.label_class = 'col-lg-2'
        self.helper.field_class = 'col-lg-10'

        self.helper.layout = Layout(
            'specialization',
            FormActions(
                Submit('submit', 'Create profile'),
                
https://user.duoip.cn/register/?kefu=dingyuan
            )
        )

    class Meta:
        model = User
        fields = ['specialization']
        widgets = {
        'specialization' : forms.CheckboxSelectMultiple,
        }

在视图中,我们获取所有类别并实例化表单:

list_categories = Category_Specialization.objects.order_by('name')
forms = UserSpecializationForm(request.POST or None, instance=request.user)

return render (request, 'users/profile/specialization.html', {
  'list_categories' : list_categories,
  'forms' : forms,
})

在模板中,我们尝试遍历类别和表单字段,并显示属于当前类别的字段:

<form action="{% url 'users:specialization'  %}" method="post">
{% csrf_token %}

{% for category in list_categories %}
  <fieldset>
  <legend>{{category.name}}</legend>
  {% for form in forms %}
    {% for form_field in form %}
      {% if form_field.field.category.name == category.name %}
       {% load crispy_forms_tags %}
       {{form_field}}
       {% crispy form %}
      {% endif %}
    {% endfor %}
  {% endfor %}
  </fieldset>
{% endfor %}

然而,上面的代码无法正确显示表单字段,因为我们没有正确访问字段的类别名称。

  1. 解决方案

一种解决方法是使用自定义小部件来渲染字段。在 form.py 中,我们可以定义一个新的多选框小部件:

class SpecializationByCategory(forms.CheckboxSelectMultiple):
    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])
        categories = Category_Specialization.objects.all()
        #for supercategory in supercategories:
        for category in categories:
            output.append(u'<li>%s</li>'%(category.name))
            output.append(u'<ul>')
            del self.choices
            self.choices = []
            specializations = Specialization.objects.filter(category=category)
            for specialization in specializations:
                self.choices.append((specialization.id,specialization.specialization_name))
                for i, (option_value, option_label) in enumerate(chain(self.choices, choices)):
                    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 = forms.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>')
            output.append(u'</li>')
        output.append(u'</ul>')
        return mark_safe(u'\n'.join(output))

然后,在 SpecializationForm 中使用这个小部件:

class SpecializationForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(UserServiceForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_class = 'form-horizontal'
        self.helper.render_hidden_fields = True
        self.helper.form_tag = False
        self.helper.label_class = 'col-lg-2'
        self.helper.field_class = 'col-lg-10'

        self.helper.layout = Layout(
            'specialization',
            FormActions(
                Submit('submit', 'Create profile'),
            )
        )

    class Meta:
        model = User
        fields = ['specialization']
        widgets = {
            'specialization' : SpecializationByCategory,
        }

这样,我们就可以在模板中正确显示表单字段了。