从模型创建表单

ModelForm

ModelForm[source]

如果你正在构建一个数据库驱动的应用程序,那么你很可能会拥有与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 以下是完整的转换列表:

模型领域 表单字段
下拉列表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.
的DateField 的DateField
DateTimeField字段 DateTimeField字段
DecimalField DecimalField
EmailField EmailField
的FileField 的FileField
FilePathField FilePathField
FloatField FloatField
ForeignKey的 ModelChoiceField (see below)
的ImageField 的ImageField
IntegerField IntegerField
IPAddressField IPAddressField
GenericIPAddressField GenericIPAddressField
ManyToManyField ModelMultipleChoiceField (see below)
NullBooleanField NullBooleanField
PositiveIntegerField IntegerField
PositiveSmallIntegerField IntegerField
SlugField SlugField
SmallIntegerField IntegerField
文本域 CharFieldwidget=forms.Textarea
TimeField TimeField
URLField URLField

正如您所预料的那样,ForeignKeyManyToManyField模型字段类型是特殊情况:

  • ForeignKey is represented by django.forms.ModelChoiceField, which is a ChoiceField whose choices are a model QuerySet.
  • ManyToManyField is represented by django.forms.ModelMultipleChoiceField, which is a MultipleChoiceField whose choices are a model QuerySet.

另外,每个生成的表单字段的属性设置如下:

  • 如果模型字段为blank=True,则表单字段中required设置为False 否则,required=True
  • 表单字段的label设置为模型字段的verbose_name,第一个字符大写。
  • 表单字段的help_text被设置为模型字段的help_text
  • 如果模型字段设置了choices,那么表单字段的widget将被设置为Select,选择来自模型字段的choices 这些选项通常会包含默认选择的空白选项。 如果该字段是必需的,则强制用户进行选择。 如果模型字段包含blank=False和显式的default值(default值将最初选择)。

最后,请注意,您可以覆盖用于给定模型字段的表单字段。 See Overriding the default fields below.

一个完整的例子

考虑这组模型:

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']

With these models, the ModelForm subclasses above would be roughly equivalent to this (the only difference being the save() method, which we’ll discuss in a moment.):

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涉及两个主要步骤:

  1. 验证表单
  2. 验证模型实例

就像正常的表单验证一样,在调用is_valid()或访问errors属性时显式地触发模型表单验证,在调用full_clean()尽管在实践中你通常不会使用后一种方法。

Model validation (Model.full_clean()) is triggered from within the form validation step, right after the form’s clean() method is called.

警告

清理过程以各种方式修改传递给ModelForm构造函数的模型实例。 例如,模型上的任何日期字段都被转换为实际的日期对象。 失败的验证可能会使底层模型实例处于不一致的状态,因此不建议重用它。

重写clean()方法

您可以覆盖模型表单上的clean()方法,以与正常表单相同的方式提供额外的验证。

附加到模型对象的模型表单实例将包含一个instance属性,该属性为其方法提供对特定模型实例的访问权限。

警告

The ModelForm.clean() method sets a flag that makes the model validation step validate the uniqueness of model fields that are marked as unique, unique_together or unique_for_date|month|year.

如果您想重写clean()方法并维护此验证,则必须调用父类的clean()方法。

与模型验证交互

As part of the validation process, ModelForm will call the clean() method of each field on your model that has a corresponding field on your form. 如果您排除了任何模型字段,则验证将不会在这些字段上运行。 请参阅form validation文档以了解有关字段清理和验证如何工作的更多信息。

在进行任何唯一性检查之前,模型的clean()方法将被调用。 有关模型的clean()钩子的更多信息,请参阅Validating objects

关于模型的error_messages 的注意事项

Error messages defined at the form field level or at the form Meta level always take precedence over the error messages defined at the model field level.

Error messages defined on model fields are only used when the ValidationError is raised during the model validation step and no corresponding error messages are defined at the form level.

You can override the error messages from NON_FIELD_ERRORS raised by model validation by adding the NON_FIELD_ERRORS key to the error_messages dictionary of the ModelForm’s inner Meta class:

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()方法。 此方法从绑定到表单的数据创建并保存数据库对象。 A subclass of ModelForm can accept an existing model instance as the keyword argument instance; if this is supplied, save() will update that instance. If it’s not supplied, save() will create a new instance of the specified model:

>>> 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来完成。 如果表单中的数据无效,即form.errors评估为True,则会引发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,并希望使用这些小部件之一的字段的默认回退行为,请使用自定义表单字段或小部件。

这个save()方法接受一个可选的commit关键字参数,它接受TrueFalse 如果用commit=False调用save(),那么它将返回尚未保存到数据库的对象。 在这种情况下,您可以在生成的模型实例上调用save() 如果要在保存对象之前对对象执行自定义处理,或者要使用专门的model saving options,则这很有用。 commit默认为True

使用commit=False的另一个副作用是在您的模型与另一个模型具有多对多关系时出现的。 如果您的模型具有多对多关系,并且在保存表单时指定commit=False,则Django无法立即保存表单数据以获得多对多关系。 这是因为在实例存在于数据库中之前,不能保存实例的多对多数据。

要解决此问题,每次使用commit=False保存表单时,Django都会在您的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与其他forms完全相同>表格。 For example, the is_valid() method is used to check for validity, the is_multipart() method is used to determine whether a form requires multipart file upload (and hence whether request.FILES must be passed to the form), etc. See Binding uploaded files to a form for more information.

选择要使用的字段

强烈建议您使用fields属性明确设置应在表单中编辑的所有字段。 如果一个表单意外地允许用户设置特定的字段,特别是在将新的字段添加到模型中时,不这样做可能很容易导致安全问题。 根据表单呈现方式的不同,问题甚至可能在网页上不可见。

另一种方法是自动包括所有的字段,或只列入一些黑名单。 This fundamental approach is known to be much less secure and has led to serious exploits on major websites (e.g. GitHub).

但是,有两种快捷方式可用于您可以保证这些安全问题不适用于您的情况:

  1. fields属性设置为特殊值'__all__'以指示应该使用模型中的所有字段。 例如:

    from django.forms import ModelForm
    
    class AuthorForm(ModelForm):
        class Meta:
            model = Author
            fields = '__all__'
    
  2. ModelForm的inner Meta类的exclude属性设置为从表单中排除的字段列表。

    例如:

    class PartialAuthorForm(ModelForm):
        class Meta:
            model = Author
            exclude = ['title']
    

    由于Author模型有3个字段nametitlebirth_date,这将导致字段namebirth_date出现在表单上。

如果使用其中任何一个,字段在表单中出现的顺序将是模型中字段的定义顺序,最后出现ManyToManyField实例。

此外,Django应用了以下规则:如果在模型字段上设置editable=False通过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()

有关使用save(commit=False)的更多详细信息,请参阅保存表单的部分

覆盖默认字段

如上面字段类型表中所描述的默认字段类型是合理的默认值。 如果你的模型中有一个DateField,你可能希望在你的表单中将它表示为DateField But ModelForm gives you the flexibility of changing the form field for a given model.

要为字段指定自定义小部件,请使用内部Meta类的widgets属性。 这应该是一个字典映射字段名称小部件类或实例。

For example, if you want the CharField for the name attribute of Author to be represented by a <textarea> instead of its default <input type="text">, you can override the field’s 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}),
        }

widgets字典接受小部件实例(例如,Textarea(...))或类(例如Textarea)。

Similarly, you can specify the labels, help_texts and error_messages attributes of the inner Meta class if you want to further customize a field.

例如,如果您想为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 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']

注意

当你像这样明确地实例化一个表单字段时,了解ModelFormForm是如何相关是很重要的。

ModelForm is a regular Form which can automatically generate certain fields. 自动生成的字段取决于Meta类的内容以及已经声明性定义了哪些字段。 Basically, ModelForm will only generate fields that are missing from the form, or in other words, fields that weren’t defined declaratively.

定义的字段保持原样,因此可以对Meta属性(如widgetslabelshelp_texts >或error_messages被忽略;这些仅适用于自动生成的字段。

同样,声明定义的字段不会像相应模型中的max_lengthrequired那样绘制它们的属性。 如果要维护在模型中指定的行为,则必须在声明表单字段时明确设置相关参数。

例如,如果Article模型看起来像这样:

class Article(models.Model):
    headline = models.CharField(
        max_length=200,
        null=True,
        blank=True,
        help_text='Use puns liberally',
    )
    content = models.TextField()

并且您希望对headline进行一些自定义验证,同时按照指定保留blankhelp_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中的字段不会本地化他们的数据。 要为字段启用本地化,可以使用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__',则所有字段将被本地化。

表单继承

As with basic forms, you can extend and reuse ModelForms by inheriting them. 如果您需要在父类上声明额外字段或额外方法以用于从模型派生的多个表单中,这非常有用。 例如,使用以前的ArticleForm类:

>>> class EnhancedArticleForm(ArticleForm):
...     def clean_pub_date(self):
...         ...

这会创建一个与ArticleForm行为相同的表单,除了pub_date字段有一些额外的验证和清除。

如果要更改Meta.fieldsMeta.exclude列表,您还可以继承父类的Meta内部类:

>>> class RestrictedArticleForm(EnhancedArticleForm):
...     class Meta(ArticleForm.Meta):
...         exclude = ('body',)

这增加了来自EnhancedArticleForm的额外方法,并修改了原始ArticleForm.Meta以删除一个字段。

然而有几件事要注意。

  • 正常的Python名称解析规则适用。 如果您有多个声明Meta内部类的基类,则只会使用第一个。 This means the child’s Meta, if it exists, otherwise the Meta of the first parent, etc.

  • 可以同时从FormModelForm继承,但是,您必须确保ModelForm首先出现在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工厂函数

您可以使用独立函数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()})

可以使用fieldsexclude关键字参数或ModelForm inner Meta 请参阅ModelForm Selecting the fields to use文档。

...或为特定领域启用本地化:

>>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=("birth_date",))

模型formsets

楷模。 BaseModelFormSet T0> ¶ T1>

regular formsets一样,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 = 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() uses formset_factory() to generate formsets. 这意味着模型表单只是知道如何与特定模型交互的基本表单的扩展。

更改查询集

默认情况下,当从模型创建一个formset时,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的窗口小部件类。 这与ModelFormMeta类中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(). 但是,对于模型表单集合,初始值仅适用于额外的表单,即那些未附加到现有模型实例的表单。 如果用户没有更改含有初始数据的额外表格,则不会对其进行验证或保存。

将对象保存在formset

ModelForm一样,您可以将数据保存为模型对象。 这是通过formset的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包含ManyToManyField,则还需要调用formset.save_m2m()以确保正确保存多对多关系。

调用save()之后,您的模型formset将具有三个新的属性,其中包含formset的更改:

models.BaseModelFormSet。 changed_objects T0> ¶ T1>
models.BaseModelFormSet。 deleted_objects T0> ¶ T1>
models.BaseModelFormSet。 new_objects T0> ¶ T1>

限制可编辑对象的数量

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不会阻止显示现有的对象:

>>> 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额外的空白表单添加到formset中,只要表单的总数不超过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>

A max_num value of None (the default) puts a high limit on the number of forms displayed (1000). 实际上这相当于没有限制。

在视图中使用模型formset

模型表单与表单集非常相似。 假设我们要呈现一个formset来编辑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})

正如你所看到的,模型框架的视图逻辑与“正常”框架的视图逻辑并没有太大的区别。 The only difference is that we call formset.save() to save the data into the database. (这在上面描述过,在Saving objects in the formset中的对象。)

ModelFormSet上覆盖clean()

ModelForms类似,默认情况下,ModelFormSetclean()方法将验证formset中的任何项都不违反唯一约束(uniqueunique_togetherunique_for_date|month|year)。 如果要覆盖ModelFormSet上的clean()方法并维护此验证,则必须调用父类的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

使用自定义的查询集

如前所述,您可以覆盖模型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})

请注意,在本例中,我们在POSTGET情况下都传递了queryset参数。

在模板中使用formset

有三种方法可以在Django模板中渲染一个formset。

首先,你可以让formset完成大部分的工作:

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

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. 例如,如果您正在渲染模型的nameage字段:

<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情况下,模型formset可以正常工作。 (这个例子假定一个名为id的主键。 如果你已经明确定义了自己的主键,这个主键不叫id,确保它被渲染。)

内联表单

楷模。 BaseInlineFormSet T0> ¶ T1>

内嵌是表单集上模型表单集的顶部上的小的抽象层。 这些简化了通过外键处理相关对象的情况。 假设你有这两个模型:

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’s prefix is 'book_set' (<model name>_set ). If Book’s ForeignKey to Author has a related_name, that’s used instead.

注意

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
            ...

另请参阅Overriding clean() on a ModelFormSet上重写clean()。

然后当你创建你的内联formset时,传入可选参数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_nameinlineformset_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})

注意我们在POSTGET情况下如何传递instance

指定以内联形式使用的小部件

inlineformset_factory使用modelformset_factory并将其大部分参数传递给modelformset_factory 这意味着您可以像传递modelformset_factory一样使用widgets参数。 请参阅指定窗口小部件使用上面的窗口。