# Django 3.2.11.dev 文档

# 表单集

class `BaseFormSet`

formset是一个抽象层，它可以在同一页面上处理多个表单的。它最适合被比喻成网格数据。我们假设您有以下表单:

    >>> from django import forms
    >>> class ArticleForm(forms.Form):
    ...     title = forms.CharField()
    ...     pub_date = forms.DateField()

您可能想允许用户一次创建多篇文章。 要创建一个 `ArticleForm` 的formset，您可以这样做:

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

如你所见，它只显示一个空表单。显示的空表单数量由 `额外` 参数控制。默认情况下，`formset_factory()` 定义了一个额外表单；下面的例子将创建一个表单集类来显示两个空白表单：

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

遍历 formset 将按照它们创建的顺序渲染表单。你可以通过为 `__iter__()` 方法提供替代的实现来改变这个顺序。

表单集也可以被索引然后返回对应的表单。如果您已经覆盖了 `__iter__` ，则还需覆盖 `__getitem__` 让它具备匹配行为。

## 使用formset的初始数据

初始数据驱动着formset的主要能力。如上所示，您可以定义额外表单的数量。也就是说，您告诉formset，除了要生成初始数据所需数量的表单外，还要显示多少额外的表单。我们来看下例子:

    >>> 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` 来显示formset，那么您需要在处理formset提交时传入相同的 `initial` ，以便formset检测用户更改了哪些表单。例如，您可能有这样的： `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=2` ， `max_num=2` 并且formset有一个 `initial` 初始化项，则会显示一张初始化表单和一张空白表单。

如果初始数据项的数量超过 `max_num` ，那么 `max_num` 的值会被无视，所有初始数据表单都会显示，并且也不会有额外的表单显示。例如，假设 `extra=3` ， `max_num=1` 并且formset有两个初始化项，那么只会显示两张有初始化数据的表单。

`max_num` 的值 `None` (默认值)，它限制最多显示(1000)张表单，其实这相当于没有限制。

`max_num` 默认只影响显示多少数量的表单而不影响验证。如果将 `validate_max=True` 传给 `formset_factory()`，那么 `max_num` 将会影响验证。参见 validate_max 。

## Limiting the maximum number of instantiated forms

New in Django 3.2.

The `absolute_max` parameter to `formset_factory()` allows limiting the number of forms that can be instantiated when supplying `POST` data. This protects against memory exhaustion attacks using forged `POST` requests:

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

When `absolute_max` is `None`, it defaults to `max_num + 1000`. (If `max_num` is `None`, it defaults to `2000`).

If `absolute_max` is less than `max_num`, a `ValueError` will be raised.

## Formset验证

formset的验证与常规 `Form` 几乎相同。formset提供了一个 `is_valid` 方法以便验证formset内所有表单:

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

我们传了空数据给formset，并被给了一个有效的结果。formset足够聪明去忽略那些没有变动的额外表单。如果我们提供了一篇无效的文章:

    >>> 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` 是一张列表，它的内容对应着formset中表单。两张表都进行了验证，并且第二项中出现了预期的错误消息。

和使用普通 `Form` 一样，formset表单中的每个字段都可能包含HTML属性，例如用于浏览器验证的 `maxlength` 。但是由于表单添加、删除的时候会影响属性 `required` 的验证，表单集中的表单不会包含此属性。

`BaseFormSet.``total_error_count`()

我们可以使用 `total_error_count` 方法来检查formset中有多少错误:

    >>> # 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`

You may have noticed the additional data (`form-TOTAL_FORMS`, `form-INITIAL_FORMS`) that was required in the formset's data above. This data is required for the `ManagementForm`. This form is used by the formset to manage the collection of forms contained in the formset. If you don't provide this management data, the formset will be invalid:

    >>> data = {
    ...     'form-0-title': 'Test',
    ...     'form-0-pub_date': '',
    ... }
    >>> formset = ArticleFormSet(data)
    >>> formset.is_valid()
    False

它被用来跟踪显示了多少个表单实例。如果您通过JavaScript添加新表单，那您同样需要增加相应内容到那些数量字段中，另一方面，如果您允许通过JavaScript来删除已存在对象，那么您需确认被移除的对象已经被标记在 `form-#-DELETE` 中并被放到 `POST` 内。无论如何，所有表单都要确保在 `POST` 数据中。

管理表单以formset的一项属性而存在。在模板中渲染formset时，你可以使用 `{{ my_formset.management_form }}` (将my_formset替换为自己的formset名称)渲染出所有管理表单的数据。

注解

As well as the `form-TOTAL_FORMS` and `form-INITIAL_FORMS` fields shown in the examples here, the management form also includes `form-MIN_NUM_FORMS` and `form-MAX_NUM_FORMS` fields. They are output with the rest of the management form, but only for the convenience of client-side code. These fields are not required and so are not shown in the example `POST` data.

Changed in Django 3.2:

`formset.is_valid()` now returns `False` rather than raising an exception when the management form is missing or has been tampered with.

### `total_form_count` 和 `initial_form_count`

`BaseFormSet` 有一对与 `ManagementForm` 密切相关的方法， `total_form_count` 和 `initial_form_count` 。

`total_form_count` 返回该formset内表单的总和。 `initial_form_count` 返回该formset内预填充的表单数量，同时用于定义需要多少表单。你可能永远不会重写这两个方法，因此在使用之前请理解它们的用途。

### `empty_form`

`BaseFormSet``有一项属性``empty_form`，它返回一个以``__prefix__`` 为前缀的表单实例，这是为了方便在动态表单中配合JavaScript使用。

### `error_messages`

New in Django 3.2.

The `error_messages` argument lets you override the default messages that the formset will raise. Pass in a dictionary with keys matching the error messages you want to override. For example, here is the default error message when the management form is missing:

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

### 自定义formset验证

formset有个与 `Form` 类相似的 `clean` 方法。您可以在这里定义自己的验证规则，它会在formset层面进行验证。

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

formset的 `clean` 方法会在所有 `Form.clean` 方法调用完之后被调用。可以使用formset的 `non_form_errors()` 方法来查看错误信息。

## 验证formset中表单的数量

Django提供了一对方法来验证已提交的表单的最小和最大数量。如果要对应用程序进行更多的可定制验证，那需要使用自定义formset验证。

### `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` 所定义的，`validate_max=True` 还是会严格针对 `max_num` 进行验证。

注解

Regardless of `validate_max`, if the number of forms in a data set exceeds `absolute_max`, then the form will fail to validate as if `validate_max` were set, and additionally only the first `absolute_max` forms will be validated. The remainder will be truncated entirely. This is to protect against memory exhaustion attacks using forged POST requests. See Limiting the maximum number of instantiated forms.

### `validate_min`

如果方法 `formset_factory()` 有传参数 `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.']

注解

无论 `validate_min` 的值是什么，如果一个 formset 不包含任何数据，那么将显示 `extra + min_num` 空表单。

## 处理表单的排序和删除

方法 `formset_factory()` 提供了两个可选参数 `can_order` 和 `can_delete` 来协助处理formset中表单的排序和删除。

### `can_order`

`BaseFormSet.``can_order`

默认值： `False`

让你创建能排序的formset:

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

让你创建能删除指定表单的formset:

    >>> 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` ，可能写在formset的 `save()` 方法中，因为对于阐述删除一张表单还没有一个通用的概念。

### `can_delete_extra`

New in Django 3.2.

`BaseFormSet.``can_delete_extra`

默认： `True`

While setting `can_delete=True`, specifying `can_delete_extra=False` will remove the option to delete extra forms.

## 给一个formset添加额外字段

如果你想在formset中添加额外的字段，这相当简单。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>

## 传递自定义参数到formset表单

有时候您的表单类需要自定义参数，比如 `MyArticleForm` 。您可以在formset实例化的时候传递这个参数:

    >>> 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` 方法。该方法只接收一个参数——formset中表单的序列。对于 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

## 自定义formset的前缀

在已渲染的HTML页面中，表单集中的每个字段都包含一个前缀。这个前缀默认是 `'form'` ，但可以使用formset的 `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">

如果您想 :ref:`在视图中使用多个formset <multiple-formsets-in-view> ` ，这个参数会很有用。

## 在视图和模板中使用formset

在视图中使用formset与使用常规的 `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>

但是对于上面让formset自己处理管理表单，还有个小小的捷径：

    <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 }}` 来渲染它。

### 在视图中使用多个formset

你可以在视图中使用多个formset。表单集从表单上借鉴了很多行为。像之前说的，你可以使用参数 `prefix` 来给formset中表单的字段附上前缀，以避免多个formset的数据传到同一个视图而引起名称冲突。让我们来看下这是如何实现的：

    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` ，以便它能被正确渲染和处理。

每个formset的 prefix 会替换添加到每个字段的 `name` 和 `id` HTML属性的默认 `form` 前缀。