如何使用每个选择的模板覆盖 ModelChoiceField / ModelMultipleChoiceField 默认小部件
How to override ModelChoiceField / ModelMultipleChoiceField default widget with a template for each choice
背景
我有两个模型,运行和订单。一个 运行 将完成许多订单,因此我的订单和 运行 之间存在多对一关系,表示为我订单上的外键。
我要建一个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: Extremely useful layout object. You can use it to set attributes in a field or render a specific field with a custom template. This way you avoid having to explicitly override the field’s widget and pass an ugly attrs dictionary:
我看到 this answer 建议使用 label_from_instance
。但是我不确定如何将一堆 html 塞进 label_from_instance
。我真的不想使用不同的标签,而是想要一个生成一堆 html 的模板,它显示有关整个订单对象的详细信息,所以我不确定这种方法是否有效。
this question中的答案大多让我感到困惑,但接受的答案似乎没有用。 (也许是 django 版本问题,或者是脆皮表格问题?)
TL;DR
如何使用 ModelMultipleChoiceField 中每个模型的数据呈现模板?
小部件控制字段在 HTML 表单中的呈现方式。 Select 小部件(及其变体)有两个属性 template_name
和 option_template_name
。 option_template_name
提供用于 select 选项的模板名称。您可以子类化 select 小部件以覆盖这些属性。使用子类,如 CheckboxSelectMultiple,可能是一个很好的起点,因为默认情况下它不会在 <select>
元素中呈现选项,所以你的样式会更容易。
默认情况下,复选框Select多个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>
您会在每个 label/input 之后看到蓝色 div。像这样
现在,诀窍在于如何让小部件命名空间可用的对象。默认情况下,只有某些属性出现在小部件上,如 return 由小部件的 get_context
method 编辑。
您可以使用自己的 MultipleModelChoiceField
子类并覆盖 label_from_instance
来完成此操作。由 label_from_instance
编辑的值 return 最终作为 label
属性提供给小部件,当它呈现 {{ widget.label }}
时,它用于模型表单中的可见文本.
只需将整个对象 label_from_instance
覆盖为 return,然后将此子类用于您的字段。
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
return 对象。
- 创建您自己的 Select 多个小部件子类以指定自定义
option_template_name
。
- 指定的模板将用于呈现每个选项,其中
widget.label
将是每个对象。
背景
我有两个模型,运行和订单。一个 运行 将完成许多订单,因此我的订单和 运行 之间存在多对一关系,表示为我订单上的外键。
我要建一个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: Extremely useful layout object. You can use it to set attributes in a field or render a specific field with a custom template. This way you avoid having to explicitly override the field’s widget and pass an ugly attrs dictionary:
我看到 this answer 建议使用
label_from_instance
。但是我不确定如何将一堆 html 塞进label_from_instance
。我真的不想使用不同的标签,而是想要一个生成一堆 html 的模板,它显示有关整个订单对象的详细信息,所以我不确定这种方法是否有效。this question中的答案大多让我感到困惑,但接受的答案似乎没有用。 (也许是 django 版本问题,或者是脆皮表格问题?)
TL;DR
如何使用 ModelMultipleChoiceField 中每个模型的数据呈现模板?
小部件控制字段在 HTML 表单中的呈现方式。 Select 小部件(及其变体)有两个属性 template_name
和 option_template_name
。 option_template_name
提供用于 select 选项的模板名称。您可以子类化 select 小部件以覆盖这些属性。使用子类,如 CheckboxSelectMultiple,可能是一个很好的起点,因为默认情况下它不会在 <select>
元素中呈现选项,所以你的样式会更容易。
默认情况下,复选框Select多个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>
您会在每个 label/input 之后看到蓝色 div。像这样
现在,诀窍在于如何让小部件命名空间可用的对象。默认情况下,只有某些属性出现在小部件上,如 return 由小部件的 get_context
method 编辑。
您可以使用自己的 MultipleModelChoiceField
子类并覆盖 label_from_instance
来完成此操作。由 label_from_instance
编辑的值 return 最终作为 label
属性提供给小部件,当它呈现 {{ widget.label }}
时,它用于模型表单中的可见文本.
只需将整个对象 label_from_instance
覆盖为 return,然后将此子类用于您的字段。
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
return 对象。 - 创建您自己的 Select 多个小部件子类以指定自定义
option_template_name
。 - 指定的模板将用于呈现每个选项,其中
widget.label
将是每个对象。