表单集 — Django 文档

来自菜鸟教程
Django/docs/3.2.x/topics/forms/formsets
跳转至:导航、​搜索

表单集

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.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></td></tr>

如您所见,它只显示一个空表单。 显示的空表格数量由 extra 参数控制。 默认情况下, formset_factory() 定义了一个额外的表单; 以下示例将创建一个 formset 类来显示两个空白表单:

>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)

迭代表单集将按照它们创建的顺序呈现表单。 您可以通过为 __iter__() 方法提供替代实现来更改此顺序。

表单集也可以被索引,它返回相应的表单。 如果您覆盖 __iter__,您还需要覆盖 __getitem__ 以获得匹配行为。

将初始数据与表单集一起使用

初始数据是驱动表单集主要可用性的驱动因素。 如上所示,您可以定义额外表格的数量。 这意味着您要告诉表单集除了从初始数据生成的表单数量外,还要显示多少额外的表单。 我们来看一个例子:

>>> 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.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-12" id="id_form-0-pub_date"></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" id="id_form-1-title"></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" id="id_form-1-pub_date"></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title"></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></td></tr>

现在上面一共显示了三种形式。 一个用于传入的初始数据和两个额外的表单。 另请注意,我们将字典列表作为初始数据传递。

如果使用 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.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></td></tr>

如果 max_num 的值大于初始数据中现有项目的数量,则最多 extra 个额外的空白表单将被添加到表单集中,只要表单的总数不超过不超过 max_num。 例如,如果 extra=2max_num=2 并且表单集初始化为一个 initial 项,则将显示初始项的表单和一个空白表单。

如果初始数据中的项数超过max_num,则不管max_num的值如何,都会显示所有初始数据表格,并且不会显示额外表格。 例如,如果 extra=3max_num=1 并且表单集使用两个初始项进行初始化,则将显示两个带有初始数据的表单。

None(默认值)的 max_num 值对显示的表单数量(1000)设置了上限。 在实践中,这相当于没有限制。

默认情况下,max_num 只影响显示的表单数量,不影响验证。 如果将 validate_max=True 传递给 formset_factory(),则 max_num 会影响验证。 参见 validate_max


限制实例化表单的最大数量

3.2 版中的新功能。


formset_factory()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_maxNone时,默认为max_num + 1000。 (如果max_numNone,则默认为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 一样,formset 表单中的每个字段都可能包含 HTML 属性,例如 maxlength 用于浏览器验证。 但是,表单集的表单字段不会包含 required 属性,因为在添加和删除表单时该验证可能不正确。

BaseFormSet.total_error_count()

要检查表单集中有多少错误,我们可以使用 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_FORMSform-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(根据需要替换表单集的名称)来包含所有管理数据。

笔记

除了此处示例中显示的 form-TOTAL_FORMSform-INITIAL_FORMS 字段外,管理表单还包括 form-MIN_NUM_FORMSform-MAX_NUM_FORMS 字段。 它们与管理表单的其余部分一起输出,但只是为了方便客户端代码。 这些字段不是必需的,因此不会在示例 POST 数据中显示。


在 3.2 版更改:formset.is_valid() 现在返回 False 而不是在管理表单丢失或被篡改时引发异常。


total_form_count 和 initial_form_count

BaseFormSet 有几个与 ManagementFormtotal_form_countinitial_form_count 密切相关的方法。

total_form_count 返回此表单集中的表单总数。 initial_form_count 返回表单集中预先填写的表单数量,也用于确定需要多少表单。 您可能永远不需要覆盖这些方法中的任何一个,所以在这样做之前请确保您了解它们的作用。


empty_form

BaseFormSet 提供了一个额外的属性 empty_form,它返回一个带有 __prefix__ 前缀的表单实例,以便在使用 JavaScript 的动态表单中更容易使用。


error_messages

3.2 版中的新功能。


error_messages 参数允许您覆盖表单集将引发的默认消息。 传入一个字典,其键与要覆盖的错误消息匹配。 例如,这里是缺少管理表单时的默认错误消息:

>>> 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.']

自定义表单集验证

表单集有一个 clean 方法,类似于 Form 类上的方法。 您可以在此处定义自己的适用于表单集级别的验证:

>>> 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 = []
...         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.append(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 方法后,将调用 formset clean 方法。 使用表单集上的 non_form_errors() 方法将发现错误。


验证表单集中的表单数量

Django 提供了几种方法来验证提交表单的最小或最大数量。 需要对表单数量进行更多可自定义验证的应用程序应使用自定义表单集验证。

validate_max

如果将 validate_max=True 传递给 formset_factory(),则验证还将检查数据集中的表单数(减去标记为删除的表单数)是否小于或等于 [ X185X]。

>>> 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.']

validate_max=True 严格根据 max_num 进行验证,即使由于提供的初始数据量过多而超过了 max_num

笔记

不管validate_max,如果一个数据集中的表单数量超过absolute_max,那么表单将无法验证,就像设置了validate_max一样,另外只有第一个[ X175X] 表单将被验证。 其余部分将被完全截断。 这是为了防止使用伪造的 POST 请求进行内存耗尽攻击。 请参阅 限制实例化表单的最大数量


validate_min

如果将 validate_min=True 传递给 formset_factory(),则验证还将检查数据集中的表单数(减去标记为删除的表单数)是否大于或等于 [ X188X]。

>>> 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.']

笔记

不管validate_min,如果表单集不包含数据,则显示extra + min_num空表单。


处理表单的排序和删除

formset_factory() 提供了两个可选参数 can_ordercan_delete,以帮助对表单集中的表单进行排序以及从表单集中删除表单。

can_order

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.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></td></tr>
<tr><th><label for="id_form-0-ORDER">Order:</label></th><td><input type="number" name="form-0-ORDER" value="1" id="id_form-0-ORDER"></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></td></tr>
<tr><th><label for="id_form-1-ORDER">Order:</label></th><td><input type="number" name="form-1-ORDER" value="2" id="id_form-1-ORDER"></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title"></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></td></tr>
<tr><th><label for="id_form-2-ORDER">Order:</label></th><td><input type="number" name="form-2-ORDER" id="id_form-2-ORDER"></td></tr>

这会为每个表单添加一个附加字段。 这个新字段被命名为 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)},
... ])
>>> formset.is_valid()
True
>>> for form in formset.ordered_forms:
...     print(form.cleaned_data)
{'pub_date': datetime.date(2008, 5, 1), 'ORDER': 0, 'title': 'Article #3'}
{'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': 'Article #2'}
{'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': 'Article #1'}

BaseFormSet 还提供了 ordering_widget 属性和 get_ordering_widget() 方法来控制与 can_order 一起使用的小部件。

ordering_widget

BaseFormSet.ordering_widget

默认值:NumberInput

设置 ordering_widget 以指定要与 can_order 一起使用的小部件类:

>>> 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)

get_ordering_widget

BaseFormSet.get_ordering_widget()

如果您需要提供用于 can_order 的小部件实例,请覆盖 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)

can_delete

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.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></td></tr>
<tr><th><label for="id_form-0-DELETE">Delete:</label></th><td><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE"></td></tr>
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></td></tr>
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></td></tr>
<tr><th><label for="id_form-1-DELETE">Delete:</label></th><td><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE"></td></tr>
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title"></td></tr>
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></td></tr>
<tr><th><label for="id_form-2-DELETE">Delete:</label></th><td><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE"></td></tr>

can_order 类似,它为每个名为 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]
[{'DELETE': True, 'pub_date': datetime.date(2008, 5, 10), 'title': 'Article #1'}]

如果您使用的是 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() 方法中,因为没有关于什么的一般概念意思是删除一个表格。


can_delete_extra

3.2 版中的新功能。


BaseFormSet.can_delete_extra

默认值:True

设置 can_delete=True 时,指定 can_delete_extra=False 将取消删除多余表格的选项。


向表单集中添加其他字段

如果您需要向表单集中添加其他字段,这可以轻松完成。 formset 基类提供了一个 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.as_table())
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title"></td></tr>
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></td></tr>
<tr><th><label for="id_form-0-my_field">My field:</label></th><td><input type="text" name="form-0-my_field" id="id_form-0-my_field"></td></tr>

将自定义参数传递给表单集表单

有时您的表单类采用自定义参数,例如 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 也可能取决于特定的表单实例。 formset 基类提供了一个 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

自定义表单集的前缀

在呈现的 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">

如果您想 在一个视图 中使用多个表单集,这将非常有用。


在视图和模板中使用表单集

在视图中使用表单集与使用常规 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>

以上最终调用了 formset 类上的 as_table 方法。

手动渲染 can_delete 和 can_order

如果手动渲染模板中的字段,则可以使用 模板:Form.DELETE 渲染 can_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>

同理,如果formset有排序能力(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 情况下传递 prefix,以便正确呈现和处理它。

每个表单集的 前缀 替换添加到每个字段的 nameid HTML 属性的默认 form 前缀。