You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Django的JSONField实现友好的键值对输入组件?

Django 实现基于ItemType的动态属性JSON输入表单

1. 动态生成属性字段的表单类

重写表单的__init__方法,根据当前Item的类型(或用户选择的类型)动态加载对应的属性输入字段:

from django import forms
from .models import Item, ItemAttributeSpec, ItemType

class ItemForm(forms.ModelForm):
    class Meta:
        model = Item
        fields = ['title', 'item_type']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 获取当前关联的ItemType:编辑时取实例的类型,新增时从POST数据中获取
        item_type = None
        if self.instance.pk:
            item_type = self.instance.item_type
        elif 'item_type' in self.data:
            try:
                item_type = ItemType.objects.get(id=self.data['item_type'])
            except ItemType.DoesNotExist:
                pass

        if item_type:
            # 加载该类型下的所有属性规格
            specs = ItemAttributeSpec.objects.filter(item_type=item_type).select_related('attribute')
            for spec in specs:
                attr_title = spec.attribute.title
                field_name = f'attr_{attr_title}'
                # 根据属性是否有选项生成对应表单字段
                if spec.choices:
                    field = forms.ChoiceField(
                        label=attr_title,
                        choices=[(opt, opt) for opt in spec.choices],
                        required=spec.required
                    )
                else:
                    field = forms.CharField(
                        label=attr_title,
                        required=spec.required
                    )
                self.fields[field_name] = field
                # 编辑状态下填充已保存的属性值
                if self.instance.pk and attr_title in self.instance.attributes:
                    self.initial[field_name] = self.instance.attributes[attr_title]

2. 表单验证与数据处理

重写clean方法收集属性输入值、验证必填项,最终将数据整理为JSON格式:

def clean(self):
        cleaned_data = super().clean()
        item_type = cleaned_data.get('item_type') or self.instance.item_type
        if not item_type:
            return cleaned_data

        attributes = {}
        specs = ItemAttributeSpec.objects.filter(item_type=item_type).select_related('attribute')
        for spec in specs:
            attr_title = spec.attribute.title
            field_name = f'attr_{attr_title}'
            value = cleaned_data.get(field_name)
            # 验证必填项
            if spec.required and not value:
                self.add_error(field_name, f"{attr_title} 为必填项")
            if value is not None:
                attributes[attr_title] = value

        cleaned_data['attributes'] = attributes
        return cleaned_data

    def save(self, commit=True):
        instance = super().save(commit=False)
        instance.attributes = self.cleaned_data['attributes']
        if commit:
            instance.save()
        return instance

3. 模板渲染表单

在模板中渲染表单,单独展示属性相关输入项:

<form method="post">
    {% csrf_token %}
    
    <div class="form-group">
        {{ form.title.label_tag }}
        {{ form.title }}
        {% if form.title.errors %}
            {% for error in form.title.errors %}
                <span class="text-danger">{{ error }}</span>
            {% endfor %}
        {% endif %}
    </div>

    <div class="form-group">
        {{ form.item_type.label_tag }}
        {{ form.item_type }}
        {% if form.item_type.errors %}
            {% for error in form.item_type.errors %}
                <span class="text-danger">{{ error }}</span>
            {% endfor %}
        {% endif %}
    </div>

    <div class="mt-4">
        <h4>属性配置</h4>
        {% for field in form %}
            {% if field.name.startswith('attr_') %}
                <div class="form-group mt-2">
                    {{ field.label_tag }}
                    {{ field }}
                    {% if field.errors %}
                        {% for error in field.errors %}
                            <span class="text-danger">{{ error }}</span>
                        {% endfor %}
                    {% endif %}
                </div>
            {% endif %}
        {% endfor %}
    </div>

    <button type="submit" class="btn btn-primary mt-3">保存</button>
</form>

4. 动态切换ItemType的优化(可选)

添加JavaScript实现切换ItemType时自动刷新表单,加载对应属性:

document.addEventListener('DOMContentLoaded', function() {
    const itemTypeSelect = document.getElementById('id_item_type');
    if (itemTypeSelect) {
        itemTypeSelect.addEventListener('change', function() {
            // 提交表单刷新页面,加载对应属性字段
            document.querySelector('form').submit();
        });
    }
});

扩展说明

  • 如果需要支持更多字段类型(如数字、日期),可以在ItemAttributeSpec模型中添加field_type字段,生成表单字段时根据该值创建对应类型的输入控件。
  • 字段命名使用attr_前缀,避免与模型原有字段冲突。

内容的提问来源于stack exchange,提问作者Antony_K

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 06:08:12