使用UpdateView更新父子模型时新建对象而非更新的问题
解决Django UpdateView中ModelFormSet新建子对象而非更新的问题
嘿,我看你在Django里用UpdateView更新带多个子模型的父对象时遇到了新建而非更新的问题,这个坑我之前也踩过!核心问题出在你没正确利用ModelFormSet的内置更新逻辑,尤其是没处理删除操作和表单的关联识别,手动循环保存的方式也绕开了FormSet的自动匹配机制。
先确认你的FormSet配置(forms.py)
首先要确保你的子模型表单集是ModelFormSet,并且开启了删除功能,这样才能识别已有对象并支持删除:
from django.forms import modelformset_factory from .models import BalaMateriesPrimeres # 生成子模型的表单集 BalaMateriesPrimeresFormSet = modelformset_factory( BalaMateriesPrimeres, fields=['你的子模型字段列表'], # 替换成实际字段 extra=1, # 允许添加新的子对象的数量 can_delete=True # 开启删除功能,会自动生成删除复选框 )
修正UpdateView的代码(views.py)
下面是修正后的EntradaUpdateView,我会在注释里标清楚关键修正点:
class EntradaUpdateView(LoginRequiredMixin, UpdateView): model = Entrada fields = ['父模型字段列表'] # 替换成实际父模型字段 template_name_suffix = '_update' def get_context_data(self, **kwargs): # 把表单集加入上下文,这样模板能渲染已有子对象的表单 context = super().get_context_data(**kwargs) if self.request.POST: # POST请求时,用提交的数据和已有子对象的queryset初始化表单集 context['formset'] = BalaMateriesPrimeresFormSet( self.request.POST, queryset=BalaMateriesPrimeres.objects.filter(num_entrada=self.object) ) else: # GET请求时,用已有子对象的queryset初始化表单集,显示现有数据 context['formset'] = BalaMateriesPrimeresFormSet( queryset=BalaMateriesPrimeres.objects.filter(num_entrada=self.object) ) return context def form_valid(self, form): # 获取上下文里的表单集 context = self.get_context_data() formset = context['formset'] if formset.is_valid(): # 先保存父对象(UpdateView的form已经验证通过,直接保存即可) self.object = form.save() # 保存表单集,commit=False拿到所有实例(包括更新和新建的) instances = formset.save(commit=False) for instance in instances: # 确保子对象关联到当前父对象 instance.num_entrada = self.object instance.save() # 处理被标记为删除的子对象(这步很重要!之前你没处理,导致旧对象残留) for instance in formset.deleted_objects: instance.delete() # 如果子模型有多对多字段,需要调用save_m2m()保存关联 formset.save_m2m() # 调用父类的form_valid完成重定向逻辑 return super().form_valid(form)
为什么你的之前代码会出问题?
我帮你梳理下之前代码的几个关键问题:
- 重复初始化表单:你在
form_valid里重新调用get_form_class()和get_form(),完全没必要,UpdateView已经把验证过的form传给了form_valid方法 - 没处理删除操作:你没有处理
formset.deleted_objects,导致旧的子对象没被删除,新的又被创建,看起来像是重复新建 - 手动绕开FormSet的自动匹配:ModelFormSet会通过表单里的隐藏
id字段自动匹配已有对象进行更新,但你手动循环保存的方式没有利用这个机制,反而可能创建了新对象 - 重复保存父对象:你先调用
form.save(),最后又调用super().form_valid(form),虽然不会报错,但没必要,统一用self.object = form.save()即可
模板里的关键注意点
确保你的模板里渲染表单集时,每个子表单都包含了自动生成的隐藏字段,这样FormSet才能识别已有对象:
<!-- 父模型表单 --> {{ form.as_p }} <!-- 子模型表单集 --> {{ formset.management_form }} <!-- 必须加这个,FormSet用来管理表单数量和id --> {% for form in formset %} {{ form.as_p }} <!-- 如果开启了can_delete,会自动显示删除复选框 --> {% endfor %}
内容的提问来源于stack exchange,提问作者DavidMM
相关产品推荐
相关产品推荐

