# Django 2.2.9.dev20191202104323 文档

# 表单集

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 。

## 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',
    ...     'form-MAX_NUM_FORMS': '',
    ... }
    >>> formset = ArticleFormSet(data)
    >>> formset.is_valid()
    True

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

    >>> data = {
    ...     'form-TOTAL_FORMS': '2',
    ...     'form-INITIAL_FORMS': '0',
    ...     'form-MAX_NUM_FORMS': '',
    ...     '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-MAX_NUM_FORMS': '',
    ...     'form-0-title': '',
    ...     'form-0-pub_date': '',
    ... }
    >>> formset = ArticleFormSet(data)
    >>> formset.has_changed()
    False

### 理解 `ManagementForm`

您可能已经注意到上面formset中有些必要的数据( `form-TOTAL_FORMS` , `form-INITIAL_FORMS` 以及 `form-MAX_NUM_FORMS` )。这些数据是 `ManagementForm` 所必须的。它被formset用来管理formset中所有表单。如果你不提供这些管理数据，则会引发一场:

    >>> data = {
    ...     'form-0-title': 'Test',
    ...     'form-0-pub_date': '',
    ... }
    >>> formset = ArticleFormSet(data)
    >>> formset.is_valid()
    Traceback (most recent call last):
    ...
    django.forms.utils.ValidationError: ['ManagementForm data is missing or has been tampered with']

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

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

### `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使用。

### 自定义formset验证

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

    >>> 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 forms.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-MAX_NUM_FORMS': '',
    ...     '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-MIN_NUM_FORMS': '',
    ...     'form-MAX_NUM_FORMS': '',
    ...     '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 1 or fewer forms.']

即使因为提供的初始数据量过大而超过了 `max_num` 所定义的，`validate_max=True` 还是会严格针对 `max_num` 进行验证。

注解

如果数据集中表单的数量超过了 `max_num` 定义的且大于1000，那么即使你设置了 `validate_max` ，这个验证也会无效，另外只有在 `max_num` 内的前1000张表单会被验证。其余部分将被完全截断。这是为了抵御使用伪造POST请求的内存耗尽攻击。

### `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-MIN_NUM_FORMS': '',
    ...     'form-MAX_NUM_FORMS': '',
    ...     '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 3 or more forms.']

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

方法 `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-MAX_NUM_FORMS': '',
    ...     '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'}

### `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-MAX_NUM_FORMS': '',
    ...     '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()` 方法中，因为对于阐述删除一张表单还没有一个通用的概念。

## 给一个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` 前缀。