ModelForm
¶如果您正在构建一个数据库驱动的应用程序,那么您很有可能会用到与Django模型密切相关的表单。例如,您可能有一个 BlogComment
模型,并且您想创建一个让用户提交评论的表单。在这种情况下,在表单中定义字段类型是多余的,因为您已经在模型中定义了字段。
因此,Django 提供了一个辅助类让你可以从一个 Django 模型创建一个 Form
类。
例如:
>>> 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
属性中指定的顺序为每个指定的模型字段设置一个表单字段。
每个模型字段都有一个对应的默认表单字段。例如,模型中的 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
值)。最后,请注意,您可以覆盖给定模型字段对应的表单字段。参见下文 覆盖默认字段 。
思考下下面这组模型:
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.full_clean()
)在表单验证步骤中紧随表单的 clean()
方法被调用后触发。
警告
Clean 过程会以各种方式去修改传递给 ModelForm
构造方法的模型实例。例如,模型上的所有日期字段都将转换为实际的日期对象。验证失败可能会使底层模型实例处于不一致状态,因此不推荐对其重用。
您可以重写模型表单上的 clean()
方法来提供额外的验证,方式和普通的表单一样。
访问模型对象对应的表单实例包含一个 instance
属性,让它可以访问对应的模型实例。
警告
ModelForm.clean()
方法设置了一个标识符,使程序在 模型验证 这步去验证标记为 unique
、 unique_together
或 unique_for_date|month|year
的模型字段的唯一性。
如果您想覆盖 clean()
方法并保持当前的验证,您必须调用父类的 clean()
方法。
作为验证过程的一部分, ModelForm
将调用模型上与表单字段对应的每个字段的 clean()
方法。如果您排除了一些模型字段,则验证将不会在这些字段上运行。更多有关字段clean及验证是如何工作的内容,请参阅 表单验证 文档。
模型的 clean()
方法会在所有唯一性检查之前被调用。有关模型 clean()
钩子的更多信息,请参阅 验证对象 。
error_messages
的注意事项¶在 表单字段
级别或者 表单 Meta 级别定义的错误信息优先级总是高于在 模型字段
级别定义的。
在 模型字段
上定义的错误信息只有在 模型验证 步骤引发 ValidationError
时才会使用,并且没有在表单级定义相应的错误信息。
您可以通过添加 NON_FIELD_ERRORS
键到 ModelForm
内部的 Meta
类的 error_messages
中来覆盖模型验证引发的 NON_FIELD_ERRORS
错误信息。
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()
请注意,如果表单 尚未验证 ,调用 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()
。如果要在保存对象之前对对象执行自定义操作,或者要使用其中一个专用的 模型保存选项 ,这很有用。 commit
的值默认为 True
。
另一个使用 commit=False
的作用,您可以在模型与另一个模型有多对多关系的时候看到。如果您的模型具有多对多关系,并且在保存表单时指定了 commit=False
,Django无法立即保存多对多关系的表单数据。这是因为实例的多对多数据只有实例在数据库中存在时才能保存。
要解决这个问题,Django会在您每次使用 commit=False
保存表单时,向 ModelForm
子类添加一个 save_m2m()
方法。在您手动保存表单生成的实例后,可以调用 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(commit=False)
的时候才需要调用 save_m2m()
。当您在表单上使用普通的 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()
方法之外,ModelForm
与普通的表单工作方式一样。例如,用 is_valid()
方法来检查合法性,用 is_multipart()
方法来确定表单是否需要multipart文件上传(之后是否必须将 request.FILES
传递给表单),等等。更多相关信息,请参阅 Binding uploaded files to a form 。
强烈建议您使用 fields
属性来显式设置所有应在表单中编辑的字段。如果不这样做,当一张表单不慎允许用户设置某些字段,尤其是在将新字段添加到模型中时,很容易导致安全问题。根据表单渲染方式的不同,甚至可能不会在网页上显示问题。
另一种方法是自动包含所有字段,其他放入黑名单。据了解,这种基本方法不太安全,并已导致大型网站上出现严重漏洞(例如, GitHub )。
但是,有两种简单的方法保证你不会出现这些安全问题:
将 fields
属性设置为特殊值 '__all__'
以表明需要使用模型中的所有字段。例如:
from django.forms import ModelForm
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = '__all__'
将 ModelForm
中Meta类的 exclude
属性设置为表单中需要排除的字段列表。
例如:
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
exclude = ['title']
由于 Author
模型有三个字段 name
、 title
和 birth_date
,上例的结果是字段 name
和 birth_date
会呈现在表单中。
不管使用哪一种,字段会按模型中定义的顺序在表单中出现, ManyToManyField
会排在最后。
另外,Django有个规则:如果您在模型字段中定义了 editable=False
, *任何*使用 ModelForm
给该模型创建的表单都不会包含这个字段。
注解
任何没在上面逻辑中包含的表单字段都会不被表单的 save()
方法处理。另外,如果手动将排除的字段添加回表单,它们也不会被模型实例初始化。
Django会阻止任何尝试保存不完整模型的行为,所以如果模型不允许缺省的字段为空,并且没有为该字段提供缺省值,那么任何尝试用这种字段的 ModelForm
的 save()
方法都会失败。为了避免这种情况,您必须使用初始值实例化您模型中缺省但又必填的字段:
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()
更多关于使用 save(commit=False)
的详细内容,请参阅 保存表单章节 。
之前在 字段类型 表格中介绍的默认字段类型都是相对合适的。如果您的模型中有一个 DateField
,您可能希望在表单中将它展示为 DateField
。但 ModelForm
可以让您灵活地改变给定模型的表单字段。
要为字段指定自定义组件,请使用内部 Meta
类的 widgets
属性。它应该是一个映射字段名到组建类或组件实例的字典。
例如,如果您希望 Author
的 name
属性的 CharField
由 <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
)。
同样的,如果您想进一步自定义一个字段,还可以指定内部Meta类的 labels
、 help_texts
和 error_messages
属性。
例如您想自定义 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
来自定义表单实例化的字段类型:
例如,如果您想对 slug
字段使用 MySlugFormField
,您可以这样做:
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,
}
最后,如果您想完全控制一个字段(包括它的类型,验证,必填等等),您可以通过声明指定字段来做到这一点,就像在一个普通的 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
仅会 自动生成表单中 缺失 的字段,或者说,没被明确定义的字段。
声明定义的字段会保持原样,因此,任何对 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
。
更多有关字段及其参数的内容,请参阅 表单字段文档 。
默认情况下, ModelForm
中的字段不会本地化他们的数据。要为字段启用本地化,您可以在 Meta
类中使用 localized_fields
属性。
>>> 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
字段会有一些额外的验证和cleaning。
如果要更改 Meta.fields
或 Meta.exclude
列表,您也可以继承父类的内部 Meta
类:
>>> class RestrictedArticleForm(EnhancedArticleForm):
... class Meta(ArticleForm.Meta):
... exclude = ('body',)
这相比 EnhancedArticleForm
增加了额外方法,并修改了原始的 ArticleForm.Meta
以删除一个字段。
然而,有几项需要注意。
适用于普通的Python名称解析规则。如果您有多个声明 Meta
内部类的基类,就是说如果声明了子类的 Meta
就会使用它,否则就用第一个父类的 Meta
。
可以同时继承 Form
和 ModelForm
,但是,您必须确保 ModelForm
在MRO中出现在首位。这是因为这些类依赖于不同的元类,而一个类只能有一个元类。
通过在子类上将名称设置为 None
,可以声明性地移除从父类继承的 Field
。
您只能使用这种技术排除父类中声明定义的字段;它不会阻止 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",))
models.
BaseModelFormSet
¶和 普通表单集 一样,Django提供了几个增强的formset类,可以很方便地配合Django模型使用。让我们重用下上面的 Author
模型:
>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
使用 fields
参数限制formset仅使用给定的字段。或者,您可以使用排除法,指定排除哪些字段:
>>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',))
这将创建一个能够处理与 Author
模型相关数据的formset。它运行起来就像一个普通的formset:
>>> 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-MAX_NUM_FORMS" id="id_form-MAX_NUM_FORMS">
<tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100"></td></tr>
<tr><th><label for="id_form-0-title">Title:</label></th><td><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"></td></tr>
注解
modelformset_factory()
使用 formset_factory()
来生成表单集。这意味着模型formset只是一个知道如何与指定模型交互的普通formset的扩展。
默认情况下,当您创建一个模型ormset时,formset将使用一个包含模型中所有对象(例如 Author.objects.all()
)的查询集。你可以通过使用 queryset
参数来覆盖这一行为:
>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
或者,您可以创建一个子类,然后在 __init__
中设置 self.queryset
:
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)
如果您想返回一个不包含 任何 已存在模型实例的formset,您可以指定一个空的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
指定字段自定义部件。这与 ModelForm
内部 Meta
类中 widgets
字典的工作方式一样:
>>> 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__'
,则所有字段都将被本地化。
As with regular formsets, it's possible to specify initial data for forms in the formset by specifying an initial
parameter when instantiating the model formset class returned by
modelformset_factory()
. However, with model
formsets, the initial values only apply to extra forms, those that aren't
attached to an existing model instance. If the length of initial
exceeds
the number of extra forms, the excess initial data is ignored. If the extra
forms with initial data aren't changed by the user, they won't be validated or
saved.
As with a ModelForm
, you can save the data as a model object. This is done
with the formset's save()
method:
# Create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)
# Assuming all is valid, save the data.
>>> instances = formset.save()
The save()
method returns the instances that have been saved to the
database. If a given instance's data didn't change in the bound data, the
instance won't be saved to the database and won't be included in the return
value (instances
, in the above example).
When fields are missing from the form (for example because they have been
excluded), these fields will not be set by the save()
method. You can find
more information about this restriction, which also holds for regular
ModelForms
, in Selecting the fields to use.
Pass commit=False
to return the unsaved model instances:
# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
... # do something with instance
... instance.save()
This gives you the ability to attach data to the instances before saving them
to the database. If your formset contains a ManyToManyField
, you'll also
need to call formset.save_m2m()
to ensure the many-to-many relationships
are saved properly.
After calling save()
, your model formset will have three new attributes
containing the formset's changes:
models.BaseModelFormSet.
changed_objects
¶models.BaseModelFormSet.
deleted_objects
¶models.BaseModelFormSet.
new_objects
¶As with regular formsets, you can use the max_num
and extra
parameters
to modelformset_factory()
to limit the number of
extra forms displayed.
max_num
does not prevent existing objects from being displayed:
>>> 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']
Also, extra=0
doesn't prevent creation of new model instances as you can
add additional forms with JavaScript
or just send additional POST data. Formsets don't yet provide functionality for an "edit only" view that
prevents creation of new instances.
If the value of max_num
is greater than the number of existing related
objects, up to extra
additional blank forms will be added to the formset,
so long as the total number of forms does not exceed 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.as_table())
<tr><th><label for="id_form-0-name">Name:</label></th><td><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"></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th><td><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"></td></tr>
<tr><th><label for="id_form-2-name">Name:</label></th><td><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"></td></tr>
<tr><th><label for="id_form-3-name">Name:</label></th><td><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"></td></tr>
max_num
的值 None
(默认值),它限制最多显示(1000)张表单,其实这相当于没有限制。
Model formsets are very similar to formsets. Let's say we want to present a
formset to edit Author
model instances:
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})
As you can see, the view logic of a model formset isn't drastically different
than that of a "normal" formset. The only difference is that we call
formset.save()
to save the data into the database. (This was described
above, in Saving objects in the formset.)
clean()
on a ModelFormSet
¶Just like with ModelForms
, by default the clean()
method of a
ModelFormSet
will validate that none of the items in the formset violate
the unique constraints on your model (either unique
, unique_together
or
unique_for_date|month|year
). If you want to override the clean()
method
on a ModelFormSet
and maintain this validation, you must call the parent
class's clean
method:
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
...
Also note that by the time you reach this step, individual model instances
have already been created for each Form
. Modifying a value in
form.cleaned_data
is not sufficient to affect the saved value. If you wish
to modify a value in ModelFormSet.clean()
you must modify
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
As stated earlier, you can override the default queryset used by the model formset:
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,
queryset=Author.objects.filter(name__startswith='O'),
)
if formset.is_valid():
formset.save()
# Do something.
else:
formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
return render(request, 'manage_authors.html', {'formset': formset})
Note that we pass the queryset
argument in both the POST
and GET
cases in this example.
There are three ways to render a formset in a Django template.
First, you can let the formset do most of the work:
<form method="post">
{{ formset }}
</form>
Second, you can manually render the formset, but let the form deal with itself:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
</form>
When you manually render the forms yourself, be sure to render the management form as shown above. See the management form documentation.
Third, you can manually render each field:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{% for field in form %}
{{ field.label_tag }} {{ field }}
{% endfor %}
{% endfor %}
</form>
If you opt to use this third method and you don't iterate over the fields with
a {% for %}
loop, you'll need to render the primary key field. For example,
if you were rendering the name
and age
fields of a model:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{{ form.id }}
<ul>
<li>{{ form.name }}</li>
<li>{{ form.age }}</li>
</ul>
{% endfor %}
</form>
Notice how we need to explicitly render {{ form.id }}
. This ensures that
the model formset, in the POST
case, will work correctly. (This example
assumes a primary key named id
. If you've explicitly defined your own
primary key that isn't called id
, make sure it gets rendered.)
models.
BaseInlineFormSet
¶Inline formsets is a small abstraction layer on top of model formsets. These simplify the case of working with related objects via a foreign key. Suppose you have these two models:
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)
If you want to create a formset that allows you to edit books belonging to a particular author, you could do this:
>>> 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
's prefix is 'book_set'
(<model name>_set
). If Book
's ForeignKey
to Author
has a
related_name
, that's used instead.
注解
inlineformset_factory()
uses
modelformset_factory()
and marks
can_delete=True
.
InlineFormSet
¶When overriding methods on InlineFormSet
, you should subclass
BaseInlineFormSet
rather than
BaseModelFormSet
.
For example, if you want to override 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
...
See also Overriding clean() on a ModelFormSet.
Then when you create your inline formset, pass in the optional argument
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)
If your model contains more than one foreign key to the same model, you'll
need to resolve the ambiguity manually using fk_name
. For example, consider
the following model:
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()
To resolve this, you can use fk_name
to
inlineformset_factory()
:
>>> FriendshipFormSet = inlineformset_factory(Friend, Friendship, fk_name='from_friend',
... fields=('to_friend', 'length_in_months'))
You may want to provide a view that allows a user to edit the related objects of a model. Here's how you can do that:
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})
Notice how we pass instance
in both the POST
and GET
cases.
inlineformset_factory
uses modelformset_factory
and passes most
of its arguments to modelformset_factory
. This means you can use
the widgets
parameter in much the same way as passing it to
modelformset_factory
. See Specifying widgets to use in the form with
widgets above.
1月 11, 2019