class BaseFormSet
表单集是用于在同一页面处理多个表单的一层抽象,可以把它理解为数据网格。假设已有如下表单:
>>> from django import forms
>>> class ArticleForm(forms.Form):
... title = forms.CharField()
... pub_date = forms.DateField()
...
要让用户一次创建多篇文章,可以从 ArticleForm 创建表单集:
>>> from django.forms import formset_factory
>>> ArticleFormSet = formset_factory(ArticleForm)
现在得到名为 ArticleFormSet 的表单集类。实例化后可迭代其中的表单,像普通表单一样显示:
>>> formset = ArticleFormSet()
>>> for form in formset:
... print(form)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></div>
这里只显示一个空表单。空表单数量由 extra 控制,formset_factory() 默认额外提供一个;以下创建两个空表单:
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
表单集支持迭代和索引,按创建顺序访问表单。必要时可以覆盖默认的 __iter__() 与 __getitem__(),改变顺序;实现方法见 Python 序列协议文档。
使用初始数据
初始数据是表单集实用性的关键。extra 指在初始数据生成的表单之外,再显示多少空表单:
>>> import datetime
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
>>> formset = ArticleFormSet(
... initial=[
... {
... "title": "Django is now open source",
... "pub_date": datetime.date.today(),
... }
... ]
... )
>>> for form in formset:
... print(form)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2023-02-11" id="id_form-0-pub_date"></div>
<div><label for="id_form-1-title">Title:</label><input type="text" name="form-1-title" id="id_form-1-title"></div>
<div><label for="id_form-1-pub_date">Pub date:</label><input type="text" name="form-1-pub_date" id="id_form-1-pub_date"></div>
<div><label for="id_form-2-title">Title:</label><input type="text" name="form-2-title" id="id_form-2-title"></div>
<div><label for="id_form-2-pub_date">Pub date:</label><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></div>
此处共三个表单:一个来自初始数据,另有两个额外空表单。初始数据以字典列表传入。
若显示表单集时使用了 initial,处理提交时也应传入相同 initial,让表单集识别用户修改了哪些表单。例如 ArticleFormSet(request.POST, initial=[...])。
限制显示的表单数量
formset_factory() 的 max_num 参数限制显示数量:
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1)
>>> formset = ArticleFormSet()
>>> for form in formset:
... print(form)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></div>
当 max_num 大于初始数据条目数时,在不超过总上限的前提下,最多添加 extra 个空表单。例如 extra=2、max_num=2 且有一个初始条目时,显示一个初始表单和一个空表单。
若初始条目数已超过 max_num,仍显示全部初始表单,不显示额外表单。例如 extra=3、max_num=1、两个初始条目,仍显示两个初始表单。
默认 max_num=None 表示1000这个较高上限,实践中相当于不限制。默认它只影响显示,不影响验证;传入 validate_max=True 才将其用于验证,见后文。
限制实例化的表单数量
absolute_max 限制根据 POST 数据可实例化的表单数,防御伪造 POST 请求造成的内存耗尽:
>>> from django.forms.formsets import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, absolute_max=1500)
>>> data = {
... "form-TOTAL_FORMS": "1501",
... "form-INITIAL_FORMS": "0",
... }
>>> formset = ArticleFormSet(data)
>>> len(formset.forms)
1500
>>> formset.is_valid()
False
>>> formset.non_form_errors()
['Please submit at most 1000 forms.']
absolute_max=None 时默认 max_num + 1000;若 max_num 也是 None,默认2000。absolute_max 小于 max_num 会抛出 ValueError。
验证表单集
表单集验证与普通 Form 几乎相同,可以用 is_valid() 一次验证所有表单:
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm)
>>> data = {
... "form-TOTAL_FORMS": "1",
... "form-INITIAL_FORMS": "0",
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
True
虽然没有提交表单字段数据,结果仍有效,因为表单集会忽略未修改的额外表单。若提交无效文章:
>>> data = {
... "form-TOTAL_FORMS": "2",
... "form-INITIAL_FORMS": "0",
... "form-0-title": "Test",
... "form-0-pub_date": "1904-06-16",
... "form-1-title": "Test",
... "form-1-pub_date": "", # <-- this date is missing but required
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {'pub_date': ['This field is required.']}]
formset.errors 是与各表单一一对应的列表。两个表单都经过验证,第二项出现预期错误。
与普通 Form 一样,字段可带 maxlength 等浏览器验证属性;但不会包含 required,因为动态增删表单时该验证可能不正确。
BaseFormSet.total_error_count()
用它获取实际错误总数:
>>> # Using the previous example
>>> formset.errors
[{}, {'pub_date': ['This field is required.']}]
>>> len(formset.errors)
2
>>> formset.total_error_count()
1
也可检查表单数据是否不同于初始数据,例如没有输入内容就提交:
>>> data = {
... "form-TOTAL_FORMS": "1",
... "form-INITIAL_FORMS": "0",
... "form-0-title": "",
... "form-0-pub_date": "",
... }
>>> formset = ArticleFormSet(data)
>>> formset.has_changed()
False
理解 ManagementForm
前面数据中的 form-TOTAL_FORMS 和 form-INITIAL_FORMS 是 ManagementForm 所需管理数据。表单集用它管理整组表单;缺失时表单集无效:
>>> data = {
... "form-0-title": "Test",
... "form-0-pub_date": "",
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
它追踪显示的表单实例数量。通过 JavaScript 添加新表单时,也应增加管理表单的计数字段;通过 JavaScript 删除已有对象时,POST 数据中应包含相应的 form-#-DELETE,正确标记删除。无论如何,POST 中都应包含全部表单。
管理表单是表单集自身的属性。在模板渲染 {{ my_formset.management_form }} 即可包含全部管理数据,其中变量名应按实际表单集替换。
total_form_count 与 initial_form_count
BaseFormSet 的这两个方法与 ManagementForm 密切相关。total_form_count() 返回总表单数;initial_form_count() 返回预填表单数,也用于确定多少表单是必需的。通常无需覆盖,修改前务必理解其作用。
empty_form
BaseFormSet.empty_form 返回前缀为 __prefix__ 的表单实例,方便通过 JavaScript 动态创建表单。
error_messages
error_messages 允许覆盖表单集默认错误消息。传入字典,键为要覆盖的消息类型,包括 too_few_forms、too_many_forms、missing_management_form。前两类可包含 %(num)d,分别替换成 min_num 和 max_num。
缺少管理表单时的默认消息:
>>> formset = ArticleFormSet({})
>>> formset.is_valid()
False
>>> formset.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.']
自定义消息:
>>> formset = ArticleFormSet(
... {}, error_messages={"missing_management_form": "Sorry, something went wrong."}
... )
>>> formset.is_valid()
False
>>> formset.non_form_errors()
['Sorry, something went wrong.']
自定义表单集验证
表单集有类似 Form 的 clean(),可在此定义跨表单验证:
>>> from django.core.exceptions import ValidationError
>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... def clean(self):
... """Checks that no two articles have the same title."""
... if any(self.errors):
... # Don't bother validating the formset unless each form is valid on its own
... return
... titles = set()
... for form in self.forms:
... if self.can_delete and self._should_delete_form(form):
... continue
... title = form.cleaned_data.get("title")
... if title in titles:
... raise ValidationError("Articles in a set must have distinct titles.")
... titles.add(title)
...
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
>>> data = {
... "form-TOTAL_FORMS": "2",
... "form-INITIAL_FORMS": "0",
... "form-0-title": "Test",
... "form-0-pub_date": "1904-06-16",
... "form-1-title": "Test",
... "form-1-pub_date": "1912-06-23",
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Articles in a set must have distinct titles.']
所有 Form.clean() 调用后,才调用表单集 clean();错误通过 non_form_errors() 取得。
非表单错误渲染时会额外带 nonform CSS 类,以便与单个表单错误区分。例如 {{ formset.non_form_errors }}:
<ul class="errorlist nonform">
<li>Articles in a set must have distinct titles.</li>
</ul>
验证表单数量
Django 提供提交表单数的最小、最大值验证。需要更灵活的数量规则时,应使用自定义表单集验证。
validate_max
给 formset_factory() 传 validate_max=True 后,还会检查提交表单数减去标记删除的数量,是否不超过 max_num:
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, max_num=1, validate_max=True)
>>> data = {
... "form-TOTAL_FORMS": "2",
... "form-INITIAL_FORMS": "0",
... "form-0-title": "Test",
... "form-0-pub_date": "1904-06-16",
... "form-1-title": "Test 2",
... "form-1-pub_date": "1912-06-23",
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Please submit at most 1 form.']
这一验证严格执行,即使超过 max_num 是因初始数据过多。可在 error_messages 设置 too_many_forms 自定义消息。
validate_min
传入 validate_min=True 后,还会检查提交表单数减去标记删除的数量,是否至少为 min_num:
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, min_num=3, validate_min=True)
>>> data = {
... "form-TOTAL_FORMS": "2",
... "form-INITIAL_FORMS": "0",
... "form-0-title": "Test",
... "form-0-pub_date": "1904-06-16",
... "form-1-title": "Test 2",
... "form-1-pub_date": "1912-06-23",
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {}]
>>> formset.non_form_errors()
['Please submit at least 3 forms.']
可通过 error_messages 的 too_few_forms 自定义消息。
排序和删除表单
formset_factory() 提供 can_order 与 can_delete 两个可选参数,支持排序和删除。
BaseFormSet.can_order
默认 False。设为真可创建可排序表单集:
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, can_order=True)
>>> formset = ArticleFormSet(
... initial=[
... {"title": "Article #1", "pub_date": datetime.date(2008, 5, 10)},
... {"title": "Article #2", "pub_date": datetime.date(2008, 5, 11)},
... ]
... )
>>> for form in formset:
... print(form)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></div>
<div><label for="id_form-0-ORDER">Order:</label><input type="number" name="form-0-ORDER" value="1" id="id_form-0-ORDER"></div>
<div><label for="id_form-1-title">Title:</label><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></div>
<div><label for="id_form-1-pub_date">Pub date:</label><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></div>
<div><label for="id_form-1-ORDER">Order:</label><input type="number" name="form-1-ORDER" value="2" id="id_form-1-ORDER"></div>
<div><label for="id_form-2-title">Title:</label><input type="text" name="form-2-title" id="id_form-2-title"></div>
<div><label for="id_form-2-pub_date">Pub date:</label><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></div>
<div><label for="id_form-2-ORDER">Order:</label><input type="number" name="form-2-ORDER" id="id_form-2-ORDER"></div>
每个表单会增加名为 ORDER 的 forms.IntegerField,初始表单自动赋予序号。用户修改顺序后:
>>> data = {
... "form-TOTAL_FORMS": "3",
... "form-INITIAL_FORMS": "2",
... "form-0-title": "Article #1",
... "form-0-pub_date": "2008-05-10",
... "form-0-ORDER": "2",
... "form-1-title": "Article #2",
... "form-1-pub_date": "2008-05-11",
... "form-1-ORDER": "1",
... "form-2-title": "Article #3",
... "form-2-pub_date": "2008-05-01",
... "form-2-ORDER": "0",
... }
>>> formset = ArticleFormSet(
... data,
... initial=[
... {"title": "Article #1", "pub_date": datetime.date(2008, 5, 10)},
... {"title": "Article #2", "pub_date": datetime.date(2008, 5, 11)},
... ],
... )
>>> for form in formset.ordered_forms:
... print(form.cleaned_data)
...
{'title': 'Article #3', 'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0}
{'title': 'Article #2', 'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1}
{'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2}
BaseFormSet 还提供 ordering_widget 和 get_ordering_widget(),控制排序字段部件。
BaseFormSet.ordering_widget
默认 NumberInput。设置所用部件类:
>>> from django.forms import BaseFormSet, formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... ordering_widget = HiddenInput
...
>>> ArticleFormSet = formset_factory(
... ArticleForm, formset=BaseArticleFormSet, can_order=True
... )
BaseFormSet.get_ordering_widget()
若需要提供部件实例,可覆盖此方法:
>>> from django.forms import BaseFormSet, formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... def get_ordering_widget(self):
... return HiddenInput(attrs={"class": "ordering"})
...
>>> ArticleFormSet = formset_factory(
... ArticleForm, formset=BaseArticleFormSet, can_order=True
... )
BaseFormSet.can_delete
默认 False。启用后允许选择要删除的表单:
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
>>> formset = ArticleFormSet(
... initial=[
... {"title": "Article #1", "pub_date": datetime.date(2008, 5, 10)},
... {"title": "Article #2", "pub_date": datetime.date(2008, 5, 11)},
... ]
... )
>>> for form in formset:
... print(form)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></div>
<div><label for="id_form-0-DELETE">Delete:</label><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE"></div>
<div><label for="id_form-1-title">Title:</label><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></div>
<div><label for="id_form-1-pub_date">Pub date:</label><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></div>
<div><label for="id_form-1-DELETE">Delete:</label><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE"></div>
<div><label for="id_form-2-title">Title:</label><input type="text" name="form-2-title" id="id_form-2-title"></div>
<div><label for="id_form-2-pub_date">Pub date:</label><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></div>
<div><label for="id_form-2-DELETE">Delete:</label><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE"></div>
每个表单增加名为 DELETE 的 forms.BooleanField。提交数据标记删除后,可通过 deleted_forms 访问:
>>> data = {
... "form-TOTAL_FORMS": "3",
... "form-INITIAL_FORMS": "2",
... "form-0-title": "Article #1",
... "form-0-pub_date": "2008-05-10",
... "form-0-DELETE": "on",
... "form-1-title": "Article #2",
... "form-1-pub_date": "2008-05-11",
... "form-1-DELETE": "",
... "form-2-title": "",
... "form-2-pub_date": "",
... "form-2-DELETE": "",
... }
>>> formset = ArticleFormSet(
... data,
... initial=[
... {"title": "Article #1", "pub_date": datetime.date(2008, 5, 10)},
... {"title": "Article #2", "pub_date": datetime.date(2008, 5, 11)},
... ],
... )
>>> [form.cleaned_data for form in formset.deleted_forms]
[{'title': 'Article #1', 'pub_date': datetime.date(2008, 5, 10), 'DELETE': True}]
使用 ModelFormSet 时,formset.save() 会删除被标记表单对应的模型实例。若使用 formset.save(commit=False),则不会自动删除,需逐个调用 formset.deleted_objects 中对象的 delete():
>>> instances = formset.save(commit=False)
>>> for obj in formset.deleted_objects:
... obj.delete()
...
普通 FormSet 没有统一的“删除表单”语义,需要自行处理 formset.deleted_forms,例如在自定义 save() 中处理。
BaseFormSet 也提供 deletion_widget 与 get_deletion_widget() 控制删除字段部件。
BaseFormSet.deletion_widget
默认 CheckboxInput。设置部件类:
>>> from django.forms import BaseFormSet, formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... deletion_widget = HiddenInput
...
>>> ArticleFormSet = formset_factory(
... ArticleForm, formset=BaseArticleFormSet, can_delete=True
... )
BaseFormSet.get_deletion_widget()
若需提供部件实例,可覆盖:
>>> from django.forms import BaseFormSet, formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... def get_deletion_widget(self):
... return HiddenInput(attrs={"class": "deletion"})
...
>>> ArticleFormSet = formset_factory(
... ArticleForm, formset=BaseArticleFormSet, can_delete=True
... )
BaseFormSet.can_delete_extra
默认 True。启用 can_delete=True 时,设 can_delete_extra=False 可去掉删除额外空表单的选项。
添加额外字段
覆盖基类的 add_fields() 即可添加字段,也可重定义排序和删除字段的默认字段或属性:
>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
... def add_fields(self, form, index):
... super().add_fields(form, index)
... form.fields["my_field"] = forms.CharField()
...
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
>>> formset = ArticleFormSet()
>>> for form in formset:
... print(form)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></div>
<div><label for="id_form-0-my_field">My field:</label><input type="text" name="form-0-my_field" id="id_form-0-my_field"></div>
向表单传递自定义参数
表单类可能接收自定义参数,如 MyArticleForm。可以在实例化表单集时传入:
>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> class MyArticleForm(ArticleForm):
... def __init__(self, *args, user, **kwargs):
... self.user = user
... super().__init__(*args, **kwargs)
...
>>> ArticleFormSet = formset_factory(MyArticleForm)
>>> formset = ArticleFormSet(form_kwargs={"user": request.user})
form_kwargs 也可能随具体表单而变化。基类的 get_form_kwargs() 接收表单在表单集中的索引;对于 empty_form,索引为 None:
>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> class BaseArticleFormSet(BaseFormSet):
... def get_form_kwargs(self, index):
... kwargs = super().get_form_kwargs(index)
... kwargs["custom_kwarg"] = index
... return kwargs
...
>>> ArticleFormSet = formset_factory(MyArticleForm, formset=BaseArticleFormSet)
>>> formset = ArticleFormSet()
自定义前缀
渲染的 HTML 中,各字段名称都有前缀,默认为 form,可通过 prefix 参数修改。默认例如:
<label for="id_form-0-title">Title:</label>
<input type="text" name="form-0-title" id="id_form-0-title">
指定 ArticleFormset(prefix='article') 后:
<label for="id_article-0-title">Title:</label>
<input type="text" name="article-0-title" id="id_article-0-title">
在同一视图使用多个表单集时,这很有用。
在视图与模板中使用表单集
以下属性和方法与渲染有关:
BaseFormSet.renderer
指定表单集使用的渲染器,默认来自 FORM_RENDERER。
BaseFormSet.template_name
表单集转成字符串时使用的模板名,例如 print(formset) 或模板里的 {{ formset }}。默认属性返回渲染器的 formset_template_name;可以设置字符串模板名,为特定表单集类覆盖。模板先渲染管理表单,再按照各表单的 Form.template_name 渲染每个表单。
各布局模板
BaseFormSet.template_name_div:调用as_div()的模板,默认 django/forms/formsets/div.html;先管理表单,再按各表单as_div()渲染。BaseFormSet.template_name_p:调用as_p(),默认 django/forms/formsets/p.html;先管理表单,再按各表单as_p()渲染。BaseFormSet.template_name_table:调用as_table(),默认 django/forms/formsets/table.html;先管理表单,再按各表单as_table()渲染。BaseFormSet.template_name_ul:调用as_ul(),默认 django/forms/formsets/ul.html;先管理表单,再按各表单as_ul()渲染。
BaseFormSet.get_context()
返回模板渲染上下文,其中 formset 为当前表单集实例。
BaseFormSet.render(template_name=None, context=None, renderer=None)
__str__、as_div()、as_p()、as_ul() 和 as_table() 都调用它。参数均可选,默认 template_name 来自同名属性,context 来自 get_context(),renderer 来自同名属性。
BaseFormSet.as_div()、as_p()、as_table()、as_ul() 分别使用 template_name_div、template_name_p、template_name_table、template_name_ul 渲染。
在视图中使用表单集与普通 Form 类差别不大,关键是确保模板包含管理表单:
from django.forms import formset_factory
from django.shortcuts import render
from myapp.forms import ArticleForm
def manage_articles(request):
ArticleFormSet = formset_factory(ArticleForm)
if request.method == "POST":
formset = ArticleFormSet(request.POST, request.FILES)
if formset.is_valid():
# do something with the formset.cleaned_data
pass
else:
formset = ArticleFormSet()
return render(request, "manage_articles.html", {"formset": formset})
manage_articles.html 可以写成:
<form method="post">
{{ formset.management_form }}
<table>
{% for form in formset %}
{{ form }}
{% endfor %}
</table>
</form>
也可以让表单集自行处理管理表单,简写为:
<form method="post">
<table>
{{ formset }}
</table>
</form>
这最终调用 BaseFormSet.render(),使用 template_name 指定模板。与普通表单一样,默认按 as_div 渲染,也支持 as_p、as_ul、as_table。可以指定 template_name,或覆盖内置模板来自定义渲染。
手动渲染 can_delete 与 can_order
若手动渲染字段,可用 {{ form.DELETE }} 渲染删除选项:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
<ul>
<li>{{ form.title }}</li>
<li>{{ form.pub_date }}</li>
{% if formset.can_delete %}
<li>{{ form.DELETE }}</li>
{% endif %}
</ul>
{% endfor %}
</form>
启用 can_order=True 时,同样可用 {{ form.ORDER }} 渲染排序字段。
在同一视图中使用多个表单集
表单集的许多行为继承自表单。通过 prefix 为字段名称加前缀,可以把多个表单集提交给一个视图而不发生名称冲突:
from django.forms import formset_factory
from django.shortcuts import render
from myapp.forms import ArticleForm, BookForm
def manage_articles(request):
ArticleFormSet = formset_factory(ArticleForm)
BookFormSet = formset_factory(BookForm)
if request.method == "POST":
article_formset = ArticleFormSet(request.POST, request.FILES, prefix="articles")
book_formset = BookFormSet(request.POST, request.FILES, prefix="books")
if article_formset.is_valid() and book_formset.is_valid():
# do something with the cleaned_data on the formsets.
pass
else:
article_formset = ArticleFormSet(prefix="articles")
book_formset = BookFormSet(prefix="books")
return render(
request,
"manage_articles.html",
{
"article_formset": article_formset,
"book_formset": book_formset,
},
)
随后照常渲染。POST 和非 POST 两条路径都必须传入相同前缀,才能正确渲染和处理。每个表单集的前缀会替换所有字段 HTML name 和 id 属性中的默认 form 前缀。











暂无评论内容