表单集是在同一页上处理多个表单的抽象层。它可以与数据网格相提并论。假设您有以下表单:
>>> 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=2
和 max_num=2
表单集初始化为一个 initial
项目,将显示初始项目的表单和一个空白表单。
如果初始数据中的项数超过 max_num
,将显示所有初始数据窗体,而不管 max_num
不会显示额外的表单。例如,如果 extra=3
和 max_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_max
是 None
,则默认为 max_num + 1000
。(如果 max_num
是 None
,则默认为 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
属性,因为添加和删除表单时验证可能不正确。
要检查表单集中有多少错误,我们可以使用 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_FORMS
, form-INITIAL_FORMS
),这是上述表单集数据中所需的。此数据是必需的 ManagementForm
。该表单由表单集用来管理表单集中包含的表单集合。如果您不提供此管理数据,表单集将无效:
>>> data = {
... "form-0-title": "Test",
... "form-0-pub_date": "",
... }
>>> formset = ArticleFormSet(data)
>>> formset.is_valid()
False
它用于跟踪显示的表单实例数。如果您通过javascript添加新表单,那么您也应该增加该表单中的count字段。另一方面,如果使用javascript允许删除现有对象,则需要确保要删除的对象正确标记为删除,方法是包括 form-#-DELETE
在 POST
数据。预计所有表单都存在于 POST
不考虑数据。
管理表单作为表单集本身的属性提供。在模板中呈现表单集时,可以通过呈现包括所有管理数据 {{{{ my_formset.management_form }}}}
(根据需要替换表单集的名称)。
备注
以及 form-TOTAL_FORMS
和 form-INITIAL_FORMS
在此处的示例中显示的字段中,管理表单还包括 form-MIN_NUM_FORMS
和 form-MAX_NUM_FORMS
菲尔兹。它们都是用睡觉输出的管理表单,但只是为了方便客户端代码。这些字段不是必填字段,因此在示例中未显示 POST
数据。
total_form_count
and initial_form_count
¶BaseFormSet
有一些方法与 ManagementForm
, total_form_count
和 initial_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_num
和 max_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_order
和 can_delete
帮助排序表单集中的表单并从表单集中删除表单。
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
¶违约: 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
¶超覆 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
¶违约: 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
¶默认: 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
¶超覆 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
¶违约: 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 .
表单集具有以下与呈现相关的属性和方法:
指定 renderer 用于表单集。默认设置为由 FORM_RENDERER
布景。
将表单集转换为字符串时呈现的模板的名称,例如VIA print(formset)
或在模板中通过 {{ formset }}
。
默认情况下,返回呈现器的 formset_template_name
。您可以将其设置为字符串模板名称,以便覆盖特定FormSet类的名称。
此模板将用于呈现表单集的管理表单,然后根据表单的 template_name
。
调用时使用的模板的名称 as_div()
。默认情况下,这是 "django/forms/formsets/div.html"
。该模板呈现表单集的管理表单,然后根据表单的 as_div()
方法。
调用时使用的模板的名称 as_p()
。默认情况下,这是 "django/forms/formsets/p.html"
。该模板呈现表单集的管理表单,然后根据表单的 as_p()
方法。
调用时使用的模板的名称 as_table()
。默认情况下,这是 "django/forms/formsets/table.html"
。该模板呈现表单集的管理表单,然后根据表单的 as_table()
方法。
调用时使用的模板的名称 as_ul()
。默认情况下,这是 "django/forms/formsets/ul.html"
。该模板呈现表单集的管理表单,然后根据表单的 as_ul()
方法。
Render方法由调用 __str__
以及 as_div()
, as_p()
, as_ul()
,以及 as_table()
方法:研究方法。所有参数都是可选的,默认为:
template_name
: template_name
context
: Value returned by get_context()
renderer
: Value returned by renderer
属性呈现表单集。 template_name_div
模板。
属性呈现表单集。 template_name_p
模板。
属性呈现表单集。 template_name_table
模板。
属性呈现表单集。 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_p
和 as_ul
有空。表单集的呈现可以通过指定 template_name
属性,或者更一般地通过 overriding the default template 。
can_delete
和 can_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
添加到每个字段的前缀 name
和 id
HTML属性。
12月 18, 2023