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

Django多关联表单提交唯一校验失败未回滚数据如何修复

问题根因

你现在的问题出在视图的执行顺序不合理:

  • 原代码在device_frm校验通过后就直接保存了Device表数据,之后才校验dd_form的合法性
  • 当DeviceDetail的mgt_ip_addr重复导致dd_form校验失败时,前面已经写入数据库的Device记录不会被回滚,就出现了场景3的脏数据问题

修复方案

第一步:先校验所有表单合法性,再执行写入操作

所有表单(主表表单、关联表表单、formset)都校验通过后,再执行数据库写入操作,从逻辑上避免提前写入主表数据。

第二步:增加数据库事务保证原子性

用Django的事务原子块包裹所有写入操作,保证所有数据要么全部写入成功,要么全部回滚,避免极端情况(比如保存过程中数据库突然报错)产生脏数据。

具体修改代码

首先在views.py头部导入事务模块:

from django.db import transaction

然后修改device_add视图的POST处理逻辑:

def device_add(request):
    if request.method == "POST":
        device_frm = DeviceForm(request.POST)
        dd_form = DeviceDetailForm(request.POST)
        
        di_formset = modelformset_factory(DeviceInterface, fields=('moduletype', 'firstportid', 'lastportid'), extra=1,max_num=3)
        di_form=di_formset(request.POST)
        # 先校验所有表单全部合法,再执行保存逻辑
        if device_frm.is_valid() and dd_form.is_valid() and di_form.is_valid():
            # 用事务包裹所有写入操作,保证原子性
            with transaction.atomic():
                # 保存Device
                new_device = device_frm.save()
                # 保存DeviceDetail
                deviceD = dd_form.save(commit=False)
                deviceD.DD2DKEY = new_device
                deviceD.hostname = new_device.hostname
                deviceD.save()
                # 保存DeviceInterface表单集
                deviceI=di_form.save(commit=False) 
                for instances in deviceI:          
                    instances.I2DKEY=new_device
                    instances.save()
            # 所有保存成功后跳转到成功页
            return render(request, 'interface/device_added.html',{'devices':Device.objects.all()})
        # 任意表单校验不通过,直接返回表单页带错误信息
        else:
            return render(request,'interface/device_add.html',{'form':device_frm, 'dd_form': dd_form, 'di_form':di_form})
    else:
        device_frm = DeviceForm()
        dd_form = DeviceDetailForm()
        di_formset = modelformset_factory(DeviceInterface, fields=('moduletype', 'firstportid', 'lastportid'),extra=1, max_num=3)
        di_form=di_formset(queryset = DeviceInterface.objects.none())
        return render(request,'interface/device_add.html',{'form':device_frm, 'dd_form': dd_form, 'di_form':di_form})

验证效果

修改后场景3的情况:
当mgt_ip_addr重复时,dd_form.is_valid()会返回False,直接进入错误返回分支,不会执行任何数据库写入操作,Device表不会写入脏数据,符合需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 07:18:03