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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 17:39:02