You need to enable JavaScript to run this app. Django 同页双表单共用视图提交时误存空表单问题解决
优惠活动
大模型
产品
解决方案
定价
更多

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>

问题原因

核心问题有两点:

  1. 视图逻辑缺陷:视图中两个表单的POST判断逻辑完全独立,只要触发任意POST请求,两个表单的验证、保存逻辑都会被执行,没有区分当前提交的是哪一个表单。空表单因存在required=False字段,可直接通过is_valid()校验触发保存。
  2. 表单无区分标识:前端两个表单没有设置任何可标识提交来源的字段,后端无法从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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.04 09:36:07