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
。 模型中的MultipleChoiceField
字段会表现成ManyToManyField
字段。 下面是一个完整的列表:
模型字段 | 表单域 |
---|---|
AutoField |
没有以形式表示 |
BigAutoField |
没有以形式表示 |
BigIntegerField |
IntegerField with
min_value set to -9223372036854775808
and max_value set to 9223372036854775807. |
BooleanField |
BooleanField |
CharField |
CharField with
max_length set to the model field’s
max_length and
empty_value
set to None if null=True . |
CommaSeparatedIntegerField |
CharField |
DateField |
的DateField |
DateTimeField |
DateTimeField字段 |
DecimalField |
DecimalField |
EmailField |
EmailField |
FileField |
的FileField |
FilePathField |
FilePathField |
FloatField |
FloatField |
ForeignKey |
ModelChoiceField (见下文) |
ImageField |
ImageField |
IntegerField |
IntegerField |
IPAddressField |
IPAddressField |
GenericIPAddressField |
GenericIPAddressField |
ManyToManyField |
ModelMultipleChoiceField (见下文) |
NullBooleanField |
NullBooleanField |
PositiveIntegerField |
IntegerField |
PositiveSmallIntegerField |
IntegerField |
SlugField |
SlugField |
SmallIntegerField |
IntegerField |
TextField |
CharField with widget=forms.Textarea |
TimeField |
TimeField |
URLField |
URLField |
可能如你所料,ManyToManyField
和 ForeignKey
字段类型属于特殊情况:
QuerySet
表示成ChoiceField
,它是一个django.forms.ModelChoiceField
,其选项是模型的ForeignKey
。QuerySet
表示成MultipleChoiceField
,它是一个django.forms.ModelMultipleChoiceField
,其选项是模型的ManyToManyField
。此外,生成的每个表单字段都有以下属性集:
否则,required=True
。verbose_name
设置为模型字段的label
,并将第一个字母大写。help_text
设置为模型字段的help_text
。
选项通常会包含空选项,并且会默认选择。 如果字段是必选的,它会强制用户选择一个选项。 如果模型字段的default
且具有一个显示的default
值,将不会包含空选项(初始将选择blank=False
值)。最后,请注意你可以为给定的模型字段重新指定表单字段。 参见下文覆盖默认的字段。
考虑下面的模型:
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): # __unicode__ on Python 2
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()
显式调用,尽管在实际应用中你将很少使用后一种方法。
clean()
的验证(Model.full_clean()
)在表单验证这一步的内部触发,紧跟在表单的Model
方法调用之后。
警告
Clean 过程会以各种方式修改传递给ModelForm
构造函数的模型实例。 例如,模型的日期字段将转换成日期对象。 验证失败可能导致模型实例处于不一致的状态,所以不建议重新使用它。
可以重写模型表单的clean()
来提供额外的验证,方法和普通的表单一样。
模型表单实例包含一个instance
属性,表示与它绑定的模型实例。
警告
unique_together
方法设置一个标识符, 使得model
validation 这一步验证标记为unique
、 unique_for_date|month|year
或ModelForm.clean()
的模型字段的唯一性。
如果你需要覆盖clean()
方法并维持这个验证行为,你必须调用父类的clean()
方法。
作为验证过程的一部分,clean()
将调用与表单字段对应的每个模型字段的ModelForm
方法。
如果你已经排除某些模型字段,这些字段不会运行验证。 关于字段clean 和验证是如何工作的,参见form validation的文档。
模型的clean()
方法在任何唯一性检查之前调用。 关于模型clean()
钩子的更多信息,参见Validating objects 。
error_messages
的注意事项¶form field
级别或form Meta级别的错误信息永远比model field
级别的错误信息优先。
model fields
的错误信息只用于model validation步骤引发ValidationError
的时候,且不会有对应的表单级别的错误信息。
你可以根据模型验证引发的Meta
覆盖错误信息,方法是添加 NON_FIELD_ERRORS
键到ModelForm
内联error_messages
类的NON_FIELD_ERRORS
字典:
from django.forms import ModelForm
from django.core.exceptions import NON_FIELD_ERRORS
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,form.errors
调用将通过检查save()
来进行验证。 如果表单中的数据不合法,将引发True
—— 例如,如果form.errors
为ValueError
。
如果表单数据中没有可选字段,则生成的模型实例使用模型字段default
(如果有)。 This behavior doesn’t apply to fields that use
CheckboxInput
,
CheckboxSelectMultiple
, or
SelectMultiple
(or any custom widget whose
value_omitted_from_data()
method always returns
False
) since an unchecked checkbox and unselected <select multiple>
don’t appear in the data of an HTML form submission. 如果您正在设计一个API并且希望使用这些小部件之一的字段的缺省回退行为,请使用自定义表单字段或小部件。
较旧的版本没有CheckboxInput
的例外,这意味着如果这是模型字段默认值,则未选中的复选框将接收到True
的值。
此save()方法接受一个可选的关键字为commit的参数,commit的取值为True或者False。 如果
commit=False
时save()
,那么它将返回一个还没有保存到数据库的对象。 这种情况下,你需要调用返回的模型实例的save()
。 如果你想在保存之前自定义一些处理,或者你想使用特定的model saving options,可以这样使用。 True
默认为commit
。
使用commit=False
的另外一个副作用是在模型具有多对多关系的时候。 如果模型具有多对多关系而且当你保存表单时指定commit=False
,Django 不会立即为多对多关系保存表单数据。 这是因为只有实例在数据库中存在时才可以保存实例的多对多数据。
为了解决这个问题,每当你使用ModelForm
保存表单时,Django 将添加一个save_m2m()
方法到你的commit=False
子类。 在你手工保存由表单生成的实例之后,你可以调用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()
除了forms
和ModelForm
方法之外,save_m2m()
与其它save()
的工作方式完全一样。 例如,request.FILES
用于检查合法性,is_multipart()
方法用于决定表单是否需要multipart 的文件上传(以及这之后is_valid()
是否必须必须传递给表单)等等。 更多信息,参见Binding uploaded files to a form。
强烈建议你使用fields
属性显式设置所有将要在表单中编辑的字段。 如果不这样做,当表单不小心允许用户设置某些特定的字段,特别是有的字段添加到模型中的时候,将很容易导致安全问题。 这些问题可能在网页上根本看不出来,它与表单的渲染方式有关。
另外一种方式是自动包含所有的字段,或者排除某些字段。 这种基本方式的安全性要差很多,而且已经导致大型的网站受到严重的利用(例如 GitHub)。
然而,有两种简单的方法保证你不会出现这些安全问题:
设置'__all__'
属性为特殊的值fields
以表示需要使用模型的所有字段。 像这样:
from django.forms import ModelForm
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = '__all__'
设置Meta
内联的ModelForm
类的exclude
属性为一个要从表单中排除的字段的列表。
像这样:
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
exclude = ['title']
因为Author
模型有3个字段name
、birth_date
和 birth_date
,上面的例子会让title
和 name
出现在表单中。
如果使用上面两种方法,表单中字段出现的顺序将和字段在模型中定义的顺序一致,其中ManyToManyField
出现在最后。
另外,Django 还将使用以下规则:如果设置模型字段的ModelForm
,那么使用editable=False
从该模型创建的任何表单都不会包含该字段。
注
不会被上述逻辑包含进表单中的字段将不会被表单的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
属性可以指定一个字段的自定义Widget。 它是映射字段名到Widget 类或实例的一个字典。
例如,<textarea>
的Author
属性为name
,如果你希望它表示成一个CharField
而不是默认的<input type="text">
,你可以覆盖字段默认的Widget:
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}),
}
不管是Widget 实例(Textarea
)还是Widget 类(Textarea(...)
),widgets
字典都可以接收。
类似地,如果你希望进一步自定义字段,你可以指定内部类Meta
的error_messages
、help_texts
和labels
。
例如,如果你希望自定义name
字段所有面向用户的字符串:
from django.utils.translation import ugettext_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 ModelForm, CharField
from myapp.models import Article
class ArticleForm(ModelForm):
slug = CharField(validators=[validate_slug])
class Meta:
model = Article
fields = ['pub_date', 'headline', 'content', 'reporter', 'slug']
注
当你像这样显式实例化表单字段时,需要理解Form
和普通的ModelForm
的关系是怎样的。
Form
就是可以自动生产相应字段的ModelForm
. 自动生成哪些字段取决于Meta
类的fields属性和在该ModelForm中显示声明的字段。 ModelForm
基本上只 生成表单中没有的字段,换句话讲就是没有显式定义的字段。
Fields defined declaratively are left as-is, therefore any customizations
made to Meta
attributes such as widgets
, labels
, help_texts
,
or error_messages
are ignored; these only apply to fields that are
generated automatically.
类似地,显式定义的字段不会从对应的模型中获取属性,例如 required
或max_length
。 如果你希望保持模型中指定的行为,你必须设置在声明表单字段时显式设置相关的参数。
例如,如果Article
模型像下面这样:
class Article(models.Model):
headline = models.CharField(
max_length=200,
null=True,
blank=True,
help_text='Use puns liberally',
)
content = models.TextField()
而你想为ArticleForm
做一些自定义的验证,在保持help_text
和blank
值的同时,你必须这样定义headline
:
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
中的字段不会本地化它们的数据。 你可以使用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
来扩展和重用他们。 当你的form是通过models生成的,而且需要在父类的基础上声明额外的field和method,这种继承是方便的。 例如,使用以前的ArticleForm
类:
>>> class EnhancedArticleForm(ArticleForm):
... def clean_pub_date(self):
... ...
以上创建了一个与 pub_date
非常类似的form,除了一些额外的验证和ArticleForm
的cleaning
如果要更改Meta.fields
或Meta.exclude
列表,您还可以将父类的Meta
子类子类化:
>>> class RestrictedArticleForm(EnhancedArticleForm):
... class Meta(ArticleForm.Meta):
... exclude = ('body',)
上例从父类ArticleForm.Meta
继承后增加了额外的方法,并修改了 EnhancedArticleForm
排除了一个字段
当然,有一些注意事项
应用正常的Python名称解析规则。 如果你有多个基类声明一个Meta
内部类,只会使用第一个。 这意味着孩子的Meta
(如果存在),否则第一个父母的Meta
等。
它可以同时继承Form
和ModelForm
,但是,必须确保Form
首先出现在MRO中。 这是因为这些类依赖于不同的元类,而一个类只能有一个元类。
可以通过在子类上将名称设置为None
,声明性地删除从父类继承的Field
。
您只能使用此技术从由父类声明性定义的字段中选择退出;它不会阻止ModelForm
元类生成默认字段。 要退出默认字段,请参阅Selecting the fields to use。
作为一个有参数的表单, 在实例化一个表单时可以通过指定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"))
这个函数还能对已有的表单类做简单的修改,比如,对给出的字段指定 widgets :
>>> from django.forms import Textarea
>>> Form = modelform_factory(Book, form=BookForm,
... widgets={"title": Textarea()})
表单包含的字段可以用 Meta
或ModelForm
关键字参数说明,或者用exclude
内部fields
类的相应属性说明。 请看 ModelForm
文档: Selecting the fields to use。
...或启用特定字段的本地化:
>>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=("birth_date",))
models.
BaseModelFormSet
¶与regular formsets一样, 它是Django提供的几个有力的表单集类来简化模型操作。 让我们继续使用上面的Author
模型:
>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
使用 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-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()
生成表单集, 这意味着模型表单集仅仅是扩展基本表单集,使其能处理模型的信息。
默认的, 如果你使用model生成formset,formset会使用一个包含模型全部对象的queryset(例如:Author.objects.all()
). 你可以使用queryset
参数重写这一行为:
>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))
或者,你可以创建子类设置 __init__
in self.queryset
:
from django.forms import BaseModelFormSet
from myapp.models import Author
class BaseAuthorFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super(BaseAuthorFormSet, self).__init__(*args, **kwargs)
self.queryset = Author.objects.filter(name__startswith='O')
然后,将BaseAuthorFormSet
类传给modelformset_factory函数:
>>> AuthorFormSet = modelformset_factory(
... Author, fields=('name', 'title'), formset=BaseAuthorFormSet)
如果想返回不包含任何已存在模型实例的表单集,可以指定一个空的查询集(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)
并不总是需要自定义一个模型表单, modelform_factory
函数有几个参数,可以传给modelformset_factory
,他们的说明如下:
widgets
¶的形式指定要使用的小部件使用ModelForm
参数,可以用字典值自定义widgets
列出字段的widget类。 这与 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',))
如果'__all__'
设置为localized_fields
这个特殊的值,所有的字段都将本地化。
与普通表单集一样,modelformset_factory()
能返回初始化的模型表单集,initial
参数能为表单集的中表单specify initial data 。 但是,在模型表单集中,初始数据仅应用在增加的表单中,不会应用到已存在的模型实例。 如果用户没有更改新增加表单中的初始数据,那他们也不会被校验和保存。
做为 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
。
传递commit=False
返回未保存的模型实例:
# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
... # do something with instance
... instance.save()
这使您能够在将数据保存到数据库之前将数据附加到实例。 如果您的表单集包含formset.save_m2m()
,您还需要调用ManyToManyField
,以确保多对多关系正确保存。
调用save()
之后,您的模型formset将有三个包含formset更改的新属性:
models.BaseModelFormSet。
changed_objects T0> ¶ T1>
models.BaseModelFormSet。
deleted_objects T0> ¶ T1>
models.BaseModelFormSet。
new_objects T0> ¶ T1>
与普通表单集一样,你可以用在modelformset_factory()
中使用 extra
和 max_num
参数,来控制额外表单的显示数量。
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或仅发送其他POST数据。 对于禁止创建新实例的“仅编辑”视图,Formsets 尚未提供功能。
如果 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.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>
None
值为f max_num
(缺省)设置一个较高的限制可显示1000个表单。 实际上相当于没有限制。
模型表单集与表单集十分类似, 假设我们想要提供一个表单集来编辑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()
将数据保存到数据库中。 (上面已经描述了在保存窗体集中的对象。)Saving objects in the formset
ModelFormSet
上覆盖clean()
¶与ModelForms
一样,默认情况下,unique_together
的unique_for_date|month|year
方法将验证formset中没有项目违反唯一约束(unique
,ModelFormSet
或clean()
)。 如果要覆盖clean
上的ModelFormSet
方法并维护此验证,则必须调用父类的clean()
方法:
from django.forms import BaseModelFormSet
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super(MyModelFormSet, self).clean()
# example custom validation across forms in the formset
for form in self.forms:
# your custom formset validation
...
另请注意,到达此步骤时,已为每个Form
创建了各个模型实例。 修改form.cleaned_data
中的值不足以影响保存的值。 如果您希望修改form.instance
中的值,则必须修改ModelFormSet.clean()
:
from django.forms import BaseModelFormSet
class MyModelFormSet(BaseModelFormSet):
def clean(self):
super(MyModelFormSet, self).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
如前所述,您可以覆盖模型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})
请注意,我们在此示例中的GET
和POST
中传递queryset
参数。
在Django模板中有三种方式来渲染表单集。
第一种方式,你可以让表单集完成大部分的工作
<form method="post" action="">
{{ formset }}
</form>
其次,你可以手动渲染formset,但让表单处理自己:
<form method="post" action="">
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
</form>
当您自己手动呈现表单时,请确保呈现如上所示的管理表单。 请参阅management form documentation。
第三,您可以手动呈现每个字段:
<form method="post" action="">
{{ formset.management_form }}
{% for form in formset %}
{% for field in form %}
{{ field.label_tag }} {{ field }}
{% endfor %}
{% endfor %}
</form>
如果您选择使用此第三种方法,并且不对{% for %}
loop,你需要渲染主键字段。 例如,如果您要渲染模型的age
和name
字段:
<form method="post" action="">
{{ 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
),请确保其呈现)。
楷模。
BaseInlineFormSet T0> ¶ T1>
内联formets是模型formets上的一个小的抽象层。 这些简化了通过外键处理相关对象的情况。 假设你有这两个模型:
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)
注
inlineformset_factory()
使用modelformset_factory()
并标记为can_delete=True
。
InlineFormSet
¶中的覆盖方法当覆盖InlineFormSet
上的方法时,您应该子类化BaseInlineFormSet
,而不是BaseModelFormSet
。
例如,如果要覆盖clean()
:
from django.forms import BaseInlineFormSet
class CustomInlineFormSet(BaseInlineFormSet):
def clean(self):
super(CustomInlineFormSet, self).clean()
# example custom validation across forms in the formset
for form in self.forms:
# your custom formset validation
...
另请参见Overriding clean() on a ModelFormSet上的clean()。
然后,在创建内联表单集时,传递可选参数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. 像这样:
return HttpResponseRedirect(author.get_absolute_url())
else:
formset = BookInlineFormSet(instance=author)
return render(request, 'manage_books.html', {'formset': formset})
注意我们如何在GET
和POST
例中传递instance
。
modelformset_factory
使用modelformset_factory
并将其大部分参数传递给inlineformset_factory
。 这意味着您可以使用modelformset_factory
参数,将其传递到widgets
。 请参阅上面的指定要在窗体中使用的窗口小部件的窗口小部件。
2017年9月6日