如何使用每个选项的模板覆盖ModelChoiceField / ModelMultipleChoiceField默认小部件

时间:2018-03-17 21:16:10

标签: python django django-forms django-crispy-forms

背景

我有两个模型,运行和订单。一次运行将完成许多订单,因此我的订单和运行之间存在多对一关系,在我的订单上表示为外键。

我想构建一个用于创建运行的UI。它应该是某人选择要运行的订单的表单。我想显示一个复选框列表以及每个订单的相关信息。我现在正在使用django crispy forms

views.py

class createRunView(LoginRequiredMixin, CreateView):
    model = Run
    form_class = CreateRunForm
    template_name = 'runs/create_run.html'

forms.py

class CreateRunForm(forms.ModelForm):
    class Meta:
       model = Run
       fields = ['orders',]

    orders = forms.ModelMultipleChoiceField(queryset=Order.objects.filter(is_active=True, is_loaded=False))

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_method = 'post'
        self.helper.layout = Layout(
            Field('orders', template="runs/list_orders.html"),
            Submit('save', 'Create Run'),
            Button('cancel', 'Cancel'),
    )

问题

  1. 我不确定list_orders.html模板中我可以使用哪些本地人。似乎有{{ field }}form.visible_fields,但如果我深入研究,我会得到TypeError: 'SubWidget' object is not iterable,这在网上几乎没有记录。

  2. 以上建议我可能仍会在模板中获取一个小部件,尽管Field('orders', template="runs/list_orders.html"),应根据crispy docs阻止该小部件:

      

    字段:非常有用的布局对象。您可以使用它在字段中设置属性或使用自定义模板呈现特定字段。这样您就可以避免显式覆盖字段的窗口小部件并传递丑陋的attrs字典:

  3. 我看过this answer建议使用label_from_instance。但是,我不确定如何将一堆html填入label_from_instance。我没有使用不同的标签,而是希望有一个模板生成一堆html,其中显示了整个订单对象的详细信息,因此我不确定这种方法是否有效。

  4. this question中的答案大多让我感到困惑,但接受的答案似乎并不奏效。 (可能是django版本问题,还是一个脆弱的表格问题?)

  5. TL; DR

    如何使用ModelMultipleChoiceField中每个模型的数据渲染模板?

1 个答案:

答案 0 :(得分:3)

窗口小部件控制字段在HTML表单中的呈现方式。 “选择”窗口小部件(及其变体)具有两个属性template_nameoption_template_nameoption_template_name提供用于选择选项的模板名称。您可以将选择窗口小部件子类化以覆盖这些属性。使用子类(如CheckboxSelectMultiple)可能是一个很好的起点,因为默认情况下它不会在<select>元素中呈现选项,因此您的样式将更容易。

默认情况下,CheckboxSelectMultiple option_template_name'django/forms/widgets/checkbox_option.html'

您可以提供自己的模板,以便按照您想要的方式呈现订单的详细信息。您forms.py

中的IE浏览器
class MyCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
    option_template_name = 'myapp/detail_options.html'

class CreateRunForm(forms.ModelForm)
    ...
    orders = ModelMultipleChoiceField(..., widget=MyCheckboxSelectMultiple)

假设myapp/detail_options.html包含以下

{# include the default behavior #}
{% include "django/forms/widgets/input_option.html" %} 
{# add your own additional div for each option #}
<div style="background-color: blue">
    <h2>Additional info</h2>
</div>

您会在每个标签/输入后看到蓝色div。像这样的东西

Custom option template

现在,诀窍将是如何让对象可用于窗口小部件命名空间。默认情况下,窗口小部件get_context method返回的窗口小部件上只显示某些属性。

您可以使用自己的MultipleModelChoiceField子类并覆盖label_from_instance来完成此操作。 label_from_instance返回的值最终会作为label属性提供给窗口小部件,该属性在呈现{{ widget.label }}时用于模型表单中的可见文本。

只需覆盖label_from_instance即可返回整个对象,然后将此子类用于您的字段。

class MyModelMultipleChoiceField(forms.ModelMultipleChoiceField):
    def label_from_instance(self, obj):
        return obj

class CreateRunForm(forms.ModelForm):
    ...
    orders = MyModelMultipleChoiceField(..., widget=MyCheckboxSelectMultiple)

现在,在myapp/detail_options模板中,您可以使用widget.label直接访问该对象,并根据需要格式化您自己的内容。例如,可以使用以下选项模板

{% include "django/forms/widgets/input_option.html" %}
{% with order=widget.label %}
    <div style="background-color: blue">
        <h2>Order info</h2>
        <p style="color: red">Order Active: {{ order.is_active }}</p>
        <p style="color: red">Order Loaded: {{ order.is_loaded }}</p>
    </div>
{% endwith %}

它会产生以下效果。

object hack

这也不会破坏使用widget.label的小部件标签文本的默认行为。请注意,在上图中,标签文字(例如Order object (1))与我们将更改应用于label_from_instance之前相同。这是因为模板渲染中的默认值是在使用模型对象时使用str(obj);与以前由默认label_from_instance完成的事情相同。

TL; DR

  • 创建自己的ModelMultiplechoiceField子类,让label_from_instance返回该对象。
  • 创建自己的SelectMultiple小部件子类以指定自定义option_template_name
  • 指定的模板将用于呈现每个选项,其中widget.label将是每个对象。