我有一个 Django 模型“Recipe”,其中包含模型“Ingredient”的外键字段。
渲染表单时,我得到一个 SELECT 列表,其中的 ID 与成分 ID 匹配,文本显示等于字段的字符串表示形式。
但是,我想将数据属性添加到与成分查询集中呈现的选项相匹配的选择列表。
例如,假设这是当前正在渲染的内容:
<option value="1158">Carrots</option>
<option value="1159">Strawberry</option>
<option value="1160">Onion</option>
<option value="1161">Spinach</option>
但是我想为相关对象添加一个数据属性:
<option value="1158" data-ingredient-type="vegetable">Carrots</option>
<option value="1159" data-ingredient-type="fruit">Strawberry</option>
<option value="1160" data-ingredient-type="vegetable">Onion</option>
<option value="1161" data-ingredient-type="vegetable">Spinach</option>
一种方法是使用自定义 Select 小部件,它允许通过小部件选择的
label
部分传递选项中的各个属性:
(来自这个伟大答案的代码)
class SelectWithOptionAttribute(Select):
"""
Use a dict instead of a string for its label. The 'label' key is expected
for the actual label, any other keys will be used as HTML attributes on
the option.
"""
def create_option(self, name, value, label, selected, index,
subindex=None, attrs=None):
# This allows using strings labels as usual
if isinstance(label, dict):
opt_attrs = label.copy()
label = opt_attrs.pop('label')
else:
opt_attrs = {}
option_dict = super().create_option(name, value,
label, selected, index, subindex=subindex, attrs=attrs)
for key,val in opt_attrs.items():
option_dict['attrs'][key] = val
return option_dict
要填充单个选项,请覆盖
label_from_instance
子类上的ModelChoiceField
方法(请参阅django文档):
class IngredientChoiceField(ModelChoiceField):
"""ChoiceField with puts ingredient-type on <options>"""
# Use our custom widget:
widget = SelectWithOptionAttribute
def label_from_instance(self, obj):
# 'obj' will be an Ingredient
return {
# the usual label:
'label': super().label_from_instance(obj),
# the new data attribute:
'data-ingredient-type': obj.type
}
最后,简单地在表单中使用该字段:
class RecipeModelForm(ModelForm):
class Meta:
model = Recipe
fields = [
# other fields ...
'ingredients',
]
field_classes = {
'ingredients': IngredientChoiceField
}
为什么不手动渲染字段?
会是这样的
<select>
{% for option in form.ingredient.choices %}
<option value="{{ option.id }}" data-ingredient-type={{ option.type }}>{{ option.name }}</option>
{% endfor %}
</select>
或者也许在模型表单类中添加 attribute ,但这必须是一个字符串(或者可能是一个函数)
widgets = { ...
'ingredients' = forms.Select(attrs={'data-ingredient-type': 'fruit'}),
...}
这在新版本的 Django 上变得容易多了:
class SelectWithAttributeField(forms.Select):
def create_option(
self, name, value, label, selected, index, subindex=None, attrs=None
):
option = super().create_option(
name, value, label, selected, index, subindex, attrs
)
if value:
option["attrs"]["data-ingredient-type"] = value.instance. ingredient
return option
在配方模型表单中将其用作:
class RecipeForm(forms.ModelForm):
class Meta:
model = Recipe
fields = "__all__"
widgets = {
"ingredient": SelectWithAttributeField,
}
def __init__(self, *args, **kwargs):
super(RecipeForm, self).__init__(*args, **kwargs)
self.fields["ingredient"].queryset = Ingredient.objects.filter(
recipe=recipe
)
我的解决方案是创建一个覆盖
create_option()
的自定义小部件:
class IngredientSelect(forms.Select):
def create_option(
self, name, value, label, selected, index, subindex=None, attrs=None
):
option = super().create_option(
name, value, label, selected, index, subindex, attrs
)
if value:
ingredient = models.Ingredient.objects.get(pk=value)
option['attrs'].update({
'data-type': ingredient.type
})
return option
然后您需要指定用于表单中成分字段的小部件:
class RecipeForm(forms.ModelForm):
class Meta:
model = models.Recipe
fields = '__all__'
widgets = {
'ingredient': IngredientSelect,
}
感谢在 Django 表单中,自定义 SelectField 和 SelectMultipleField 向我指出了这个解决方案。
我对这个解决方案并不完全满意,因为它假设
value
是 pk
的 Ingredient
并执行直接数据库查询来获取 Ingredient
选项。看起来模型对象应该可以从 ModelChoiceField
获得,但我无法找到获取它的方法。