Django ModelFormset_Factory表单渲染异常问题求助
问题:Model Formset添加POST处理逻辑后布局混乱
已查阅相关问答、Django官方文档并跟随教程操作,但仍无法正确渲染Model Formset。GET请求场景下代码运行正常,添加POST请求处理逻辑后表单出现布局混乱的异常情况。
相关代码
forms.py
class BulkUpdateForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(BulkUpdateForm, self).__init__(*args, **kwargs) self.helper = FormHelper(self) self.helper.form_method = "post" self.helper.layout = Layout( Row( Column("slug"), ), Row( Column("registered"), Column("start_date"), Column("end_date"), Column("achieved"), Column("pfr_start"), Column("pfr_end"), ), ) self.fields["slug"].label = "" class Meta: model = LearnerInstance fields = ["id","slug","registered","start_date","end_date","achieved","pfr_start","pfr_end", ] widgets = { "slug": forms.TextInput( attrs={ "readonly": True, }, ), }
模板文件
<div class="container d-flex align-items-center flex-column pt-1"> <div class="card text-dark bg-light mb-3 p-3" style="max-width: 95rem;"> <br> <form action="" method="post"> {% csrf_token %} {{ formset.management_form }} {% for form in formset %} {% crispy form %} <hr> {% endfor %} </div> <input type="submit" name='registered', class="btn btn-primary" value="Update"> </form> </div>
初始views.py(GET请求正常)
def bulk_update(request): context = {} selected_rows = request.POST.getlist("select") # 从上一页带复选框的表格提交的待更新数据ID BulkFormset = modelformset_factory( LearnerInstance, form=BulkUpdateForm, edit_only=True, extra=0) queryset = LearnerInstance.objects.filter(id__in=selected_rows) formset = BulkFormset(queryset=queryset) context["formset"] = formset return render(request, "learner/bulk_update.html", context)
修改后的views.py(POST请求后布局混乱)
def bulk_update(request): context = {} selected_rows = request.POST.getlist("select") BulkFormset = modelformset_factory( LearnerInstance, form=BulkUpdateForm, edit_only=True, extra=0 ) queryset = LearnerInstance.objects.filter(id__in=selected_rows) if request.method == "POST": formset = BulkFormset( request.POST, queryset=queryset, ) if formset.is_valid(): formset.save() else: formset = BulkFormset(queryset=queryset) context["formset"] = formset return render(request, "learner/bulk_update.html", context)
问题原因
- 选中ID获取逻辑错误:POST请求时,
selected_rows = request.POST.getlist("select")会尝试从当前表单提交的请求中获取"select"参数,但当前POST是更新表单的请求,并非从上一页表格提交的选择请求,导致queryset为空,formset渲染异常。 - 表单嵌套破坏结构:每个
BulkUpdateForm的FormHelper设置了form_method = "post",而模板中已经存在外层<form>标签,造成表单嵌套,破坏HTML结构,导致布局混乱。 - 模板DOM结构错误:模板中存在多余的
</div>标签,导致页面DOM结构不闭合,渲染时布局错乱。
解决方案
1. 用Session存储选中的ID
从上一页提交的选中ID无法在当前视图的POST请求中直接获取,改用Session临时存储:
- 上一页处理复选框提交的视图:
def select_learners(request): if request.method == "POST": selected_ids = request.POST.getlist("select") request.session['selected_learner_ids'] = selected_ids return redirect('bulk_update')
- 修改
bulk_update视图获取ID的逻辑:
def bulk_update(request): context = {} # 从Session读取选中的ID selected_rows = request.session.get('selected_learner_ids', []) # POST处理完成后清除Session中的数据,避免重复使用 if request.method == "POST": request.session.pop('selected_learner_ids', None) BulkFormset = modelformset_factory( LearnerInstance, form=BulkUpdateForm, edit_only=True, extra=0 ) queryset = LearnerInstance.objects.filter(id__in=selected_rows) if request.method == "POST": formset = BulkFormset( request.POST, queryset=queryset, ) if formset.is_valid(): formset.save() # 重定向避免POST重复提交 return redirect('update_success') else: formset = BulkFormset(queryset=queryset) context["formset"] = formset return render(request, "learner/bulk_update.html", context)
2. 移除FormHelper中的表单标签设置
模板已有外层表单,不需要每个子表单再生成表单标签:
def __init__(self, *args, **kwargs): super(BulkUpdateForm, self).__init__(*args, **kwargs) self.helper = FormHelper(self) # 关闭子表单的自动表单标签生成 self.helper.form_tag = False self.helper.layout = Layout( Row( Column("slug"), ), Row( Column("registered"), Column("start_date"), Column("end_date"), Column("achieved"), Column("pfr_start"), Column("pfr_end"), ), ) self.fields["slug"].label = ""
3. 修正模板的DOM结构
移除多余的</div>标签,保证HTML结构闭合:
<div class="container d-flex align-items-center flex-column pt-1"> <div class="card text-dark bg-light mb-3 p-3" style="max-width: 95rem;"> <br> <form action="" method="post"> {% csrf_token %} {{ formset.management_form }} {% for form in formset %} {% crispy form %} <hr> {% endfor %} <input type="submit" name='registered' class="btn btn-primary" value="Update"> </form> </div> </div>
内容的提问来源于stack exchange,提问作者Lisa Higgins
相关产品推荐
相关产品推荐

