---
title: "表单集"
version: 4.1
locale: zh-hans
source: https://docs.djangoproject.com/zh-hans/4.1/topics/forms/formsets/
canonical: https://djangodocs.dev/zh-hans/4.1/topics/forms/formsets/
---
# 表单集

#### `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()`](/zh-hans/4.1/ref/forms/formsets/#django.forms.formsets.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=[...])` 。

> **See also**
>
> [创建模型表单集](/zh-hans/4.1/topics/forms/modelforms/#model-formsets)。

## 限制表单的最大数量

[`formset_factory()`](/zh-hans/4.1/ref/forms/formsets/#django.forms.formsets.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()`](/zh-hans/4.1/ref/forms/formsets/#django.forms.formsets.formset_factory)，那么 `max_num` 将会影响验证。参见 [validate\_max](#validate-max) 。

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

`formet_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_max` 为 `None` 时，它默认为 `max_num + 1000`。（如果 `max_num` 是 `None`，则默认为 `2000`）。

如果 `absolute_max` 小于 `max_num`，将引发 `ValueError`。

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

你可能已经注意到在上面的表单集的数据中需要额外的数据（ `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来删除已存在对象，那么您需确认被移除的对象已经被标记在 `form-#-DELETE` 中并被放到 `POST` 内。无论如何，所有表单都要确保在 `POST` 数据中。

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

> **Note**
>
> 除了这里的例子中显示的 `form-TOTAL_FORMS` 和 `form-INITIAL_FORMS` 字段，管理表单还包括 `form-MIN_NUM_FORMS` 和 `form-MAX_NUM_FORMS` 字段。它们与管理表单的其他部分一起输出，但只是为了方便客户端的代码。这些字段不是必须的，所以没有显示在示例的 `POST` 数据中。

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

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. Error message keys include `'too_few_forms'`,
`'too_many_forms'`, and `'missing_management_form'`. The
`'too_few_forms'` and `'too_many_forms'` error messages may contain
`%(num)d`, which will be replaced with `min_num` and `max_num`,
respectively.

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

> **Changed in Django 4.1**
>
> The `'too_few_forms'` and `'too_many_forms'` keys were added.

### 自定义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()` 方法来查看错误信息。

非表单错误将用一个额外的类 `nonform` 来呈现，以帮助区分它们与表单特定错误。例如，`{{ formset.non_form_errors }}` 将看起来像:

```html+django
<ul class="errorlist nonform">
    <li>Articles in a set must have distinct titles.</li>
</ul>
```

> **Changed in Django 4.0**
>
> 增加了额外的 `nonform` 类。

## 验证formset中表单的数量

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

### `validate_max`

如果方法 [`formset_factory()`](/zh-hans/4.1/ref/forms/formsets/#django.forms.formsets.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` 进行验证。

The error message can be customized by passing the `'too_many_forms'` message
to the [error\_messages](#formsets-error-messages) argument.

> **Note**
>
> 不管 `validate_max` 如何，如果数据集中的表单数量超过 `absolute_max`，那么表单将无法验证，就像 `validate_max` 被设置一样，另外只有第一个 `absolute_max` 的表单会被验证。其余的将被完全截断。这是为了防止使用伪造的 POST 请求的内存耗尽攻击。参见 [限制实例化表单的最大数量](#formsets-absolute-max)。

### `validate_min`

如果方法 [`formset_factory()`](/zh-hans/4.1/ref/forms/formsets/#django.forms.formsets.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.']
```

The error message can be customized by passing the `'too_few_forms'` message
to the [error\_messages](#formsets-error-messages) argument.

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

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

方法 [`formset_factory()`](/zh-hans/4.1/ref/forms/formsets/#django.forms.formsets.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`](#django.forms.formsets.BaseFormSet) 也提供了 [`ordering_widget`](#django.forms.formsets.BaseFormSet.ordering_widget) 属性和 [`get_ordering_widget()`](#django.forms.formsets.BaseFormSet.get_ordering_widget) 方法，来控制与 [`can_order`](#django.forms.formsets.BaseFormSet.can_order) 一起使用的小部件。

#### `ordering_widget`

#### `BaseFormSet.ordering_widget`

默认: [`NumberInput`](/zh-hans/4.1/ref/forms/widgets/#django.forms.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`](/zh-hans/4.1/topics/forms/modelforms/#django.forms.models.BaseModelFormSet) ，那些标记为删除的表单模型实例会在调用 `formset.save()` 时被删除。

如果您调用调用 `formset.save(commit=False)` ，对象将不会被自动删除。您需要在每个 [`formset.deleted_objects`](/zh-hans/4.1/topics/forms/modelforms/#django.forms.models.BaseModelFormSet.deleted_objects)  上调用 `delete()` 来真正删除他们:

```
>>> instances = formset.save(commit=False)
>>> for obj in formset.deleted_objects:
...     obj.delete()
```

另一方面，如果您使用的是普通的 `FormSet` ，那需要您自己去处理 `formset.deleted_forms` ，可能写在formset的 `save()` 方法中，因为对于阐述删除一张表单还没有一个通用的概念。

[`BaseFormSet`](#django.forms.formsets.BaseFormSet) 也提供了一个 [`deletion_widget`](#django.forms.formsets.BaseFormSet.deletion_widget) 属性和 [`get_deletion_widget()`](#django.forms.formsets.BaseFormSet.get_deletion_widget) 方法，控制用于 [`can_delete`](#django.forms.formsets.BaseFormSet.can_delete) 的部件。

#### `deletion_widget`

> **New in Django 4.0**

#### `BaseFormSet.deletion_widget`

默认： [`CheckboxInput`](/zh-hans/4.1/ref/forms/widgets/#django.forms.CheckboxInput)

设置 `deletion_widget` 来指定与 `can_delete` 一起使用的部件类：

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

#### `get_deletion_widget`

> **New in Django 4.0**

#### `BaseFormSet.get_deletion_widget()`

如果你需要提供一个用于 `can_delete` 的部件实例，请覆盖 `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)
```

### `can_delete_extra`

#### `BaseFormSet.can_delete_extra`

默认： `True`

在设置 `can_delete=True` 的同时，指定 `can_delete_extra=False` 将移除删除额外表格的选项。

## 给一个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](#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()
```

## 自定义formset的前缀

在已渲染的HTML页面中，表单集中的每个字段都包含一个前缀。这个前缀默认是 `'form'` ，但可以使用formset的 `prefix` 参数来自定义。

例如，在默认情况下，您可能会看到:

```html
<label for="id_form-0-title">Title:</label>
<input type="text" name="form-0-title" id="id_form-0-title">
```

但使用 `ArticleFormset(prefix='article')` 的话就会变为：

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

Formsets have the following attributes and methods associated with rendering:

#### `BaseFormSet.renderer`

> **New in Django 4.0**

指定 [渲染器](/zh-hans/4.1/ref/forms/renderers/) 用于表单集。默认为 `FORM_RENDER` 配置所指定的渲染器。

#### `BaseFormSet.template_name`

> **New in Django 4.0**

The name of the template rendered if the formset is cast into a string,
e.g. via `print(formset)` or in a template via `{{ formset }}`.

By default, a property returning the value of the renderer's
[`formset_template_name`](/zh-hans/4.1/ref/forms/renderers/#django.forms.renderers.BaseRenderer.formset_template_name). You may
set it as a string template name in order to override that for a particular
formset class.

This template will be used to render the formset's management form, and
then each form in the formset as per the template defined by the form's
[`template_name`](/zh-hans/4.1/ref/forms/api/#django.forms.Form.template_name).

> **Changed in Django 4.1**
>
> In older versions `template_name` defaulted to the string value
> `'django/forms/formset/default.html'`.

#### `BaseFormSet.template_name_div`

> **New in Django 4.1**

The name of the template used when calling [`as_div()`](#django.forms.formsets.BaseFormSet.as_div). By default this
is `"django/forms/formsets/div.html"`. This template renders the
formset's management form and then each form in the formset as per the
form's [`as_div()`](/zh-hans/4.1/ref/forms/api/#django.forms.Form.as_div) method.

#### `BaseFormSet.template_name_p`

> **New in Django 4.0**

The name of the template used when calling [`as_p()`](#django.forms.formsets.BaseFormSet.as_p). By default this
is `"django/forms/formsets/p.html"`. This template renders the formset's
management form and then each form in the formset as per the form's
[`as_p()`](/zh-hans/4.1/ref/forms/api/#django.forms.Form.as_p) method.

#### `BaseFormSet.template_name_table`

> **New in Django 4.0**

The name of the template used when calling [`as_table()`](#django.forms.formsets.BaseFormSet.as_table). By default
this is `"django/forms/formsets/table.html"`. This template renders the
formset's management form and then each form in the formset as per the
form's [`as_table()`](/zh-hans/4.1/ref/forms/api/#django.forms.Form.as_table) method.

#### `BaseFormSet.template_name_ul`

> **New in Django 4.0**

The name of the template used when calling [`as_ul()`](#django.forms.formsets.BaseFormSet.as_ul). By default this
is `"django/forms/formsets/ul.html"`. This template renders the formset's
management form and then each form in the formset as per the form's
[`as_ul()`](/zh-hans/4.1/ref/forms/api/#django.forms.Form.as_ul) method.

#### `BaseFormSet.get_context()`

> **New in Django 4.0**

返回用于在模板中渲染表单集的上下文。

可用的上下文：

- `formset`：表单集的实例。

#### `BaseFormSet.render(template_name=None, context=None, renderer=None)`

> **New in Django 4.0**

The render method is called by `__str__` as well as the [`as_div()`](#django.forms.formsets.BaseFormSet.as_div),
[`as_p()`](#django.forms.formsets.BaseFormSet.as_p), [`as_ul()`](#django.forms.formsets.BaseFormSet.as_ul), and [`as_table()`](#django.forms.formsets.BaseFormSet.as_table) methods. All arguments
are optional and will default to:

- `template_name`： [`template_name`](#django.forms.formsets.BaseFormSet.template_name)
- `context`： 由 [`get_context()`](#django.forms.formsets.BaseFormSet.get_context) 返回的值
- `renderer`： 由 [`renderer`](#django.forms.formsets.BaseFormSet.renderer) 返回的值

#### `BaseFormSet.as_div()`

> **New in Django 4.1**

Renders the formset with the [`template_name_div`](#django.forms.formsets.BaseFormSet.template_name_div) template.

#### `BaseFormSet.as_p()`

用 [`template_name_p`](#django.forms.formsets.BaseFormSet.template_name_p) 模板渲染表单集。

#### `BaseFormSet.as_table()`

使用 [`template_name_table`](#django.forms.formsets.BaseFormSet.template_name_table) 模板渲染表单集。

#### `BaseFormSet.as_ul()`

用 [`template_name_ul`](#django.forms.formsets.BaseFormSet.template_name_ul) 模板渲染表单集。

在视图中使用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` 可能如下所示：

```html+django
<form method="post">
    {{ formset.management_form }}
    <table>
        {% for form in formset %}
        {{ form }}
        {% endfor %}
    </table>
</form>
```

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

```html+django
<form method="post">
    <table>
        {{ formset }}
    </table>
</form>
```

上面的内容最终会调用表单集类上的 [`BaseFormSet.render()`](#django.forms.formsets.BaseFormSet.render) 方法。这将使用 [`template_name`](#django.forms.formsets.BaseFormSet.template_name) 属性指定的模板来渲染表单集。与表单类似，默认情况下，表单集将被渲染为 `as_table`，其他辅助方法 `as_p` 和 `as_ul` 可用。表单集的渲染可以通过指定 `template_name` 属性来定制，或者更普遍的是通过 [覆盖默认模板](/zh-hans/4.1/ref/forms/renderers/#overriding-built-in-formset-templates)。

> **Changed in Django 4.0**
>
> 表单集的渲染被转移到模板引擎。

### 手动渲染 `can_delete` 和 `can_order`

如果您在模板中手动渲染字段，您可以用 `{{ form.DELETE }}` 来渲染参数 `can_delete` 。

```html+django
<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](#formset-prefix) 会替换添加到每个字段的 `name` 和 `id` HTML属性的默认 `form` 前缀。
