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

Django动态新增表单数据仅存最后一条的问题解决

Django动态表单批量保存问题解决方案

问题核心

你遇到的问题本质是动态新增的表单字段名称重复,导致后端接收数据时后续条目覆盖前面的;或是后端未遍历所有提交条目,仅处理了最后一条。以下是针对性的修正方案:


1. 模型层(models.py)调整示例

确保药品剂量模型与主业务模型(如就诊记录)关联,示例:

from django.db import models

class VisitRecord(models.Model):
    patient_name = models.CharField(max_length=100)
    # 其他主表字段(如就诊日期等)

class Medication(models.Model):
    visit_record = models.ForeignKey(VisitRecord, on_delete=models.CASCADE, related_name='medications')
    drug_name = models.CharField(max_length=100)
    dosage = models.CharField(max_length=50)  # 存储如"每日2次,每次1片"的剂量信息

2. 表单层(forms.py)改用表单集

Django自带的formset专门处理批量表单提交,替换单个表单:

from django import forms
from .models import Medication

class MedicationForm(forms.ModelForm):
    class Meta:
        model = Medication
        fields = ['drug_name', 'dosage']
        widgets = {
            'drug_name': forms.TextInput(attrs={'class': 'form-control'}),
            'dosage': forms.TextInput(attrs={'class': 'form-control'}),
        }

# 创建表单集,初始显示1个表单
MedicationFormSet = forms.formset_factory(MedicationForm, extra=1)

3. 视图层(views.py)处理表单集提交

遍历表单集内所有有效表单,关联主记录后保存:

from django.shortcuts import render, redirect
from .models import VisitRecord
from .forms import MedicationFormSet

def add_visit(request):
    if request.method == 'POST':
        # 处理主表单数据(示例为患者姓名)
        patient_name = request.POST.get('patient_name')
        visit_record = VisitRecord.objects.create(patient_name=patient_name)
        
        # 处理药品表单集
        formset = MedicationFormSet(request.POST)
        if formset.is_valid():
            for form in formset:
                if form.cleaned_data:  # 跳过空表单
                    medication = form.save(commit=False)
                    medication.visit_record = visit_record
                    medication.save()
            return redirect('visit_list')  # 跳转到记录列表页
    else:
        # 初始化表单集
        formset = MedicationFormSet()
    
    return render(request, 'registration.html', {'formset': formset})

4. 前端(registration.html)动态新增表单条目

利用Django表单集的management_form追踪表单数量,避免字段名重复:

<form method="post">
    {% csrf_token %}
    <!-- 主表单字段示例 -->
    <div class="mb-3">
        <label for="patient_name" class="form-label">患者姓名</label>
        <input type="text" class="form-control" id="patient_name" name="patient_name" required>
    </div>

    <!-- 药品表单集容器 -->
    <div id="medication-forms">
        {% for form in formset %}
            <div class="medication-form row mb-3">
                {{ form.id }} <!-- 必须保留的隐藏字段,用于表单集识别 -->
                <div class="col-md-5">
                    {{ form.drug_name.label_tag }}
                    {{ form.drug_name }}
                </div>
                <div class="col-md-5">
                    {{ form.dosage.label_tag }}
                    {{ form.dosage }}
                </div>
                <div class="col-md-2 mt-4">
                    {% if forloop.first %}
                        <button type="button" class="btn btn-primary" id="add-form">新增药品</button>
                    {% else %}
                        <button type="button" class="btn btn-danger remove-form">删除</button>
                    {% endif %}
                </div>
            </div>
        {% endfor %}
    </div>

    <!-- 表单集管理字段,必须包含 -->
    {{ formset.management_form }}

    <button type="submit" class="btn btn-success">保存记录</button>
</form>

<script>
document.addEventListener('DOMContentLoaded', function() {
    const addButton = document.getElementById('add-form');
    const formContainer = document.getElementById('medication-forms');
    const totalForms = document.getElementById('id_form-TOTAL_FORMS');
    
    let formCount = parseInt(totalForms.value);
    
    // 新增表单逻辑
    addButton.addEventListener('click', function() {
        const newForm = document.querySelector('.medication-form').cloneNode(true);
        // 更新新增表单的字段name和id索引
        newForm.querySelectorAll('input').forEach(input => {
            input.name = input.name.replace('form-0', `form-${formCount}`);
            input.id = input.id.replace('form-0', `form-${formCount}`);
            input.value = ''; // 清空输入值
        });
        // 替换按钮:新增表单显示删除按钮
        newForm.querySelector('button').remove();
        const removeBtn = document.createElement('button');
        removeBtn.type = 'button';
        removeBtn.className = 'btn btn-danger remove-form';
        removeBtn.textContent = '删除';
        newForm.querySelector('.col-md-2').appendChild(removeBtn);
        
        formContainer.appendChild(newForm);
        formCount++;
        totalForms.value = formCount;
        
        bindRemoveButtons();
    });
    
    // 删除表单逻辑
    function bindRemoveButtons() {
        document.querySelectorAll('.remove-form').forEach(btn => {
            btn.addEventListener('click', function() {
                this.closest('.medication-form').remove();
                formCount--;
                totalForms.value = formCount;
                // 重新索引剩余表单的字段名和id
                document.querySelectorAll('.medication-form').forEach((form, index) => {
                    form.querySelectorAll('input').forEach(input => {
                        input.name = input.name.replace(/form-\d+/, `form-${index}`);
                        input.id = input.id.replace(/form-\d+/, `form-${index}`);
                    });
                });
            });
        });
    }
    
    // 初始化删除按钮事件绑定
    bindRemoveButtons();
});
</script>

关键注意事项

  • 必须保留表单集的management_form,它负责追踪表单总数、初始数等,Django依赖此识别所有提交的表单。
  • 动态新增的表单字段名称必须遵循form-<索引>-<字段名>格式,避免字段名重复导致数据覆盖。
  • 后端处理时需遍历所有表单,并跳过空表单(防止新增后未输入就提交的无效数据)。
  • 每个表单必须包含隐藏的id字段({{ form.id }}),表单集需要此字段识别表单。

内容的提问来源于stack exchange,提问作者Furkan Yıldız

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 21:40:41