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

Django一次提交保存父/子/孙三级关联表单的实现方案咨询

解决方案

针对你这种三级关联模型一次性提交的场景,Django原生inlineformset_factory默认仅支持两级关联,确实无法直接匹配需求,推荐用「二级formset + 自定义处理第三层IMEI表单」的方案实现,具体逻辑如下:

后端实现

表单定义

先定义基础表单和二级formset:

from django import forms
from django.forms import inlineformset_factory
from django.shortcuts import render, redirect
from .models import Collection, MobileCollection, Imei, ModelCategory
from django.contrib.auth.models import User

# 第一层:发票基本信息表单
class CollectionForm(forms.ModelForm):
    class Meta:
        model = Collection
        fields = ["company", "invoice_no", "collection_date"]

# 第二层:单型号手机入库表单,新增标识字段关联对应IMEI组
class MobileCollectionForm(forms.ModelForm):
    form_id = forms.CharField(widget=forms.HiddenInput, required=False)
    class Meta:
        model = MobileCollection
        fields = ["mobile", "qnt", "price"]

# 生成二级formset,支持动态新增/删除手机型号记录
# 注意:如果你的Collection关联MobileCollection的related_name是collections,需要加related_name参数
MobileCollectionFormSet = inlineformset_factory(
    Collection,
    MobileCollection,
    form=MobileCollectionForm,
    extra=1,  # 默认显示1条
    can_delete=True,
    # related_name="collections" # 取消注释匹配你模型里的related_name配置
)

视图逻辑

视图层手动处理第三层IMEI的校验和保存:

def add_collection(request):
    if request.method == "POST":
        collection_form = CollectionForm(request.POST)
        mobile_formset = MobileCollectionFormSet(request.POST, instance=Collection())
        
        if collection_form.is_valid() and mobile_formset.is_valid():
            # 先保存主发票记录
            collection = collection_form.save(commit=False)
            collection.admin = request.user
            collection.save()
            
            # 遍历每条手机型号记录
            for mobile_form in mobile_formset:
                if mobile_form.cleaned_data.get("DELETE"):
                    continue
                mobile_record = mobile_form.save(commit=False)
                mobile_record.collection = collection
                qnt = mobile_form.cleaned_data["qnt"]
                # 表单标识和前端生成的IMEI字段名对应
                form_id = mobile_form.cleaned_data["form_id"] or f"form_{mobile_form.prefix.split('-')[1]}"
                mobile_record.save()

                # 校验并保存对应数量的IMEI
                imei_errors = []
                for idx in range(qnt):
                    imei_val = request.POST.get(f"imei_{form_id}_{idx}", "").strip()
                    # 基础格式校验
                    if not imei_val or len(imei_val) != 15:
                        imei_errors.append(f"第{idx+1}个IMEI格式错误,要求15位字符")
                        continue
                    # 唯一性校验
                    if Imei.objects.filter(imei=imei_val).exists():
                        imei_errors.append(f"IMEI {imei_val} 已存在,不可重复录入")
                        continue
                    Imei.objects.create(mobile=mobile_record, imei=imei_val)
                
                # 有错误直接回滚所有操作,返回错误提示
                if imei_errors:
                    collection.delete()
                    mobile_form.add_error(None, "\n".join(imei_errors))
                    return render(request, "collection_form.html", {
                        "collection_form": collection_form,
                        "mobile_formset": mobile_formset
                    })
            
            # 全部保存成功跳转列表页
            return redirect("collection_list")
    else:
        collection_form = CollectionForm()
        mobile_formset = MobileCollectionFormSet()
    
    return render(request, "collection_form.html", {
        "collection_form": collection_form,
        "mobile_formset": mobile_formset
    })

前端实现

前端核心逻辑是动态新增二级表单、根据qnt值生成对应数量的IMEI输入框,示例代码如下:

<!-- 表单模板 collection_form.html -->
<form method="post">
    {% csrf_token %}
    <!-- 第一层发票表单 -->
    <h3>发票信息</h3>
    {{ collection_form.as_p }}

    <h3>手机入库明细</h3>
    <div id="mobile_formset_container">
        {% for form in mobile_formset %}
            <div class="mobile-form-item">
                {{ form.as_p }}
                <!-- IMEI输入框容器 -->
                <div class="imei-container" id="imei_container_form_{{ forloop.counter0 }}"></div>
            </div>
        {% endfor %}
    </div>

    <button type="button" id="add_mobile_btn">新增手机型号</button>
    <button type="submit">提交保存</button>

    <!-- 隐藏的空表单模板,用于动态新增二级表单 -->
    <div id="empty_form_template" style="display: none;">
        <div class="mobile-form-item">
            {{ mobile_formset.empty_form.as_p }}
            <div class="imei-container" id="imei_container_form_{{ mobile_formset.empty_form.prefix }}"></div>
        </div>
    </div>
</form>

<script>
// 初始化二级表单索引
let formIndex = {{ mobile_formset.total_form_count }};

// 监听qnt字段变化,动态生成IMEI输入框
document.addEventListener("change", function(e) {
    if (e.target.name.endsWith("-qnt")) {
        const prefix = e.target.name.split("-")[1];
        const formId = `form_${prefix}`;
        const qnt = parseInt(e.target.value) || 0;
        const container = document.getElementById(`imei_container_${formId}`);
        container.innerHTML = "";

        for (let i = 0; i < qnt; i++) {
            const input = document.createElement("input");
            input.type = "text";
            input.name = `imei_${formId}_${i}`;
            input.maxLength = 15;
            input.placeholder = `请输入第${i+1}个IMEI`;
            input.style.margin = "2px 0";
            container.appendChild(input);
            container.appendChild(document.createElement("br"));
        }
    }
});

// 新增手机型号按钮逻辑
document.getElementById("add_mobile_btn").addEventListener("click", function() {
    const container = document.getElementById("mobile_formset_container");
    const emptyForm = document.getElementById("empty_form_template").innerHTML.replace(/__prefix__/g, formIndex);
    container.insertAdjacentHTML("beforeend", emptyForm);
    // 更新formset总表单数
    document.getElementById("id_mobilecollection_set-TOTAL_FORMS").value = formIndex + 1;
    formIndex++;
});
</script>

方案优势

  1. 完全匹配你的业务需求,既支持动态新增手机型号记录,也能根据填写的数量实时生成对应IMEI输入框
  2. 校验逻辑完全可控,可灵活扩展IMEI的规则校验
  3. 无需依赖第三方扩展包,完全基于Django原生能力实现

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 03:54:03