从模型创建表单

ModelForm

class 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 . 以下是转换的完整列表:

模型场

表单字段

AutoField

表格中未显示

BigAutoField

表格中未显示

BigIntegerField

IntegerField 具有 min_value 设置为-9223372036854775808和 max_value 设置为9223372036854775807。

BinaryField

CharField 如果 editable 设置为 True 在模型字段上,否则不在表单中表示。

BooleanField

BooleanFieldNullBooleanField 如果 null=True .

CharField

CharField 具有 max_length 设置为模型字段的 max_lengthempty_value 设置为 None 如果 null=True .

DateField

DateField

DateTimeField

DateTimeField

DecimalField

DecimalField

DurationField

DurationField

EmailField

EmailField

FileField

FileField

FilePathField

FilePathField

FloatField

FloatField

ForeignKey

ModelChoiceField (见下文)

ImageField

ImageField

IntegerField

IntegerField

IPAddressField

IPAddressField

GenericIPAddressField

GenericIPAddressField

JSONField

JSONField

ManyToManyField

ModelMultipleChoiceField (见下文)

PositiveBigIntegerField

IntegerField

PositiveIntegerField

IntegerField

PositiveSmallIntegerField

IntegerField

SlugField

SlugField

SmallAutoField

表格中未显示

SmallIntegerField

IntegerField

TextField

CharField with widget=forms.Textarea

TimeField

TimeField

URLField

URLField

UUIDField

UUIDField

如你所料, ForeignKeyManyToManyField 模型字段类型是特殊情况:

  • 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())

A的验证 ModelForm

验证 ModelForm

  1. Validating the form

  2. Validating the model instance

与普通表单验证一样,模型表单验证在调用 is_valid() 或访问 errors 属性,并在调用时显式 full_clean() 尽管在实践中通常不会使用后一种方法。

Model 验证 (Model.full_clean() )从表单验证步骤中触发,就在表单的 clean() 方法被调用。

警告

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

压倒 clean() 方法

您可以覆盖 clean() 方法在模型窗体上以与在普通窗体上相同的方式提供附加验证。

附加到模型对象的模型表单实例将包含 instance 属性,使其方法可以访问该特定模型实例。

警告

这个 ModelForm.clean() 方法设置一个标志,使 model validation 步骤验证标记为的模型字段的唯一性 uniqueunique_togetherunique_for_date|month|year .

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

与模型验证的交互

作为验证过程的一部分, ModelForm 将调用 clean() 模型上每个字段的方法,该方法在表单上有一个对应的字段。如果排除了任何模型字段,则不会对这些字段运行验证。见 form validation 有关如何进行现场清洁和验证的更多文档。

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

关于模型的考虑 error_messages

在中定义的错误消息 form field 水平或在 form Meta 级别始终优先于在 model field 水平。

上定义的错误消息 model fields 仅当 ValidationErrormodel 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 ,如果有一个,则为该字段。此行为不适用于使用 CheckboxInputCheckboxSelectMultipleSelectMultiple (或任何自定义小部件 value_omitted_from_data() 方法始终返回 False )因为一个未选中的复选框和未选中的 <select multiple> 不显示在HTML表单提交的数据中。如果您正在设计一个API,并且想要使用这些小部件之一的字段的默认回退行为,请使用自定义表单字段或小部件。

这个 save() 方法接受可选的 commit 关键字参数,它接受 TrueFalse . 如果你调用 save() 具有 commit=False ,然后它将返回一个尚未保存到数据库中的对象。在这种情况下,由你来调用 save() 在生成的模型实例上。如果要在保存对象之前对其进行自定义处理,或者要使用特定的 model saving options . commitTrue 默认情况下。

使用的另一个副作用 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 )。

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

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

    from django.forms import ModelForm
    
    
    class AuthorForm(ModelForm):
        class Meta:
            model = Author
            fields = "__all__"
    
  2. 设置 exclude 的属性 ModelForm 内心 Meta 类到要从窗体中排除的字段列表。

    例如::

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

    自从 Author 模型有3个字段 nametitlebirth_date ,这将导致字段 namebirth_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 属性。在这种情况下,必须重写表单字段才能使用不同的小部件。

同样,您可以指定 labelshelp_textserror_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_classesformfield_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 类,其中的字段已声明性定义。基本上, ModelFormonly 生成以下字段: 丢失的 从窗体,或者换句话说,未声明性定义的字段。

声明性定义的字段保持原样,因此对 Meta 属性,例如 widgetslabelshelp_textserror_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 不会本地化他们的数据。要为字段启用本地化,可以使用 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.fieldsMeta.exclude 列表:

>>> class RestrictedArticleForm(EnhancedArticleForm):
...     class Meta(ArticleForm.Meta):
...         exclude = ["body"]
...

这将添加 EnhancedArticleForm 并修改原始文件 ArticleForm.Meta 删除一个字段。

不过,有几点需要注意。

  • 标准的python名称解析规则适用。如果有多个基类声明 Meta 内部类,只使用第一个类。这意味着孩子的 Meta ,否则 Meta 第一个父母的,等等。

  • 两者都有可能继承 FormModelForm 但同时,您必须确保 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工厂函数

您可以使用独立函数从给定的模型创建表单 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 内部的 Meta 类。请看 ModelForm 选择要使用的字段 文档。

..。或为特定字段启用本地化:

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

模型窗体集

class models.BaseModelFormSet

喜欢 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 超过了额外窗体的数目,多余的初始数据将被忽略。如果带有初始数据的额外表单没有被用户更改,它们将不会被验证或保存。

在表单集中保存对象

就像一个 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() 方法。您可以找到有关此限制的更多信息,此限制也适用于 ModelFormsSelecting 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() ,您的模型表单集将具有三个包含表单集更改的新属性:

models.BaseModelFormSet.changed_objects
models.BaseModelFormSet.deleted_objects
models.BaseModelFormSet.new_objects

限制可编辑对象的数量

与常规表单集一样,您可以使用 max_numextra 参数到 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 将验证表单集中没有任何项违反模型上的唯一约束(或者 uniqueunique_togetherunique_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 两个方面的论点 POSTGET 本例中的案例。

使用模板中的表单集

在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 %} 循环,则需要呈现主键字段。例如,如果要呈现 nameage 模型的字段:

<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 ,确保渲染。)

内联窗体集

class models.BaseInlineFormSet

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

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)

BookFormSetprefix'book_set' (<model name>_set )如果 BookForeignKeyAuthor 有一个 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_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})

注意我们是怎么通过的 instance 在两者中 POSTGET 病例。

指定要在内联表单中使用的小部件

inlineformset_factory 使用 modelformset_factory 并将其大部分参数传递给 modelformset_factory . 这意味着您可以使用 widgets 参数的传递方式与 modelformset_factory . 见 Specifying widgets to use in the form with widgets 上面。