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

Ale*_*ail 5 python django django-forms django-crispy-forms

背景

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

我想构建一个 UI 来创建运行。它应该是一种有人选择要运行的订单的形式。我想在每个订单的信息旁边显示一个复选框列表。我现在正在使用django 脆皮形式。

视图.py

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

表格.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'),
    )
Run Code Online (Sandbox Code Playgroud)

问题

  1. 我不确定list_orders.html模板中有哪些当地人可供我使用。似乎有{{ field }},也许form.visible_fields但是如果我深入研究其中一个TypeError: 'SubWidget' object is not iterable,我会得到一个,它几乎没有在线记录。

  2. 以上表明我可能仍然在模板中获得一个小部件,尽管事实上Field('orders', template="runs/list_orders.html"),应该防止这种情况发生,根据脆皮文档:

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

  3. 我看过这个答案,建议使用label_from_instance. 但是,我不确定如何将一堆 html 填充到label_from_instance. 我真的想要一个模板,而不是有一个不同的标签,它会生成一堆 html 来显示有关整个订单对象的详细信息,所以我不确定这种方法是否可行。

  4. 这个问题的答案大多让我感到困惑,但似乎接受的答案不起作用。(可能是 django 版本问题,还是脆皮形式问题?)

TL; 博士

如何使用 ModelMultipleChoiceField 中每个模型的数据呈现模板?

syt*_*ech 5

小部件控制字段在 HTML 表单中的呈现方式。Select 小部件(及其变体)有两个属性template_name和option_template_name. 该option_template_name设备使用模板的名称,选择选项。您可以将选择小部件子类化以覆盖这些属性。使用子类,比如CheckboxSelectMultiple,可能是一个很好的起点,因为默认情况下它不会在<select>元素中呈现选项,所以你的样式会更容易。

默认情况下 CheckboxSelectMultipleoption_template_name是'django/forms/widgets/checkbox_option.html'.

您可以提供自己的模板,该模板将按照您的意愿呈现订单的详细信息。IE 在你forms.py

class MyCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
    option_template_name = 'myapp/detail_options.html'

class CreateRunForm(forms.ModelForm):
    ...
    orders = ModelMultipleChoiceField(..., widget=MyCheckboxSelectMultiple)
Run Code Online (Sandbox Code Playgroud)

假设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>
Run Code Online (Sandbox Code Playgroud)

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

自定义选项模板

现在,诀窍是如何让对象可用于小部件命名空间。默认情况下,小部件上仅存在某些属性,由小部件的get_context方法返回。

您可以使用自己的子类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)
Run Code Online (Sandbox Code Playgroud)

所以现在在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 %}
Run Code Online (Sandbox Code Playgroud)

并且会产生以下效果。

对象黑客

这也不会破坏在任何地方widget.label使用的小部件标签文本的默认行为。请注意,在上图中,标签文本(例如Order object (1))与我们将更改应用于 之前相同label_from_instance。这是因为模板渲染中的默认设置是str(obj)在与模型对象一起呈现时使用;以前默认情况下会做的同样的事情label_from_instance。

TL; 博士

  • 使自己的子类ModelMultiplechoiceField有label_from_instance返回的对象。
  • 制作您自己的 SelectMultiple 小部件子类以指定自定义option_template_name.
  • 指定的模板将用于呈现每个选项,widget.label每个对象将在哪里。