ModelForm

Posted crazy-zjl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ModelForm相关的知识,希望对你有一定的参考价值。

 

ModelForm

模型的属性与表单属性对应关系

如果模型字段设置了 blank=True ,那么表单字段的 required 属性被设置为 False,否则 required=True 。

表单字段的 label 设置为模型字段的 verbose_name ,并且首字母大写。

表单字段的 help_text 设置为模型字段的 help_text 。

如果模型字段设置了 choices ,那么表单字段的 widget 会被设置为 Select ,其选项来自模型字段的 choices 。这些选项通常包含一个默认选中的空选项。如果字段设置了必填,则会强制用户进行选择。如果模型字段设置了 blank=False 以及一个明确的 default 值,则表单字段中不会包含空选项(默认会选中 default 值)

error_messages注意事项

在 form field 级别或者 form Meta 级别定义的错误信息优先级总是高于在 model field 级别定义的。

在 model fields 上定义的错误信息只有在 model validation 步骤引发 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.",
            }
        }
View Code

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()
View Code

请注意,如果表单尚未验证,调用 save() 将通过检查 form.errors 来实现验证。如果表单验证不过,则会引发 ValueError —— 比如,如果 form.errors 返回 True 。

 save() 方法接受一个可选参数 commit ,它的值是 True 或者 False 。如果调用 save()的时候使用 commit=False ,那么它会返回一个尚未保存到数据库的对象。在这种情况下,需要您自己在生成的模型实例上调用 save() 。如果要在保存对象之前对对象执行自定义操作,或者要使用其中一个专用的 model save options,这很有用。 commit 的值默认为 True

另一个使用 commit=False 的作用,您可以在模型与另一个模型有多对多关系的时候看到。如果您的模型具有多对多关系,并且在保存表单时指定了 commit=False ,Django无法立即保存多对多关系的表单数据。这是因为实例的多对多数据只有实例在数据库中存在时才能保存。

要解决这个问题,Django会在您每次使用 commit=False 保存表单时,向 ModelForm 子类添加一个 save_m2m() 方法。在您手动保存表单生成的实例后,可以调用 save_m2m()来保存多对多的表单数据。例如:

技术分享图片
# 用POST数据创建一个表单实例
>>> f = AuthorForm(request.POST)

# 返回一个尚未保存的实例对象
>>> new_author = f.save(commit=False)

# 修改一些数据
>>> new_author.some_field = some_value

# 保存这个新的实例
>>> new_author.save()

# 调用save_m2m(),保存具有 多对多关系 的数据
>>> f.save_m2m()
View Code

只有在您使用 save(commit=False) 的时候才需要调用 save_m2m() 。当您在表单上使用普通的 save() 时,无需调用其他方法,所有数据(包括多对多数据)都会被保存。例如:

技术分享图片
# 用POST数据创建表单实例
>>> a = Author()
>>> f = AuthorForm(request.POST, instance=a)

# 创建并保存这个新实例,不需要在做其他事
>>> new_author = f.save()
View Code

除了 save() 和 save_m2m() 方法之外,ModelForm 与普通的表单工作方式一样。例如,用 is_valid() 方法来检查合法性,用 is_multipart() 方法来确定表单是否需要multipart文件上传(之后是否必须将 request.FILES 传递给表单),等等。更多相关信息,请参阅 Binding uploaded files to a form 。

 

选择要使用的字段

强烈建议您使用 fields 属性来显式设置所有应在表单中编辑的字段。如果不这样做,当一张表单不慎允许用户设置某些字段,尤其是在将新字段添加到模型中时,很容易导致安全问题。根据表单渲染方式的不同,甚至可能不会在网页上显示问题。

另一种方法是自动包含所有字段,其他放入黑名单。

但是,有两种简单的方法保证你不会出现这些安全问题:

  1. 将 fields 属性设置为特殊值 ‘__all__‘ 以表明需要使用模型中的所有字段。例如:

技术分享图片
from django.forms import ModelForm

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = __all__
View Code

2. 将 ModelForm 中Meta类的 exclude 属性设置为表单中需要排除的字段列表。

例如:

技术分享图片
class PartialAuthorForm(ModelForm):
    class Meta:
        model = Author
        #除了"title"字段,其他字段都需要显示
        exclude = [title]
View Code

不管使用哪一种,字段会按模型中定义的顺序在表单中出现, 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()
View Code

 

覆盖默认字段

formmodel模型的默认字段类型都是相对应的。如果您的模型中有一个 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}),
        }
View Code

 

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

同样的,如果您想进一步自定义一个字段,还可以指定内部Meta类的 labels 、 help_texts 和 error_messages 属性。

例如您想自定义 name 字段中所有面向用户的字符文本:

技术分享图片
 gettext和gettext_lazy区别:

  标准翻译:使用函数 gettext() 来指定一个翻译字符串

  惰性翻译:使得其中的值只有在访问时才会被翻译,而不是在gettext_lazy() 被调用时翻译

  参考:https://cloud.tencent.com/developer/article/1368969

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."),
            },
        }


 
View Code

 

您还可以指定 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,
        }
View Code

 

最后,如果您想完全控制一个字段(包括它的type, validators, required, 等等),您可以通过声明指定字段来做到这一点,就像在一个普通的 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‘]
View Code

 

启用对字段的本地化

默认情况下, 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,)
View Code

如果 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
View Code

 

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

 

要包含的字段可以使用 fields 和 exclude 关键字参数或 ModelForm 内部的 Meta 类中相应的属性来指定。

... 或者为个别字段启用本地化功能:

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

 

模型表单集

和 普通表单集 一样,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>
View Code

 

注意:
modelformset_factory() 使用 formset_factory() 来生成表单集。
这意味着模型formset只是一个知道如何与指定模型交互的普通formset的扩展。

 

更改查询集

默认情况下,当您创建一个模型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 指定字段自定义部件。这与 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__‘ ,则所有字段都将被本地化。

 

提供初始值

与常规的formset一样,在实例化modelformset_factory()返回的modelformset类时,可以通过指定初始参数在formset中为表单指定初始数据。然而,对于model formset,初始值只适用于额外的表单,即不附加到现有模型实例的表单。如果initial的长度超过了额外表单的数量,则忽略多余的初始数据。如果用户没有更改带有初始数据的额外表单,则不会对其进行验证或保存。

 

在formset中保存对象

与使用 ModelForm 一样,您可以将数据保存为 model 对象。这是通过formset的save()方法完成的:

# Create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)

# Assuming all is valid, save the data.
>>> instances = formset.save()

 

 save()方法返回已保存到数据库的实例。如果绑定数据中给定实例的数据没有发生变化,那么该实例将不会保存到数据库中,也不会包含在返回值中(实例,在上面的示例中)。

当表单中缺少字段(例如,因为它们被排除在外)时, 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()

这使您能够在将数据保存到数据库之前将数据附加到实例。如果您的表单集包含 ManyToManyField,您还需要调用formset.save_m2m() 来确保正确保存多对多关系。

在调用 save()之后,您的模型表单集合将具有三个新属性,包含表单集的更改

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

 

限制可编辑对象的数量

与常规的表单集一样,您可以使用max_num和额外的参数来modelformset_factory(),以限制显示的额外表单的数量。

max_num 不阻止显示现有对象: 

 

另外, extra=0 并不妨碍创建新的模型实例,因为您可以使用javascript添加额外的表单,或者仅仅发送额外的POST数据。formset还没有为“仅编辑”视图提供阻止创建新实例的功能。

如果 max_num 的值大于现有相关对象的数量,则在forms总数不超过max_num的情况下,将向formset添加额外空白表单:

技术分享图片
>>> 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>
View Code

max_num 的值 None (默认值),它限制最多显示(1000)张表单,其实这相当于没有限制。

 

在view中使用 model formset

模型表单集非常类似于表单集。假设我们想要呈现一个编辑Author模型实例的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)
        if formset.is_valid():
            formset.save()
            # do something.
    else:
        formset = AuthorFormSet()
    return render(request, manage_authors.html, {formset: formset})
View Code

正如您所看到的,模型表单集的视图逻辑与“普通”表单集的视图逻辑并没有太大的不同。唯一的区别是,我们调用 formset.save() 将数据保存到数据库中。(上面在将对象保存到formset中已经描述了这一点。)

 

在 ModelFormSet重新定义 clean() 

就像使用 ModelForms一样,默认情况下, ModelFormSet的 clean() 方法将验证formset中的任何项都不会违反模型上的唯一约束( uniqueunique_together or unique_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
            ...

 

还请注意,在完成此步骤之前,已经为每个表单创建了单独的模型实例。修改表单中的值。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
View Code

 

使用自定义queryset

如前所述,您可以覆盖model formset使用的默认queryset:

技术分享图片
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})
View Code

注意,在本例中,我们在 POST 和 GET 案例中都传递了 queryset 参数。

 

在模板中使用表单集

在Django模板中呈现表单集有三种方法。

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

<form method="post">
    {{ formset }}
</form>

 

其次,您可以手动渲染表单集,让表单自行处理:

<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form }}
    {% endfor %}
</form>

当您自己手动渲染表单时,请确保渲染如上所示的管理表单。参见 management form documentation

 

第三,您可以手动渲染每个字段:

<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {% for field in form %}
            {{ field.label_tag }} {{ field }}
        {% endfor %}
    {% endfor %}
</form>

 

如果选择使用第三种方法,并且不使用 {% for %}循环遍历字段,则需要呈现主键字段。例如,如果您正在渲染模型的 name和 age 字段:

<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form.id }}
        <ul>
            <li>{{ form.name }}</li>
            <li>{{ form.age }}</li>
        </ul>
    {% endfor %}
</form>

注意,我们需要显式地渲染 {{ form.id }}。这可以确保在POST情况下,model formset将正确工作。(本例假设一个名为id的主键。如果您已经显式地定义了自己的主键,而这个主键不是id,那么请确保它被渲染。)

 

内联表单集

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

技术分享图片
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)
View Code

 

如果您想创建一个允许您编辑属于某个特定作者的图书的formset,您可以这样做:

>>> 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的前缀是‘book_set‘ (<model name>_set)。如果Book的foreign - key 到 Author有一个related_name,那么就用它。

 

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

参见在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)
View Code

 

同一个模型多个外键

如果您的模型包含同一个模型的多个外键,则需要使用 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()
View Code

要解决这个问题,可以使用fk_name 来 inlineformset_factory():

>>> FriendshipFormSet = inlineformset_factory(Friend, Friendship, fk_name=from_friend,
...     fields=(to_friend, length_in_months))

 

在view中使用内联表单集

您可能希望提供一个视图,该视图允许用户编辑模型的相关对象。你可以这样做:

技术分享图片
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})
View Code

注意,在POST和GET案例中是如何传递实例的。

 

 

 
 

学习自用,欢迎大神评论、指正

更多详情见Django文档之ModelForm:

https://docs.djangoproject.com/zh-hans/2.1/topics/forms/modelforms/

以上是关于ModelForm的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ModelForm 中使用 forms.ChoiceField()?

123.ModelForm的使用

表单-modelform

Django之ModelForm使用

12_模型表单modelForm的使用

Django ModelForm 覆盖 __init__