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

Posted

技术标签:

【中文标题】如何使用每个选项的模板覆盖 ModelChoiceField / ModelMultipleChoiceField 默认小部件【英文标题】:How to override ModelChoiceField / ModelMultipleChoiceField default widget with a template for each choice 【发布时间】:2018-08-26 18:36:07 【问题描述】:

背景

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

我想构建一个 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'),
    )

问题

    我不确定在 list_orders.html 模板中可以使用哪些本地人。似乎有 field form.visible_fields,但如果我深入了解其中一个,我会得到一个TypeError: 'SubWidget' object is not iterable,几乎没有在线记录。

    以上表明我可能仍然会在模板中获得一个小部件,尽管Field('orders', template="runs/list_orders.html"), 应该阻止这种情况,根据crispy docs:

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

    我见过this answer,它建议使用label_from_instance。但是我不确定如何将一堆 html 塞入label_from_instance。我真的想要一个模板来生成一堆显示整个订单对象详细信息的 html,而不是使用不同的标签,所以我不确定这种方法是否有效。

    this question 中的答案大部分让我感到困惑,但似乎接受的答案不起作用。 (可能是 django 版本问题,还是脆皮表单问题?)

TL;DR

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

【问题讨论】:

您是否尝试过使用自定义小部件@AlexLenail? ***.com/questions/44675550/… 【参考方案1】:

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

默认情况下,CheckboxSelectMultiple option_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)

假设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。像这样的

现在,诀窍是如何让小部件命名空间可用的对象。默认情况下,小部件上仅存在某些属性,由小部件的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 %

它会产生以下效果。

这也不会破坏使用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 将是每个对象。

【讨论】:

很遗憾,我无法让第一部分工作。我更改了detail_options.html,但无法获得要渲染的内容。所有呈现的是默认的 ModelMultipleChoiceField 小部件。我很难弄清楚如何调试它,因为我已经完全遵循了这个答案,甚至无法得到一个错误来引导我去某个地方。也许这与 django-crispy-forms 有关? option_template_name 的路径根应该是什么?我试过/myapp/templates/orders/detail_options.html/orders/detail_options.html 都没有成功。有什么方法可以强制应用程序以某种方式中断,这会告诉我为什么它似乎忽略了这些更改?

以上是关于如何使用每个选项的模板覆盖 ModelChoiceField / ModelMultipleChoiceField 默认小部件的主要内容,如果未能解决你的问题,请参考以下文章

django ModelChoiceField:如何遍历模板中的实例?

OpenCover - 如何查看每个测试的覆盖率?

如何在 html 电子邮件模板中覆盖 gmail 视口?

Django:覆盖每个应用程序而不是每个项目的“不可覆盖”管理模板?

覆盖模板时 TeamCity 中的 VCS 标签

如何在列表视图之上设置选项卡布局但在 Android 中不覆盖?