ModelForm
¶如果您正在构建一个数据库驱动的应用程序,那么很可能您将拥有与Django模型紧密对应的表单。例如,您可能有 BlogComment
模型,您希望创建一个允许人们提交评论的表单。在这种情况下,在表单中定义字段类型是多余的,因为您已经在模型中定义了字段。
因此,Django提供了一个助手类,该类允许您创建 Form
从Django模型中类。
例如:
>>> from django.forms import ModelForm
>>> from myapp.models import Article
# Create the form class.
>>> class ArticleForm(ModelForm):
... class Meta:
... model = Article
... fields = ["pub_date", "headline", "content", "reporter"]
...
# Creating a form to add an article.
>>> form = ArticleForm()
# Creating a form to change an existing article.
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(instance=article)
生成的 Form
类将按中指定的顺序为每个指定的模型字段指定一个窗体字段。 fields
属性。
每个模型字段都有一个对应的默认表单字段。例如,A CharField
在模型上表示为 CharField
一种形式。模型 ManyToManyField
表示为 MultipleChoiceField
. 以下是转换的完整列表:
模型场 |
表单字段 |
---|---|
表格中未显示 |
|
表格中未显示 |
|
|
|
|
|
|
|
|
|
|
|
|
|
表格中未显示 |
|
|
|
如你所料, ForeignKey
和 ManyToManyField
模型字段类型是特殊情况:
ForeignKey
代表为 django.forms.ModelChoiceField
,这是一个 ChoiceField
谁的选择是模型 QuerySet
.
ManyToManyField
代表为 django.forms.ModelMultipleChoiceField
,这是一个 MultipleChoiceField
谁的选择是模型 QuerySet
.
此外,每个生成的表单字段都具有如下设置的属性:
如果模型字段 blank=True
然后 required
设置为 False
在窗体字段上。否则, required=True
.
表单域的 label
设置为 verbose_name
模型字段,第一个字符大写。
表单域的 help_text
设置为 help_text
模型字段的。
如果模型字段 choices
设置,然后表单域的 widget
将被设置为 Select
,选择来自模型字段的 choices
. 选择通常包括默认选择的空白选项。如果需要该字段,则强制用户进行选择。如果模型字段已包含,则不包含空白选择。 blank=False
和明确的 default
价值观 default
最初将选择值)。
最后,请注意,您可以重写用于给定模型字段的表单字段。见 Overriding the default fields 下面。
考虑这组模型:
from django.db import models
from django.forms import ModelForm
TITLE_CHOICES = {
"MR": "Mr.",
"MRS": "Mrs.",
"MS": "Ms.",
}
class Author(models.Model):
name = models.CharField(max_length=100)
title = models.CharField(max_length=3, choices=TITLE_CHOICES)
birth_date = models.DateField(blank=True, null=True)
def __str__(self):
return self.name
class Book(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ["name", "title", "birth_date"]
class BookForm(ModelForm):
class Meta:
model = Book
fields = ["name", "authors"]
有了这些模型, ModelForm
上面的子类大致相当于这个(唯一的区别是 save()
方法,我们稍后讨论。)::
from django import forms
class AuthorForm(forms.Form):
name = forms.CharField(max_length=100)
title = forms.CharField(
max_length=3,
widget=forms.Select(choices=TITLE_CHOICES),
)
birth_date = forms.DateField(required=False)
class BookForm(forms.Form):
name = forms.CharField(max_length=100)
authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
ModelForm
¶验证 ModelForm
:
与普通表单验证一样,模型表单验证在调用 is_valid()
或访问 errors
属性,并在调用时显式 full_clean()
尽管在实践中通常不会使用后一种方法。
Model
验证 (Model.full_clean()
)从表单验证步骤中触发,就在表单的 clean()
方法被调用。
警告
清除过程修改传递给 ModelForm
以各种方式建造。例如,模型上的任何日期字段都将转换为实际日期对象。验证失败可能会使基础模型实例处于不一致的状态,因此建议不要重用它。
clean()
方法¶您可以覆盖 clean()
方法在模型窗体上以与在普通窗体上相同的方式提供附加验证。
附加到模型对象的模型表单实例将包含 instance
属性,使其方法可以访问该特定模型实例。
警告
这个 ModelForm.clean()
方法设置一个标志,使 model validation 步骤验证标记为的模型字段的唯一性 unique
, unique_together
或 unique_for_date|month|year
.
如果要重写 clean()
方法并维护此验证,必须调用父类的 clean()
方法。
作为验证过程的一部分, ModelForm
将调用 clean()
模型上每个字段的方法,该方法在表单上有一个对应的字段。如果排除了任何模型字段,则不会对这些字段运行验证。见 form validation 有关如何进行现场清洁和验证的更多文档。
模型的 clean()
方法将在进行任何唯一性检查之前调用。见 Validating objects 有关模型的更多信息 clean()
钩子。
error_messages
¶在中定义的错误消息 form field
水平或在 form Meta 级别始终优先于在 model field
水平。
上定义的错误消息 model fields
仅当 ValidationError
在 model validation 步骤和没有在表单级别定义相应的错误消息。
您可以覆盖来自的错误消息 NON_FIELD_ERRORS
由模型验证引发,方法是添加 NON_FIELD_ERRORS
关键 error_messages
字典 ModelForm
内心 Meta
类:
from django.core.exceptions import NON_FIELD_ERRORS
from django.forms import ModelForm
class ArticleForm(ModelForm):
class Meta:
error_messages = {
NON_FIELD_ERRORS: {
"unique_together": "%(model_name)s's %(field_labels)s are not unique.",
}
}
save()
方法¶每个 ModelForm
也有 save()
方法。此方法从绑定到表单的数据创建并保存数据库对象。一个子类 ModelForm
不能接受现有模型实例作为关键字参数 instance
;如果提供, save()
将更新该实例。如果没有供应, save()
将创建指定模型的新实例:
>>> from myapp.models import Article
>>> from myapp.forms import ArticleForm
# Create a form instance from POST data.
>>> f = ArticleForm(request.POST)
# Save a new Article object from the form's data.
>>> new_article = f.save()
# Create a form to edit an existing Article, but use
# POST data to populate the form.
>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(request.POST, instance=a)
>>> f.save()
请注意,如果表单 hasn't been validated 呼唤 save()
会通过检查 form.errors
. 一 ValueError
如果表单中的数据未验证,则将引发--例如,如果 form.errors
评估为 True
.
如果表单数据中没有出现可选字段,则生成的模型实例将使用模型字段 default
,如果有一个,则为该字段。此行为不适用于使用 CheckboxInput
, CheckboxSelectMultiple
或 SelectMultiple
(或任何自定义小部件 value_omitted_from_data()
方法始终返回 False
)因为一个未选中的复选框和未选中的 <select multiple>
不显示在HTML表单提交的数据中。如果您正在设计一个API,并且想要使用这些小部件之一的字段的默认回退行为,请使用自定义表单字段或小部件。
这个 save()
方法接受可选的 commit
关键字参数,它接受 True
或 False
. 如果你调用 save()
具有 commit=False
,然后它将返回一个尚未保存到数据库中的对象。在这种情况下,由你来调用 save()
在生成的模型实例上。如果要在保存对象之前对其进行自定义处理,或者要使用特定的 model saving options . commit
是 True
默认情况下。
使用的另一个副作用 commit=False
当您的模型与另一个模型具有多对多关系时会看到。如果您的模型具有多对多关系,并且您指定 commit=False
保存表单时,Django无法立即保存多对多关系的表单数据。这是因为在实例存在于数据库中之前,无法为实例保存多对多的数据。
要解决此问题,每次使用 commit=False
,Django增加了一个 save_m2m()
你的方法 ModelForm
子类。手动保存表单生成的实例后,可以调用 save_m2m()
保存多对多表单数据。例如:
# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)
# Create, but don't save the new author instance.
>>> new_author = f.save(commit=False)
# Modify the author in some way.
>>> new_author.some_field = "some_value"
# Save the new instance.
>>> new_author.save()
# Now, save the many-to-many data for the form.
>>> f.save_m2m()
叫唤 save_m2m()
仅当您使用 save(commit=False)
。当您使用 save()
在表单上,保存所有数据--包括多对多数据--而不需要任何额外的方法调用。例如:
# Create a form instance with POST data.
>>> a = Author()
>>> f = AuthorForm(request.POST, instance=a)
# Create and save the new author instance. There's no need to do anything else.
>>> new_author = f.save()
除了 save()
和 save_m2m()
方法,A ModelForm
工作方式与其他任何方式完全相同 forms
形式。例如, is_valid()
方法用于检查有效性, is_multipart()
方法用于确定窗体是否需要多部分文件上载(因此 request.FILES
必须传递到表单)等。请参见 将上载的文件绑定到表单 更多信息。
强烈建议您使用 fields
属性。如果表单意外地允许用户设置某些字段,特别是在将新字段添加到模型中时,不这样做很容易导致安全问题。根据表单的呈现方式,问题甚至可能在网页上不可见。
另一种方法是自动包括所有字段,或者只删除部分字段。众所周知,这种基本方法的安全性要低得多,并导致了主要网站上的严重利用漏洞(例如 GitHub )。
但是,在您可以保证这些安全问题不适用于您的情况下,有两种快捷方式:
设置 fields
特殊值的属性 '__all__'
指示应使用模型中的所有字段。例如::
from django.forms import ModelForm
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = "__all__"
设置 exclude
的属性 ModelForm
内心 Meta
类到要从窗体中排除的字段列表。
例如::
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
exclude = ["title"]
自从 Author
模型有3个字段 name
, title
和 birth_date
,这将导致字段 name
和 birth_date
在表格上出现。
如果使用其中任何一个,字段在表单中的显示顺序将是字段在模型中定义的顺序,使用 ManyToManyField
实例最后出现。
此外,Django应用以下规则:如果 editable=False
在模型字段上, any 从模型创建的表单通过 ModelForm
不包括该字段。
备注
上述逻辑未包含在表单中的任何字段都不会由表单的 save()
方法。此外,如果手动将排除的字段添加回表单,则不会从模型实例初始化这些字段。
Django将阻止任何保存不完整模型的尝试,因此,如果模型不允许缺少的字段为空,并且不为缺少的字段提供默认值,则任何尝试 save()
一 ModelForm
缺少字段将失败。要避免此失败,必须使用缺少但必需的字段的初始值来实例化模型:
author = Author(title="Mr")
form = PartialAuthorForm(request.POST, instance=author)
form.save()
或者,您可以使用 save(commit=False)
并手动设置任何额外的必需字段:
form = PartialAuthorForm(request.POST)
author = form.save(commit=False)
author.title = "Mr"
author.save()
见 section on saving forms 有关使用的详细信息 save(commit=False)
.
默认字段类型,如中所述 Field types 上表是合理的默认值。如果你有 DateField
在你的模型中,很可能你希望它被表示为 DateField
以你的形式。但是 ModelForm
使您能够灵活地更改给定模型的表单字段。
要为字段指定自定义小部件,请使用 widgets
内部属性 Meta
类。这应该是一个将字段名映射到小部件类或实例的字典。
例如,如果您想要 CharField
对于 name
属性 Author
由A代表 <textarea>
而不是违约 <input type="text">
,您可以重写字段的小部件:
from django.forms import ModelForm, Textarea
from myapp.models import Author
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ["name", "title", "birth_date"]
widgets = {
"name": Textarea(attrs={"cols": 80, "rows": 20}),
}
这个 widgets
字典接受任何一个小部件实例(例如, Textarea(...)
)或类别(例如, Textarea
)。注意, widgets
对于具有非空的模型字段,将忽略字典。 choices
属性。在这种情况下,必须重写表单字段才能使用不同的小部件。
同样,您可以指定 labels
, help_texts
和 error_messages
内部属性 Meta
如果要进一步自定义字段,请初始化。
例如,如果您想自定义 name
领域:
from django.utils.translation import gettext_lazy as _
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ["name", "title", "birth_date"]
labels = {
"name": _("Writer"),
}
help_texts = {
"name": _("Some useful help text."),
}
error_messages = {
"name": {
"max_length": _("This writer's name is too long."),
},
}
您还可以指定 field_classes
或 formfield_callback
自定义表单实例化的字段类型。
例如,如果您想使用 MySlugFormField
对于 slug
字段,您可以执行以下操作:
from django.forms import ModelForm
from myapp.models import Article
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ["pub_date", "headline", "content", "reporter", "slug"]
field_classes = {
"slug": MySlugFormField,
}
或:
from django.forms import ModelForm
from myapp.models import Article
def formfield_for_dbfield(db_field, **kwargs):
if db_field.name == "slug":
return MySlugFormField()
return db_field.formfield(**kwargs)
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = ["pub_date", "headline", "content", "reporter", "slug"]
formfield_callback = formfield_for_dbfield
最后,如果您想要完全控制一个字段(包括它的类型、验证器、必需等),您可以通过声明性地指定字段来实现,就像在常规的 Form
.
如果要指定字段的验证器,可以通过声明性地定义字段并设置其 validators
参数::
from django.forms import CharField, ModelForm
from myapp.models import Article
class ArticleForm(ModelForm):
slug = CharField(validators=[validate_slug])
class Meta:
model = Article
fields = ["pub_date", "headline", "content", "reporter", "slug"]
备注
当您显式地实例化这样的表单字段时,了解如何 ModelForm
规则的 Form
是相关的。
ModelForm
是规则的 Form
可以自动生成某些字段。自动生成的字段取决于 Meta
类,其中的字段已声明性定义。基本上, ModelForm
将 only 生成以下字段: 丢失的 从窗体,或者换句话说,未声明性定义的字段。
声明性定义的字段保持原样,因此对 Meta
属性,例如 widgets
, labels
, help_texts
或 error_messages
被忽略;这些仅适用于自动生成的字段。
同样,以声明方式定义的字段也不会像 max_length
或 required
来自相应的模型。如果要维护模型中指定的行为,则在声明表单字段时必须显式设置相关参数。
例如,如果 Article
模型如下:
class Article(models.Model):
headline = models.CharField(
max_length=200,
null=True,
blank=True,
help_text="Use puns liberally",
)
content = models.TextField()
您想对 headline
,同时保留 blank
和 help_text
指定的值,您可以定义 ArticleForm
这样地::
class ArticleForm(ModelForm):
headline = MyFormField(
max_length=200,
required=False,
help_text="Use puns liberally",
)
class Meta:
model = Article
fields = ["headline", "content"]
必须确保表单字段的类型可用于设置相应模型字段的内容。当它们不兼容时,您将得到 ValueError
因为没有进行隐式转换。
见 form field documentation 有关字段及其参数的详细信息。
默认情况下, ModelForm
不会本地化他们的数据。要为字段启用本地化,可以使用 localized_fields
属性 Meta
类。
>>> from django.forms import ModelForm
>>> from myapp.models import Author
>>> class AuthorForm(ModelForm):
... class Meta:
... model = Author
... localized_fields = ['birth_date']
如果 localized_fields
设置为特殊值 '__all__'
,所有字段都将本地化。
与基本表单一样,您可以扩展和重用 ModelForms
通过继承它们。如果您需要在父类上声明额外的字段或额外的方法,以便在从模型派生的许多表单中使用,这将非常有用。例如,使用以前的 ArticleForm
班级:
>>> class EnhancedArticleForm(ArticleForm):
... def clean_pub_date(self): ...
...
这将创建一个行为与 ArticleForm
除了有一些额外的验证和清理 pub_date
字段。
您还可以将父级的 Meta
内部类,如果要更改 Meta.fields
或 Meta.exclude
列表:
>>> class RestrictedArticleForm(EnhancedArticleForm):
... class Meta(ArticleForm.Meta):
... exclude = ["body"]
...
这将添加 EnhancedArticleForm
并修改原始文件 ArticleForm.Meta
删除一个字段。
不过,有几点需要注意。
标准的python名称解析规则适用。如果有多个基类声明 Meta
内部类,只使用第一个类。这意味着孩子的 Meta
,否则 Meta
第一个父母的,等等。
两者都有可能继承 Form
和 ModelForm
但同时,您必须确保 ModelForm
首先出现在MRO中。这是因为这些类依赖于不同的元类,并且一个类只能有一个元类。
可以声明性地删除 Field
通过将名称设置为 None
在子类上。
只能使用此技术从父类声明性定义的字段中选择退出;它不会阻止 ModelForm
生成默认字段的元类。要退出默认字段,请参见 选择要使用的字段 .
与常规表单一样,可以通过指定 initial
实例化窗体时使用。以这种方式提供的初始值将覆盖表单域中的初始值和附加模型实例中的值。例如:
>>> article = Article.objects.get(pk=1)
>>> article.headline
'My headline'
>>> form = ArticleForm(initial={"headline": "Initial headline"}, instance=article)
>>> form["headline"].value()
'Initial headline'
您可以使用独立函数从给定的模型创建表单 modelform_factory()
,而不是使用类定义。如果您没有太多要进行的定制,这可能会更方便:
>>> from django.forms import modelform_factory
>>> from myapp.models import Book
>>> BookForm = modelform_factory(Book, fields=["author", "title"])
这也可用于修改现有表单,例如,通过指定要用于给定域的小部件:
>>> from django.forms import Textarea
>>> Form = modelform_factory(Book, form=BookForm, widgets={"title": Textarea()})
可以使用指定要包含的字段 fields
和 exclude
关键字参数,或 ModelForm
内部的 Meta
类。请看 ModelForm
选择要使用的字段 文档。
..。或为特定字段启用本地化:
>>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=["birth_date"])
喜欢 regular formsets ,Django提供了两个增强的FormSet类,使使用Django模型更加方便。让我们重复使用 Author
来自上方的模型:
>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
vbl.使用 fields
将表单集限制为仅使用给定的字段。或者,您也可以采用“选择退出”的方法,指定要排除的字段:
>>> AuthorFormSet = modelformset_factory(Author, exclude=["birth_date"])
这将创建一个能够使用与 Author
模特。它的工作原理与常规表单集类似:
>>> formset = AuthorFormSet()
>>> print(formset)
<input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS"><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS"><input type="hidden" name="form-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS"><input type="hidden" name="form-MAX_NUM_FORMS" value="1000" id="id_form-MAX_NUM_FORMS">
<div><label for="id_form-0-name">Name:</label><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100"></div>
<div><label for="id_form-0-title">Title:</label><select name="form-0-title" id="id_form-0-title">
<option value="" selected>---------</option>
<option value="MR">Mr.</option>
<option value="MRS">Mrs.</option>
<option value="MS">Ms.</option>
</select><input type="hidden" name="form-0-id" id="id_form-0-id"></div>
备注
modelformset_factory()
用途 formset_factory()
生成表格。这意味着模型形式集是了解如何与特定模型交互的基本形式集的扩展。
备注
使用时 multi-table inheritance ,表单集工厂生成的表单将包含父链接字段(默认情况下 <parent_model_name>_ptr
)而不是 id
菲尔德。
默认情况下,从模型创建表单集时,表单集将使用包括模型中所有对象的查询集(例如, Author.objects.all()
)。方法可以重写此行为 queryset
论据:
>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith="O"))
或者,您可以创建一个子类来设置 self.queryset
在里面 __init__
::
from django.forms import BaseModelFormSet
from myapp.models import Author
class BaseAuthorFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.queryset = Author.objects.filter(name__startswith="O")
然后,通过您的 BaseAuthorFormSet
类绑定到工厂函数:
>>> AuthorFormSet = modelformset_factory(
... Author, fields=["name", "title"], formset=BaseAuthorFormSet
... )
如果要返回不包括 any 模型的现有实例,则可以指定空的QuerySet:
>>> AuthorFormSet(queryset=Author.objects.none())
默认情况下,当您使用 modelformset_factory
,将使用 modelform_factory()
. 通常,指定自定义模型表单很有用。例如,可以创建具有自定义验证的自定义模型表单:
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
fields = ["name", "title"]
def clean_name(self):
# custom validation for the name field
...
然后,将模型表单传递到工厂函数:
AuthorFormSet = modelformset_factory(Author, form=AuthorForm)
并不总是需要定义自定义模型表单。这个 modelformset_factory
函数有几个参数传递给 modelform_factory
,如下所述。
widgets
¶使用 widgets
参数,则可以指定值字典以自定义 ModelForm
特定字段的窗口小部件类。它的工作方式与 widgets
内页词典 Meta
A类 ModelForm
作品:
>>> AuthorFormSet = modelformset_factory(
... Author,
... fields=["name", "title"],
... widgets={"name": Textarea(attrs={"cols": 80, "rows": 20})},
... )
localized_fields
¶使用 localized_fields
参数,您可以为表单中的字段启用本地化。
>>> AuthorFormSet = modelformset_factory(
... Author, fields=['name', 'title', 'birth_date'],
... localized_fields=['birth_date'])
如果 localized_fields
设置为特殊值 '__all__'
,所有字段都将本地化。
与常规表单集一样,可以 specify initial data 通过指定 initial
实例化由返回的Model Formset类时的参数 modelformset_factory()
. 但是,对于模型表单集,初始值仅适用于附加表单,即那些未附加到现有模型实例的表单。如果 initial
超过了额外窗体的数目,多余的初始数据将被忽略。如果带有初始数据的额外表单没有被用户更改,它们将不会被验证或保存。
和A一样 ModelForm
,可以将数据保存为模型对象。这是用表格集完成的 save()
方法:
# Create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)
# Assuming all is valid, save the data.
>>> instances = formset.save()
这个 save()
方法返回已保存到数据库中的实例。如果给定实例的数据在绑定数据中没有更改,则该实例将不会保存到数据库中,也不会包含在返回值中。 (instances
,在上面的示例中)。
当表单中缺少字段时(例如,因为它们已被排除),这些字段将不会由 save()
方法。您可以找到有关此限制的更多信息,此限制也适用于 ModelForms
在 Selecting the fields to use .
通过 commit=False
要返回未保存的模型实例,请执行以下操作:
# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
... # do something with instance
... instance.save()
...
这使您能够在将实例保存到数据库之前将数据附加到实例。如果您的表单集包含 ManyToManyField
,您还需要调用 formset.save_m2m()
以确保正确保存多对多关系。
调用后 save()
,您的模型表单集将具有三个包含表单集更改的新属性:
与常规表单集一样,您可以使用 max_num
和 extra
参数到 modelformset_factory()
限制显示的额外表单数。
max_num
不阻止显示现有对象:
>>> Author.objects.order_by("name")
<QuerySet [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]>
>>> AuthorFormSet = modelformset_factory(Author, fields=["name"], max_num=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by("name"))
>>> [x.name for x in formset.get_queryset()]
['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman']
另外, extra=0
不会像您一样阻止创建新的模型实例 add additional forms with JavaScript 或发送额外的帖子数据。看见 阻止创建新对象 如何做到这一点。
如果值为 max_num
大于现有相关对象的数量,最多为 extra
只要表单总数不超过表单总数,就会将其他空白表单添加到表单集中 max_num
:
>>> AuthorFormSet = modelformset_factory(Author, fields=["name"], max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by("name"))
>>> for form in formset:
... print(form)
...
<div><label for="id_form-0-name">Name:</label><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100"><input type="hidden" name="form-0-id" value="1" id="id_form-0-id"></div>
<div><label for="id_form-1-name">Name:</label><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100"><input type="hidden" name="form-1-id" value="3" id="id_form-1-id"></div>
<div><label for="id_form-2-name">Name:</label><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100"><input type="hidden" name="form-2-id" value="2" id="id_form-2-id"></div>
<div><label for="id_form-3-name">Name:</label><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100"><input type="hidden" name="form-3-id" id="id_form-3-id"></div>
A max_num
价值 None
(默认值)对显示的表单数(1000)设置了一个上限。实际上,这相当于没有限制。
使用 edit_only
参数,则可以阻止创建任何新对象:
>>> AuthorFormSet = modelformset_factory(
... Author,
... fields=["name", "title"],
... edit_only=True,
... )
在这里,表单集将仅编辑现有的 Author
实例。不会创建或编辑任何其他对象。
模型表单集与表单集非常相似。假设我们要呈现一个要编辑的表单集 Author
模型实例:
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
if request.method == "POST":
formset = AuthorFormSet(request.POST, request.FILES)
if formset.is_valid():
formset.save()
# do something.
else:
formset = AuthorFormSet()
return render(request, "manage_authors.html", {"formset": formset})
如您所见,模型表单集的视图逻辑与“普通”表单集的视图逻辑没有显著不同。唯一的区别是我们称之为 formset.save()
将数据保存到数据库中。(上述内容在 在表单集中保存对象 )
clean()
在一 ModelFormSet
¶就像 ModelForms
,默认情况下 clean()
A方法 ModelFormSet
将验证表单集中没有任何项违反模型上的唯一约束(或者 unique
, unique_together
或 unique_for_date|month|year
)如果要重写 clean()
A方法 ModelFormSet
并且维护这个验证,您必须调用父类的 clean
方法:
from django.forms import BaseModelFormSet
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super().clean()
# example custom validation across forms in the formset
for form in self.forms:
# your custom formset validation
...
另外请注意,当您到达此步骤时,已经为每个 Form
. 修改中的值 form.cleaned_data
不足以影响保存的值。如果要修改 ModelFormSet.clean()
你必须修改 form.instance
::
from django.forms import BaseModelFormSet
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super().clean()
for form in self.forms:
name = form.cleaned_data["name"].upper()
form.cleaned_data["name"] = name
# update the instance value.
form.instance.name = name
如前所述,您可以重写模型表单集使用的默认查询集:
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author
def manage_authors(request):
AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
queryset = Author.objects.filter(name__startswith="O")
if request.method == "POST":
formset = AuthorFormSet(
request.POST,
request.FILES,
queryset=queryset,
)
if formset.is_valid():
formset.save()
# Do something.
else:
formset = AuthorFormSet(queryset=queryset)
return render(request, "manage_authors.html", {"formset": formset})
注意我们通过了 queryset
两个方面的论点 POST
和 GET
本例中的案例。
在Django模板中呈现表单集有三种方法。
首先,您可以让表单集完成大部分工作:
<form method="post">
{{ formset }}
</form>
其次,您可以手动呈现表单集,但让表单自行处理:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
</form>
当您自己手动呈现表单时,请确保按照上面所示呈现管理表单。见 management form documentation .
第三,您可以手动呈现每个字段:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{% for field in form %}
{{ field.label_tag }} {{ field }}
{% endfor %}
{% endfor %}
</form>
如果您选择使用这第三种方法,并且不使用 {% for %}
循环,则需要呈现主键字段。例如,如果要呈现 name
和 age
模型的字段:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form.id }}
<ul>
<li>{{ form.name }}</li>
<li>{{ form.age }}</li>
</ul>
{% endfor %}
</form>
注意我们需要如何显式地呈现 {{{{ form.id }}}}
. 这样可以确保在 POST
箱子,会正常工作。(此示例假定一个名为 id
. 如果您已经显式定义了自己的主键,但没有调用 id
,确保渲染。)
内联表单集是模型表单集顶部的一个小抽象层。这些简化了通过外键处理相关对象的情况。假设您有这两种型号:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
如果要创建允许您编辑属于特定作者的图书的表单集,可以执行以下操作:
>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book, fields=["title"])
>>> author = Author.objects.get(name="Mike Royko")
>>> formset = BookFormSet(instance=author)
BookFormSet
的 prefix 是 'book_set'
(<model name>_set
)如果 Book
的 ForeignKey
到 Author
有一个 related_name
,这是用来代替的。
备注
inlineformset_factory()
使用 modelformset_factory()
标记 can_delete=True
.
InlineFormSet
¶重写上的方法时 InlineFormSet
,您应该子类 BaseInlineFormSet
而不是 BaseModelFormSet
.
例如,如果要重写 clean()
::
from django.forms import BaseInlineFormSet
class CustomInlineFormSet(BaseInlineFormSet):
def clean(self):
super().clean()
# example custom validation across forms in the formset
for form in self.forms:
# your custom formset validation
...
也见 压倒一切 clean() 在一 ModelFormSet .
然后,在创建内联形式集时,传入可选参数 formset
:
>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(
... Author, Book, fields=["title"], formset=CustomInlineFormSet
... )
>>> author = Author.objects.get(name="Mike Royko")
>>> formset = BookFormSet(instance=author)
如果模型包含同一模型的多个外键,则需要使用 fk_name
. 例如,考虑以下模型:
class Friendship(models.Model):
from_friend = models.ForeignKey(
Friend,
on_delete=models.CASCADE,
related_name="from_friends",
)
to_friend = models.ForeignKey(
Friend,
on_delete=models.CASCADE,
related_name="friends",
)
length_in_months = models.IntegerField()
要解决此问题,您可以使用 fk_name
至 inlineformset_factory()
:
>>> FriendshipFormSet = inlineformset_factory(
... Friend, Friendship, fk_name="from_friend", fields=["to_friend", "length_in_months"]
... )
您可能希望提供一个视图,允许用户编辑模型的相关对象。你可以这样做:
def manage_books(request, author_id):
author = Author.objects.get(pk=author_id)
BookInlineFormSet = inlineformset_factory(Author, Book, fields=["title"])
if request.method == "POST":
formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
if formset.is_valid():
formset.save()
# Do something. Should generally end with a redirect. For example:
return HttpResponseRedirect(author.get_absolute_url())
else:
formset = BookInlineFormSet(instance=author)
return render(request, "manage_books.html", {"formset": formset})
注意我们是怎么通过的 instance
在两者中 POST
和 GET
病例。
inlineformset_factory
使用 modelformset_factory
并将其大部分参数传递给 modelformset_factory
. 这意味着您可以使用 widgets
参数的传递方式与 modelformset_factory
. 见 Specifying widgets to use in the form with widgets 上面。
7月 22, 2024