Django嵌套表单实现Poll关联Question动态增删及保存问题排查
问题修复方案
你遇到的删除失效问题由4处配置错误导致,按以下步骤修改即可:
1. 调整表单集配置(forms.py)
首先开启表单集的删除权限,同时移除不需要显式提交的外键字段poll:
QuestionInlineFormSet = inlineformset_factory(Poll, Question, extra=1, can_delete=True, # 移除poll字段,外键会自动关联当前Poll实例 fields=('type', 'text'), widgets={ 'type': w.Select(attrs={'class': 'form-control'}), 'text': w.TextInput(attrs={'class': 'form-control'}), })
2. 补全表单项隐藏字段(question.html)
开启can_delete后每个子表单会自动生成DELETE标记字段,原有数据还需要id字段定位记录,需在表单最前端渲染这两个隐藏字段:
<div class="questions"> <div class="row form-row"> <!-- 新增两个隐藏字段 --> {{ form.id }} {{ form.DELETE.as_hidden }} <div class="form-group col-md-5{% if form.type.errors %} has-error{% endif %}"> <label>{{ form.type.label }}</label> {{ form.type }} <p class="help-block">{{ form.type.errors }}</p> </div> <div class="form-group col-md-5{% if form.text.errors %} has-error{% endif %}"> <label>{{ form.text.label }}</label> {{ form.text }} <p class="help-block">{{ form.text.errors }}</p> </div> <div class="col-md-2" style="margin-top: 25px"> <button type="button" class="btn btn-danger remove-form-row">✕</button> </div> </div> </div>
3. 修正前端交互逻辑(question.html的JS部分)
原有两个逻辑错误:一是前缀和后端配置的questions_formset不匹配,二是删除已有数据时直接移除DOM导致删除标记无法提交,修改后代码如下:
function updateElementIndex(el, prefix, ndx) { let id_regex = new RegExp('(' + prefix + '-\\d+)'); let replacement = prefix + '-' + ndx; if ($(el).attr("for")) $(el).attr("for", $(el).attr("for").replace(id_regex, replacement)); if (el.id) el.id = el.id.replace(id_regex, replacement); if (el.name) el.name = el.name.replace(id_regex, replacement); } function deleteForm(prefix, btn) { let total = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val()); let formRow = btn.closest('.form-row'); // 判断是否为已存入数据库的旧数据 let idInput = formRow.find('input[name$="-id"]'); if (idInput.val()) { // 旧数据:标记为删除,隐藏行不删除DOM formRow.find('input[name$="-DELETE"]').val('1'); formRow.hide(); return false; } // 新增未保存的数据:直接移除DOM if (total > 1) { formRow.remove(); let forms = $('.form-row:visible'); $('#id_' + prefix + '-TOTAL_FORMS').val(forms.length); for (let i = 0, formCount = forms.length; i < formCount; i++) { $(forms.get(i)).find(':input').each(function () { updateElementIndex(this, prefix, i); }); } } } function cloneMore(selector, prefix) { let newElement = $(selector).clone(true); let total = $('#id_' + prefix + '-TOTAL_FORMS').val(); newElement.find(':input:not([type=button]):not([type=submit]):not([type=reset])').each(function () { let name = $(this).attr('name').replace('-' + (total - 1) + '-', '-' + total + '-'); let id = 'id_' + name; $(this).attr({'name': name, 'id': id}).val('').removeAttr('checked'); }); newElement.find('label').each(function () { let forValue = $(this).attr('for'); if (forValue) { forValue = forValue.replace('-' + (total - 1) + '-', '-' + total + '-'); $(this).attr({'for': forValue}); } }); total++; $('#id_' + prefix + '-TOTAL_FORMS').val(total); $(selector).after(newElement); } $(document).on('click', '.add-form-row', function (event) { event.preventDefault(); // 前缀改为和后端一致的questions_formset cloneMore('.form-row:last', 'questions_formset'); }) $(document).on('click', '.remove-form-row', function (event) { event.preventDefault(); // 前缀改为和后端一致的questions_formset deleteForm('questions_formset', $(this)); });
4. 优化视图保存逻辑(可选,views.py)
建议先校验子表单集再保存主表单,避免主表单保存成功但子表单校验失败导致数据不一致:
# Create视图的form_valid修改为如下 def form_valid(self, form): self.object = form.save(commit=False) questions_formset = QuestionInlineFormSet(data=self.request.POST, instance=self.object, prefix='questions_formset') if not questions_formset.is_valid(): return self.form_invalid(form) result = super().form_valid(form) questions_formset.save() return result
Update视图的form_valid方法做相同修改即可。
修改完成后即可实现动态新增、删除Question,编辑页正常加载已有数据的需求。
内容的提问来源于stack exchange,提问作者zavullon
相关产品推荐
相关产品推荐

