Django 同页双表单共用视图提交时误存空表单问题解决
问题描述
Django项目开发中需要在单个页面承载2个不同的表单:
- 两个表单对应不同的表单模型,放置在同一页面,共用同一个视图函数处理
- 单独测试两个表单均可正常运行,但提交任意一个表单时,另一个表单会被以空表单形式意外保存,不希望出现该情况
- 求问题原因与解决方案
现有代码
views.py
def ocr(request, id): ... pdf = get_object_or_404(Pdf, id=id) approval = ApprovalProcess(user_id=request.user, highest_rank=1) # FORM 1 if request.method == 'POST': form = PdfRiskForm(request.POST, request.FILES, instance=pdf) if form.is_valid(): form.save() if pdf.risk_rating == "Low Risk": n = len([x for x in doa_low_list if x < pdf.credit_limit]) approval.highest_rank = n elif pdf.risk_rating == "Medium Risk": n = len([x for x in doa_medium_list if x < pdf.credit_limit]) .... approval.save() else: form = PdfRiskForm() # FORM 2 if request.method == 'POST': form_2 = CommentForm(request.POST or None) if form_2.is_valid(): form_2.instance.comment_other = form.cleaned_data.get("comment_other") form_2.instance.username = request.user form_2.instance.comp_name = userP[0].company form_2.instance.doc_id = pdf if form_2.comment_others is not None: form_2.save() else: form_2 = CommentForm()
models.py
注:原代码将表单类定义写在models.py中,不符合Django项目规范
class PdfRiskForm(forms.ModelForm): risk_rating = forms.ChoiceField(label='Risk Rating', choices=Pdf.RISK_RATING) credit_limit = forms.IntegerField() comments = RichTextField letter_portion = forms.IntegerField(min_value=0, max_value=100, required=False, label="Guarantee Portion (%)") class Meta: model = Pdf fields = ['risk_rating', 'credit_limit', 'letter_portion', 'comments'] class CommentFromOthers(models.Model): comp_name = models.ForeignKey(CompanyProfile, on_delete=models.CASCADE, null=True) doc_id = models.ForeignKey(Pdf, on_delete=models.DO_NOTHING, null=True) comment_others = RichTextField(blank=True) created_date = models.DateTimeField(default=datetime.now()) username = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
template.html
<div class="card-body"> <form method="POST" enctype="multipart/form-data"> {% csrf_token %} {{ form.media }} {{ form|crispy }} <input type="submit" class="btn btn-primary btn-lg" value="Approve"> </form> </div> ... <div id="demo" class="collapse"> <form method="POST" enctype="multipart/form-data"> {% csrf_token %} {{ form_2.media }} {{ form_2|crispy }} <input type="submit" class="btn btn-primary btn-lg" value="Send"> </form> </div>
问题原因
核心问题有两点:
- 视图逻辑缺陷:视图中两个表单的POST判断逻辑完全独立,只要触发任意POST请求,两个表单的验证、保存逻辑都会被执行,没有区分当前提交的是哪一个表单。空表单因存在
required=False字段,可直接通过is_valid()校验触发保存。 - 表单无区分标识:前端两个表单没有设置任何可标识提交来源的字段,后端无法从POST请求中判断本次请求对应哪个表单。
附带问题:
- 表单类错误定义在models.py中,不符合Django项目规范,易造成维护混乱
CommentForm的字段赋值逻辑错误,form_2.comment_others为表单类属性而非cleaned后的字段值,判断逻辑无效created_date默认值直接传入datetime.now()调用结果,会在模型加载时生成固定时间,无法在新建实例时自动获取当前时间
解决方案
步骤1:前端表单添加提交标识
给每个表单的提交按钮添加独立的name属性,用于后端识别提交来源:
<!-- 表单1提交按钮修改 --> <input type="submit" name="submit_form1" class="btn btn-primary btn-lg" value="Approve"> <!-- 表单2提交按钮修改 --> <input type="submit" name="submit_form2" class="btn btn-primary btn-lg" value="Send">
步骤2:修改视图逻辑,按提交标识分流处理
替换原有并列的POST判断逻辑,仅对对应提交的表单执行校验、保存:
def ocr(request, id): pdf = get_object_or_404(Pdf, id=id) approval = ApprovalProcess(user_id=request.user, highest_rank=1) # 先初始化两个表单的默认实例(GET请求或未提交对应表单时使用) form = PdfRiskForm(instance=pdf) form_2 = CommentForm() if request.method == 'POST': # 仅处理表单1的提交 if 'submit_form1' in request.POST: form = PdfRiskForm(request.POST, request.FILES, instance=pdf) if form.is_valid(): form.save() # 原有审批等级计算逻辑保留 if pdf.risk_rating == "Low Risk": n = len([x for x in doa_low_list if x < pdf.credit_limit]) approval.highest_rank = n elif pdf.risk_rating == "Medium Risk": n = len([x for x in doa_medium_list if x < pdf.credit_limit]) approval.save() # 仅处理表单2的提交 elif 'submit_form2' in request.POST: form_2 = CommentForm(request.POST) if form_2.is_valid(): # 修正原字段赋值逻辑 form_2.instance.username = request.user form_2.instance.comp_name = userP[0].company form_2.instance.doc_id = pdf # 如需禁止空评论提交,可在此加判断:if form_2.cleaned_data.get('comment_others') form_2.save() # 统一返回渲染上下文 return render(request, 'template.html', { 'form': form, 'form_2': form_2, 'pdf': pdf })
步骤3:修复附带问题
- 将
PdfRiskForm类从models.py移至forms.py,符合Django项目结构规范 - 修正
created_date默认值设置,改为传入可调用对象:# models.py中修改 created_date = models.DateTimeField(default=datetime.now) - 如需强制评论必填,可在
CommentForm中将comment_others字段设为required=True
内容的提问来源于stack exchange,提问作者user14887093
相关产品推荐
相关产品推荐

