表单集

class BaseFormSet[源代码]

表单集是在同一页上处理多个表单的抽象层。它可以与数据网格相提并论。假设您有以下表单:

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

您可能希望允许用户一次创建多篇文章。创建表格集的步骤 ArticleForm 您会这样做:

>>> from django.forms import formset_factory
>>> ArticleFormSet = formset_factory(ArticleForm)

现在,您已经创建了一个名为 ArticleFormSet 。实例化表单集使您能够迭代表单集中的表单并像使用常规表单一样显示它们:

>>> formset = ArticleFormSet()
>>> for form in formset:
...     print(form)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></div>

正如您所看到的,它只显示了一个空表单。显示的空表单数由 extra 参数。默认情况下, formset_factory() 定义一个额外的表单;下面的示例将创建一个FormSet类来显示两个空白表单:

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

迭代一个表单集将按照表单创建的顺序呈现表单。您可以通过为 __iter__() 方法。

也可以将表单集编入索引中,该索引将返回相应的表单。如果你重写 __iter__ ,您还需要重写 __getitem__ 有匹配的行为。

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

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

>>> import datetime
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
>>> formset = ArticleFormSet(
...     initial=[
...         {
...             "title": "Django is now open source",
...             "pub_date": datetime.date.today(),
...         }
...     ]
... )

>>> for form in formset:
...     print(form)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2023-02-11" id="id_form-0-pub_date"></div>
<div><label for="id_form-1-title">Title:</label><input type="text" name="form-1-title" id="id_form-1-title"></div>
<div><label for="id_form-1-pub_date">Pub date:</label><input type="text" name="form-1-pub_date" id="id_form-1-pub_date"></div>
<div><label for="id_form-2-title">Title:</label><input type="text" name="form-2-title" id="id_form-2-title"></div>
<div><label for="id_form-2-pub_date">Pub date:</label><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></div>

现在总共有三种形式显示在上面。一个用于传递的初始数据,另外两个表单。还要注意的是,我们正在传递一个字典列表作为初始数据。

如果你使用 initial 对于显示表单集,应传递相同的 initial 处理该表单集的提交时,以便表单集可以检测用户更改了哪些表单。例如,您可能有如下内容: ArticleFormSet(request.POST, initial=[...]) .

限制窗体的最大数目

这个 max_num 参数设置为 formset_factory() 使您能够限制表单集将显示的表单数量:

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

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

如果初始数据中的项数超过 max_num ,将显示所有初始数据窗体,而不管 max_num 不会显示额外的表单。例如,如果 extra=3max_num=1 表单集初始化为两个初始项,将显示两个具有初始数据的表单。

A max_num 价值 None (默认值)对显示的表单数(1000)设置了一个上限。实际上,这相当于没有限制。

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

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

这个 absolute_max 参数设置为 formset_factory() 允许限制提供时可实例化的表单的数量 POST 数据。这可以防止使用伪造的内存耗尽攻击 POST 请求:

>>> from django.forms.formsets import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, absolute_max=1500)
>>> data = {
...     "form-TOTAL_FORMS": "1501",
...     "form-INITIAL_FORMS": "0",
... }
>>> formset = ArticleFormSet(data)
>>> len(formset.forms)
1500
>>> formset.is_valid()
False
>>> formset.non_form_errors()
['Please submit at most 1000 forms.']

什么时候 absolute_maxNone ,则默认为 max_num + 1000 。(如果 max_numNone ,则默认为 2000 )。

如果 absolute_max 小于 max_num ,A ValueError 将被提升。

表单集验证

使用表单集进行验证几乎与使用常规 Form 。有一个 is_valid 方法,以提供一种验证表单集中所有表单的便捷方法:

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm)
>>> data = {
...     "form-TOTAL_FORMS": "1",
...     "form-INITIAL_FORMS": "0",
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
True

我们没有向表单集传递任何数据,这将导致生成有效的表单。表单集足够智能,可以忽略未更改的额外表单。如果我们提供了一篇无效文章:

>>> data = {
...     "form-TOTAL_FORMS": "2",
...     "form-INITIAL_FORMS": "0",
...     "form-0-title": "Test",
...     "form-0-pub_date": "1904-06-16",
...     "form-1-title": "Test",
...     "form-1-pub_date": "",  # <-- this date is missing but required
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
>>> formset.errors
[{}, {'pub_date': ['This field is required.']}]

正如我们所看到的, formset.errors 是其条目与表单集中的表单相对应的列表。对这两个表单中的每一个都进行了验证,第二个项目将显示预期的错误消息。

就像使用普通的 Form ,表单集表单中的每个字段可能包含HTML属性,例如 maxlength 用于浏览器验证。但是,表单集的表单字段不包括 required 属性,因为添加和删除表单时验证可能不正确。

BaseFormSet.total_error_count()[源代码]

要检查表单集中有多少错误,我们可以使用 total_error_count 方法:

>>> # Using the previous example
>>> formset.errors
[{}, {'pub_date': ['This field is required.']}]
>>> len(formset.errors)
2
>>> formset.total_error_count()
1

我们还可以检查表单数据是否与初始数据不同(即表单发送时没有任何数据):

>>> data = {
...     "form-TOTAL_FORMS": "1",
...     "form-INITIAL_FORMS": "0",
...     "form-0-title": "",
...     "form-0-pub_date": "",
... }
>>> formset = ArticleFormSet(data)
>>> formset.has_changed()
False

了解 ManagementForm

您可能已经注意到额外的数据 (form-TOTAL_FORMSform-INITIAL_FORMS ),这是上述表单集数据中所需的。此数据是必需的 ManagementForm 。该表单由表单集用来管理表单集中包含的表单集合。如果您不提供此管理数据,表单集将无效:

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

它用于跟踪显示的表单实例数。如果您通过javascript添加新表单,那么您也应该增加该表单中的count字段。另一方面,如果使用javascript允许删除现有对象,则需要确保要删除的对象正确标记为删除,方法是包括 form-#-DELETEPOST 数据。预计所有表单都存在于 POST 不考虑数据。

管理表单作为表单集本身的属性提供。在模板中呈现表单集时,可以通过呈现包括所有管理数据 {{{{ my_formset.management_form }}}} (根据需要替换表单集的名称)。

备注

以及 form-TOTAL_FORMSform-INITIAL_FORMS 在此处的示例中显示的字段中,管理表单还包括 form-MIN_NUM_FORMSform-MAX_NUM_FORMS 菲尔兹。它们都是用睡觉输出的管理表单,但只是为了方便客户端代码。这些字段不是必填字段,因此在示例中未显示 POST 数据。

total_form_count and initial_form_count

BaseFormSet 有一些方法与 ManagementFormtotal_form_countinitial_form_count .

total_form_count 返回此表单集中的表单总数。 initial_form_count 返回表单集中预先填充的表单数,并用于确定需要多少表单。您可能永远不需要重写这两种方法中的任何一种,因此请确保您了解它们在执行之前所做的工作。

empty_form

BaseFormSet 提供附加属性 empty_form 返回前缀为的窗体实例 __prefix__ 以便于在带有JavaScript的动态表单中使用。

error_messages

这个 error_messages 参数允许您覆盖表单集将引发的默认消息。传入一个字典,其中的键与要重写的错误消息匹配。错误消息键包括 'too_few_forms''too_many_forms' ,以及 'missing_management_form' 。这个 'too_few_forms''too_many_forms' 错误消息可能包含 %(num)d ,它将被替换为 min_nummax_num ,分别为。

例如,以下是缺少管理表单时的默认错误消息:

>>> formset = ArticleFormSet({})
>>> formset.is_valid()
False
>>> formset.non_form_errors()
['ManagementForm data is missing or has been tampered with. Missing fields: form-TOTAL_FORMS, form-INITIAL_FORMS. You may need to file a bug report if the issue persists.']

下面是一条定制的错误消息:

>>> formset = ArticleFormSet(
...     {}, error_messages={"missing_management_form": "Sorry, something went wrong."}
... )
>>> formset.is_valid()
False
>>> formset.non_form_errors()
['Sorry, something went wrong.']

自定义表单集验证

表单集具有一个 clean 方法类似于 Form 班级。您可以在此处定义您自己的验证,该验证在表单集级进行:

>>> from django.core.exceptions import ValidationError
>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm

>>> class BaseArticleFormSet(BaseFormSet):
...     def clean(self):
...         """Checks that no two articles have the same title."""
...         if any(self.errors):
...             # Don't bother validating the formset unless each form is valid on its own
...             return
...         titles = set()
...         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.add(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.']

窗体集 clean 方法是在 Form.clean 方法已被调用。错误将使用 non_form_errors() 方法。

将使用附加的类呈现非表单错误 nonform 以帮助将它们与特定于表单的错误区分开来。例如, {{ formset.non_form_errors }} 将如下所示:

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

正在验证表单集中的表单数

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

validate_max

如果 validate_max=True 传递给 formset_factory() ,验证还将检查数据集中的表单数减去标记为删除的表单数是否小于或等于 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.']

validate_max=True 验证 max_num 即使 max_num 已超过,因为提供的初始数据量过多。

错误消息可以通过传递 'too_many_forms' 发送给 error_messages 争论。

备注

不管 validate_max ,如果数据集中的窗体数超过 absolute_max ,则表单将无法验证,就像 validate_max 是的,另外只有第一个 absolute_max 表格将被验证。其余部分将被完全截断。这是为了防止使用伪造的POST请求进行内存耗尽攻击。看到了吗 限制实例化表单的最大数量 .

validate_min

如果 validate_min=True 传递给 formset_factory() ,验证还将检查数据集中的表单数减去标记为删除的表单数是否大于或等于 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.']

错误消息可以通过传递 'too_few_forms' 发送给 error_messages 争论。

备注

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

处理表格的排序和删除

这个 formset_factory() 提供两个可选参数 can_ordercan_delete 帮助排序表单集中的表单并从表单集中删除表单。

can_order

BaseFormSet.can_order

违约: False

允许您创建能够订购的表单集:

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, can_order=True)
>>> formset = ArticleFormSet(
...     initial=[
...         {"title": "Article #1", "pub_date": datetime.date(2008, 5, 10)},
...         {"title": "Article #2", "pub_date": datetime.date(2008, 5, 11)},
...     ]
... )
>>> for form in formset:
...     print(form)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></div>
<div><label for="id_form-0-ORDER">Order:</label><input type="number" name="form-0-ORDER" value="1" id="id_form-0-ORDER"></div>
<div><label for="id_form-1-title">Title:</label><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></div>
<div><label for="id_form-1-pub_date">Pub date:</label><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></div>
<div><label for="id_form-1-ORDER">Order:</label><input type="number" name="form-1-ORDER" value="2" id="id_form-1-ORDER"></div>
<div><label for="id_form-2-title">Title:</label><input type="text" name="form-2-title" id="id_form-2-title"></div>
<div><label for="id_form-2-pub_date">Pub date:</label><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></div>
<div><label for="id_form-2-ORDER">Order:</label><input type="number" name="form-2-ORDER" id="id_form-2-ORDER"></div>

这将为每个表单添加一个额外的字段。此新字段名为 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()[源代码]

超覆 get_ordering_widget() 如果您需要提供与配合使用的小部件实例 can_order

>>> from django.forms import BaseFormSet, formset_factory
>>> from myapp.forms import ArticleForm
>>> class BaseArticleFormSet(BaseFormSet):
...     def get_ordering_widget(self):
...         return HiddenInput(attrs={"class": "ordering"})
...

>>> ArticleFormSet = formset_factory(
...     ArticleForm, formset=BaseArticleFormSet, can_order=True
... )

can_delete

BaseFormSet.can_delete

违约: False

允许您创建能够选择要删除的表单的表单集:

>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm
>>> ArticleFormSet = formset_factory(ArticleForm, can_delete=True)
>>> formset = ArticleFormSet(
...     initial=[
...         {"title": "Article #1", "pub_date": datetime.date(2008, 5, 10)},
...         {"title": "Article #2", "pub_date": datetime.date(2008, 5, 11)},
...     ]
... )
>>> for form in formset:
...     print(form)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date"></div>
<div><label for="id_form-0-DELETE">Delete:</label><input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE"></div>
<div><label for="id_form-1-title">Title:</label><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title"></div>
<div><label for="id_form-1-pub_date">Pub date:</label><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date"></div>
<div><label for="id_form-1-DELETE">Delete:</label><input type="checkbox" name="form-1-DELETE" id="id_form-1-DELETE"></div>
<div><label for="id_form-2-title">Title:</label><input type="text" name="form-2-title" id="id_form-2-title"></div>
<div><label for="id_form-2-pub_date">Pub date:</label><input type="text" name="form-2-pub_date" id="id_form-2-pub_date"></div>
<div><label for="id_form-2-DELETE">Delete:</label><input type="checkbox" name="form-2-DELETE" id="id_form-2-DELETE"></div>

类似于 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) ,则不会自动删除对象。你需要打电话给 delete() 在每个 formset.deleted_objects 要实际删除它们,请执行以下操作:

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

另一方面,如果您使用的是普通的 FormSet 由你来处理 formset.deleted_forms 可能在你的表格里 save() 方法,因为没有删除表单的一般概念。

BaseFormSet 还提供了一个 deletion_widget 属性和 get_deletion_widget() 控制与一起使用的小部件的方法 can_delete

deletion_widget

BaseFormSet.deletion_widget

默认: 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

BaseFormSet.get_deletion_widget()[源代码]

超覆 get_deletion_widget() 如果您需要提供与配合使用的小部件实例 can_delete

>>> 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基类提供了 add_fields 方法。您可以覆盖此方法来添加您自己的字段,甚至可以重新定义Order和Delete字段的默认字段/属性:

>>> 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)
...
<div><label for="id_form-0-title">Title:</label><input type="text" name="form-0-title" id="id_form-0-title"></div>
<div><label for="id_form-0-pub_date">Pub date:</label><input type="text" name="form-0-pub_date" id="id_form-0-pub_date"></div>
<div><label for="id_form-0-my_field">My field:</label><input type="text" name="form-0-my_field" id="id_form-0-my_field"></div>

将自定义参数传递到窗体集窗体

有时,您的Form类采用自定义参数,如 MyArticleForm 。您可以在实例化表单集时传递此参数:

>>> from django.forms import BaseFormSet
>>> from django.forms import formset_factory
>>> from myapp.forms import ArticleForm

>>> class MyArticleForm(ArticleForm):
...     def __init__(self, *args, user, **kwargs):
...         self.user = user
...         super().__init__(*args, **kwargs)
...

>>> ArticleFormSet = formset_factory(MyArticleForm)
>>> formset = ArticleFormSet(form_kwargs={"user": request.user})

这个 form_kwargs 也可能取决于特定的表单实例。FormSet基类提供了 get_form_kwargs 方法。该方法只接受一个参数--表单集中表单的索引。该指数是 None 对于 empty_form

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

自定义表单集的前缀

在呈现的HTML中,表单集在每个字段的名称上包含一个前缀。默认情况下,前缀为 'form' ,但可以使用表单集的 prefix 参数。

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

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

但与 ArticleFormset(prefix='article') 变成:

<label for="id_article-0-title">Title:</label>
<input type="text" name="article-0-title" id="id_article-0-title">

如果你想的话这很有用 use more than one formset in a view .

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

表单集具有以下与呈现相关的属性和方法:

BaseFormSet.renderer

指定 renderer 用于表单集。默认设置为由 FORM_RENDERER 布景。

BaseFormSet.template_name

将表单集转换为字符串时呈现的模板的名称,例如VIA print(formset) 或在模板中通过 {{ formset }}

默认情况下,返回呈现器的 formset_template_name 。您可以将其设置为字符串模板名称,以便覆盖特定FormSet类的名称。

此模板将用于呈现表单集的管理表单,然后根据表单的 template_name

BaseFormSet.template_name_div

调用时使用的模板的名称 as_div() 。默认情况下,这是 "django/forms/formsets/div.html" 。该模板呈现表单集的管理表单,然后根据表单的 as_div() 方法。

BaseFormSet.template_name_p

调用时使用的模板的名称 as_p() 。默认情况下,这是 "django/forms/formsets/p.html" 。该模板呈现表单集的管理表单,然后根据表单的 as_p() 方法。

BaseFormSet.template_name_table

调用时使用的模板的名称 as_table() 。默认情况下,这是 "django/forms/formsets/table.html" 。该模板呈现表单集的管理表单,然后根据表单的 as_table() 方法。

BaseFormSet.template_name_ul

调用时使用的模板的名称 as_ul() 。默认情况下,这是 "django/forms/formsets/ul.html" 。该模板呈现表单集的管理表单,然后根据表单的 as_ul() 方法。

BaseFormSet.get_context()[源代码]

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

可用的上下文为:

  • formset :表单集的实例。

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

Render方法由调用 __str__ 以及 as_div()as_p()as_ul() ,以及 as_table() 方法:研究方法。所有参数都是可选的,默认为:

BaseFormSet.as_div()

属性呈现表单集。 template_name_div 模板。

BaseFormSet.as_p()

属性呈现表单集。 template_name_p 模板。

BaseFormSet.as_table()

属性呈现表单集。 template_name_table 模板。

BaseFormSet.as_ul()

属性呈现表单集。 template_name_ul 模板。

在视图中使用窗体集与使用常规 Form 班级。您唯一想要了解的是确保使用模板内的管理表单。让我们看一个示例视图:

from django.forms import formset_factory
from django.shortcuts import render
from myapp.forms import ArticleForm


def manage_articles(request):
    ArticleFormSet = formset_factory(ArticleForm)
    if request.method == "POST":
        formset = ArticleFormSet(request.POST, request.FILES)
        if formset.is_valid():
            # do something with the formset.cleaned_data
            pass
    else:
        formset = ArticleFormSet()
    return render(request, "manage_articles.html", {"formset": formset})

这个 manage_articles.html 模板可能如下所示:

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

但是,通过让表单集自己处理管理表单,可以实现上述目的:

<form method="post">
    <table>
        {{ formset }}
    </table>
</form>

以上代码最终调用 BaseFormSet.render() 在FormSet类上使用。属性指定的模板呈现表单集 template_name 属性。与表单类似,默认情况下将呈现表单集 as_table ,并使用其他帮助器方法 as_pas_ul 有空。表单集的呈现可以通过指定 template_name 属性,或者更一般地通过 overriding the default template

手动渲染 can_deletecan_order

如果手动渲染模板中的字段,则可以渲染 can_delete 参数与 {{{{ form.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>

同样,如果表单集能够 (can_order=True )可以用 {{{{ form.ORDER }}}} .

在视图中使用多个表单集

如果愿意,您可以在视图中使用多个表单集。表单集的许多行为都是从表单中借用的。说你可以用 prefix 为表单集表单域名称加上前缀,以允许将多个表单集发送到一个视图而不发生名称冲突。让我们来看看这是如何实现的:

from django.forms import formset_factory
from django.shortcuts import render
from myapp.forms import ArticleForm, BookForm


def manage_articles(request):
    ArticleFormSet = formset_factory(ArticleForm)
    BookFormSet = formset_factory(BookForm)
    if request.method == "POST":
        article_formset = ArticleFormSet(request.POST, request.FILES, prefix="articles")
        book_formset = BookFormSet(request.POST, request.FILES, prefix="books")
        if article_formset.is_valid() and book_formset.is_valid():
            # do something with the cleaned_data on the formsets.
            pass
    else:
        article_formset = ArticleFormSet(prefix="articles")
        book_formset = BookFormSet(prefix="books")
    return render(
        request,
        "manage_articles.html",
        {
            "article_formset": article_formset,
            "book_formset": book_formset,
        },
    )

然后将表单集呈现为正常。重要的是指出你需要通过 prefix 在Post和非Post案例中,以便正确呈现和处理。

每个窗体的 prefix 替换默认值 form 添加到每个字段的前缀 nameid HTML属性。