bigywater.tech
Django · 67

FormSet 与 InlineFormSet

FormSet 是「一次提交多个同类表单」的机制。它靠什么知道有几个表单?怎么限制数量?inline 是怎么关联父对象的?为什么渲染时会发 N 次查询?这篇逐项验证。

ManagementForm
客户端提交的 TOTAL_FORMS 决定处理几个表单;缺失即校验失败
11 次
10 个表单各带一个外键下拉框时渲染 FormSet 的查询数(1 + 10)
validate_min / max
要显式打开,才会校验提交的表单数量

ManagementForm

实测 · 本机执行输出
未绑定 FormSet 的 management_form 初始值: {'TOTAL_FORMS': 2, 'INITIAL_FORMS': 0, 'MIN_NUM_FORMS': 0, 'MAX_NUM_FORMS': 1000}
渲染出的隐藏字段: ['<input type="hidden" name="form-TOTAL_FORMS" value="2" id="i', '<input type="hidden" name="form-INITIAL_FORMS" value="0" id=']
有 ManagementForm:is_valid() = True  cleaned = [{'x': 1}, {'x': 2}]
缺 ManagementForm:is_valid() = False  non_form_errors = ['ManagementForm data is missing or has been tampered with. Missing fields: form-TOTAL_FORMS, form-INITIAL_FORMS. You may need to file a bug report if the issue persists.']  code = missing_management_form

模板里忘了渲染 {{ formset.management_form }},提交时就是这个错误——看起来像「表单坏了」,其实是缺隐藏字段。

数量与删除

实测 · 本机执行输出
未绑定:表单数 = 3 (min_num 2 + extra 1)
只提交 1 个:is_valid() = False ['Please submit at least 2 forms.']
提交 5 个(max_num=3):is_valid() = False ['Please submit at most 3 forms.']
第 2 个勾选 DELETE:cleaned_data = [{'x': 1, 'DELETE': False}, {'x': 2, 'DELETE': True}, {'x': 3, 'DELETE': False}]  deleted_forms = 1

min_num/max_num 默认只影响「显示几个」;要拒绝不符合数量的提交,必须 validate_min/validate_max。实测未绑定 FormSet 的 MAX_NUM_FORMS 初始值是 1000(没有测提交超过它时的行为)。

InlineFormSet

实测 · 本机执行输出
is_valid = True  表单字段: ['title', 'id', 'DELETE', 'author']
save() 发出的查询数: 2  保存的对象: [('b1', True), ('b2', True)]
再打开编辑页:表单数 = 4  (2 个已有 + extra 2)  INITIAL_FORMS = 2

父对象通过 instance= 传入,外键字段由 Django 自动处理。

N+1

实测 · 本机执行输出
10 本书、每本一个 editor 下拉框,渲染整个 FormSet 的查询数: 11
   查询种类: {'SELECT "lab_book"."id", "lab_book"."author_id", "lab_book"."title", "l': 1, 'SELECT "lab_editor"."id", "lab_editor"."name" FROM "lab_editor"': 10}

每个表单实例创建自己的 ModelChoiceField 选项,渲染时各自查询。表单数多、选项字段多时明显。

分步演示:FormSet

FormSet
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。SQLite
    • 没有验证:modelformset_factory 的保存行为、formset 里的 unique 校验(跨表单重复)、JS 动态增删表单时 TOTAL_FORMS 的维护、N+1 的具体解决办法
    京ICP备2026067502号-1京公网安备11010502063487号