如何将数据属性添加到django模型表单modelchoicefield

ncecgwcz  于 2023-01-18  发布在  Go
关注(0)|答案(4)|浏览(150)

我有一个django模型表单'Recipe',其中有一个外键字段指向模型'Ingredient'。
在呈现表单时,我得到了一个SELECT列表,它的ID与配料ID匹配,文本显示与字段的字符串表示相同。
但是,我想向选择列表添加一个数据属性,该属性与来自Ingredient查询集的呈现选项相匹配。
例如,假设这是当前正在渲染的内容:

<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>
rsaldnfx

rsaldnfx1#

一种方法是使用自定义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

要填充单个选项,请覆盖ModelChoiceField子类上的label_from_instance方法(参见django docs):

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
    }

最后,在表单中简单使用此字段:

RecipeModelForm(ModelForm):

class Meta:
    model = Recipe
    fields = [
        # other fields ...
        'ingredients',
    ]
    
    field_classes = {
        'ingredients': IngredientChoiceField
    }
wooyq4lh

wooyq4lh2#

为什么不手动渲染字段?
就像

<select>
  {% for option in form.ingredient.choices %}
     <option value="{{ option.id }}" data-ingredient-type={{ option.type }}>{{ option.name }}</option>
  {% endfor %}
</select>

或者可能在你的模型表单类中你给它添加了属性,但是这必须是一个字符串(或者可能是一个函数)

widgets = { ...
     'ingredients' = forms.Select(attrs={'data-ingredient-type': 'fruit'}),
   ...}
axzmvihb

axzmvihb3#

我的解决方案是创建一个覆盖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,
        }

感谢In Django form, custom SelectField and SelectMultipleField为我指明了这个解决方案。
我对这个解决方案并不完全满意,因为它假设valueIngredientpk,并执行直接数据库查询以获取Ingredient选项,似乎模型对象应该可以从ModelChoiceField获得,但我无法找到获取它的方法。

mzillmmw

mzillmmw4#

这在新版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
        )

相关问题