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
相关产品推荐
相关产品推荐

