表单集

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)

You now have created a formset class named ArticleFormSet. Instantiating the formset gives you the ability to iterate over the forms in the formset and display them as you would with a regular form:

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

As you can see it only displayed one empty form. The number of empty forms that is displayed is controlled by the extra parameter. By default, formset_factory() defines one extra form; the following example will create a formset class to display two blank forms:

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

Iterating over a formset will render the forms in the order they were created. You can change this order by providing an alternate implementation for the __iter__() method.

表单集也可以被索引然后返回对应的表单。如果您已经覆盖了 __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=2max_num=2 并且formset有一个 initial 初始化项,则会显示一张初始化表单和一张空白表单。

如果初始数据项的数量超过 max_num ,那么 max_num 的值会被无视,所有初始数据表单都会显示,并且也不会有额外的表单显示。例如,假设 extra=3max_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_countinitial_form_count

BaseFormSet 有一对与 ManagementForm 密切相关的方法, total_form_countinitial_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_ordercan_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_deletecan_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 会替换添加到每个字段的 nameid HTML属性的默认 form 前缀。