如何用HTMX动态添加Django Formset实例并正确提交POST请求?
解决Django嵌套动态Formset结合HTMX时仅最后一个子表单数据被提交的问题
问题核心原因
每次通过HTMX调用addStructure视图时,返回的都是全新的StructureFormset实例,所有表单字段的前缀都是form-0。POST提交时,重复的字段名会导致Django仅保留最后一个实例的数据,而忽略之前的所有表单。正常的Django Formset是通过递增的索引前缀(如form-0、form-1、form-2)来区分不同表单实例的。
解决方案步骤
1. 前端传递当前表单计数给HTMX请求
修改plan_forms.html中的"Add structure"链接,通过hx-vals传递当前已有的子表单数量,让后端生成对应索引的空表单:
<a href="" hx-get="{% url 'structure-form' %}" hx-vals='{"form_count": document.querySelectorAll("#structureforms tr").length}' hx-swap="beforeend" hx-target="#structureforms"> Add structure </a>
2. 后端根据计数生成带正确索引的空表单
修改views.py中的addStructure视图,接收前端传递的form_count参数,生成对应索引前缀的单个空表单(而非整个formset):
def addStructure(request): form_count = int(request.GET.get('form_count', 0)) # 初始化formset并获取空表单模板 sformset = StructureFormset(queryset=Structure.objects.none(), prefix='form') empty_form = sformset.empty_form # 修改空表单的前缀索引,确保唯一性 empty_form.prefix = f'form-{form_count}' context = {"form": empty_form} return render(request, 'app/formsets/structure_form.html', context)
3. 修改子表单模板渲染单个表单
更新formsets/structure_form.html,渲染单个表单而非整个formset:
<tr> <td class="col-12 px-1"> {{ form.as_table }} <!-- 可根据需求替换为.as_p或.as_ul --> </td> </tr>
4. 修正父视图的Formset初始化
修改createPlan视图,指定formset的prefix参数,确保Django能正确识别所有动态添加的表单字段:
def createPlan(request): form = PlanForm(request.POST or None) # 指定prefix与前端保持一致,确保能解析所有动态表单 sformset = StructureFormset(request.POST or None, queryset=Structure.objects.none(), prefix='form') context = { 'form': form, 'sformset': sformset, } if request.method == 'POST': if form.is_valid() and sformset.is_valid(): plan = form.save() # 批量关联子表单到父模型 for structure in sformset.save(commit=False): structure.plan = plan structure.save() # 提交后可重定向到列表页或当前页 return redirect('create_plan') return render(request, 'app/plan_forms.html', context)
5. 初始化渲染默认子表单
在plan_forms.html的structureforms tbody中,添加初始表单的渲染逻辑:
<tbody style="user-select: none;" id="structureforms" hx-sync="closest form:queue"> {% for form in sformset %} <tr> <td class="col-12 px-1">{{ form.as_table }}</td> </tr> {% endfor %} </tbody>
效果说明
修改后,每次点击"Add structure"都会生成带唯一索引前缀的子表单(form-0、form-1...),POST提交时Django能正确识别所有子表单数据,不会出现覆盖问题,同时保持了HTMX无JS(除了内置API)的特性。
内容的提问来源于stack exchange,提问作者Daniel
相关产品推荐
相关产品推荐

