django关于ModelForm

1.class ModelForm

If you’re building a database-driven app, chances are you’ll have forms that map closely to Django models. For instance, you might have a BlogComment model, and you want to create a form that lets people submit comments.
In this case, it would be redundant to define the field types in your form, because you’ve already defined the fields in your model.
For this reason, Django provides a helper class that lets you create a Form class from a Django model. For example:

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

ModelForm通常用于实现从model到form的映射, 从而快速实现表单的创建,可以减少很多重复工作; 简单的使用见上例;

2.model和form之间的对应关系:
(1)

The generated Form class will have a form field for every model field specified, in the order specified in the fields attribute.
Each model field has a corresponding default form field. For example, a CharField on a model is represented as a CharField on a form. A model ManyToManyField is represented as a MultipleChoiceField. Here is the full list of conversions:

对于指定的每个model字段,生成的form类都有一个相应的字段与其对应, 每个model字段有一个默认的form字段用于生成对应关系,例如model中的CharField默认对应的form字段为Form的CharField; 具体的字段对应表见django文档;

(2)

As you might expect, the ForeignKey and ManyToManyField model field types are special cases:
• 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.

几种特殊字段: 外键字段对应于form中的ModelChoiceField, 即选项为QuerySet对象的ChoiceField字段; 多对多字段对应ModelMultipleChoice字段;

In addition, each generated form field has attributes set as follows:
• If the model field has blank=True, then required is set to False on the form field. Otherwise, required=True.
• The form field’s label is set to the verbose_name of the model field, with the first character capitalized.
• The form field’s help_text is set to the help_text of the model field.
• If the model field has choices set, then the form field’s widget will be set to Select, with choices coming from the model field’s choices. The choices will normally include the blank choice which is selected by default. If the field is required, this forces the user to make a selection. The blank choice will not be included if the model field has blank=False and an explicit default value (the default value will be initially selected instead).
Finally, note that you can override the form field used for a given model field. See Overriding the default fields below;

除此之外, 还有几点注意:
1)如果model字段具有blank=True, 那么form中required会设为False;
2)form中的label和model中的verbose_name相对应, 但首字母变为大写;
3)form中的help_text和model中的help_text相对应;
4)如果model中的字段设置了choices参数, 那么相对应的form字段的widget变为select, 通常会包含空选项并默认选中, 但如果model中blank=False并且设置了default参数则不包含空选项;
5)你可以覆盖对应的form field;

3.完整实例:

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

4.ModelForm的验证

There are two main steps involved in validating a ModelForm:

  1. Validating the form
  2. Validating the model instance
    Just like normal form validation, model form validation is triggered implicitly when calling is_valid() or accessing the errors attribute and explicitly when calling full_clean(), although you will typically not use the latter method in practice.
    Model validation (Model.full_clean()) is triggered from within the form validation step, right after the form’s clean() method is called.
    Warning: The cleaning process modifies the model instance passed to the ModelForm constructor in various ways. For instance, any date fields on the model are converted into actual date objects. Failed validation may leave the underlying model instance in an inconsistent state and therefore it’s not recommended to reuse it.

ModelForm的验证分为两步: 验证form和验证model实例;
和通常的form验证相同, model form验证会在调用is_valid()或访问errors属性是隐性触发或调用full_clean()时显性触发, 尽管后一种方法通常不会使用;
Model的验证在form验证时内部触发, 在紧接着form的clean()方法调用后;

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

5.Overriding the clean() method覆盖clean()方法

You can override the clean() method on a model form to provide additional validation in the same way you can on a normal form.
A model form instance attached to a model object will contain an instance attribute that gives its methods access to that specific model instance.
Warning: 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. If you would like to override the clean() method and maintain this validation, you must call the parent class’s clean() method.

和通常的form一样,你可以通过覆盖clean()方法来进行额外的验证;
一个附带model实例的model form具有instance属性以便它的方法来访问相应的model实例;
注意: modelform的clean()方法设置了一个flag参数,用于在model验证阶段进行唯一性验证,例如设置了unique, unique_together or unique_for_date|month|year的字段.所以如果你想保留这个唯一性验证, 那么你在覆盖该方法时必须调用父类的clean()方法;

Interaction with model validation
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. If you have excluded any model fields, validation will not be run on those fields. See the form validation documentation for more on how field cleaning and validation work.
The model’s clean() method will be called before any uniqueness checks are made. See Validating objects for more information on the model’s clean() hook.

modelform会调用model所有相关字段的clean()方法, 但不会在modelform中排除的字段上调用; model的clean()方法会在任何唯一性验证前调用;

7.关于错误信息

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

在form字段或Meta中定义的错误信息总是优先于model中的错误信息被调用;
只有在model验证阶段抛出ValiditionError并且在form验证阶段定义没有相关错误信息时,model的错误信息才被使用;
您可以通过将NON_FIELD_ERRORS键添加到ModelForm内部Meta类的error_messages字典中来覆盖模型验证引发的NON_FIELD_ERRORS错误消息,例如上例;

8.The save() method save方法:

1)Every ModelForm also has a save() method. This method creates and saves a database object from the data bound to the form. 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()

1)每个modelform都有save()方法,此方法会创建或更新和表单绑定的相关数据库的对象;ModelForm的子类可以以关键字参数instance的方式接收一个现有的model实例;如果给出了此参数, 则save()方法会更新该对象, 否则创建新对象;见上例;

2):

Note that if the form hasn’t been validated, calling save() will do so by checking form.errors. A ValueError will be raised if the data in the form doesn’t validate – i.e., if form.errors evaluates to True.
如果在数据未验证前调用save()方法, 则save()会通过调用form.errors属性进行验证, 如果该属性为True, 则抛出异常;

3):

If an optional field doesn’t appear in the form’s data, the resulting model instance uses the model field default, if there is one, for that field. 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. Use a custom form field or widget if you’re designing an API and want the default fallback behavior for a field that uses one of these widgets.

如果可选字段没有出现在form的数据中, 那模型实例保存时会使用该字段的默认值, 如果有的话; 但该行为并不适用于使用CheckboxInput, CheckboxSelectMultiple 或者SelectMultiple 的字段, 应为如果这些插件未选中, 根本不会出现在form表单提交的数据中, 而如果有默认值, 则默认为选中状态;如果你想对这些插件也使用这种行为,或正在设计API, 那么可以进行自定义form字段或插件;

4):

This save() method accepts an optional commit keyword argument, which accepts either True or False. If you call save() with commit=False, then it will return an object that hasn’t yet been saved to the database. In this case, it’s up to you to call save() on the resulting model instance. This is useful if you want to do custom processing on the object before saving it, or if you want to use one of the specialized model saving options. commit is True by default.

该方法还可以设定一个可选参数commit, 为true或False;如果为false, 那么将会返回一个尚未在数据库保存的对象;在这种情况下,你可以对该对象进行自定义的处理,然后再保存它;默认为True;

Another side effect of using commit=False is seen when your model has a many-to-many relation with another model. If your model has a many-to-many relation and you specify commit=False when you save a form, Django cannot immediately save the form data for the many-to-many relation. This is because it isn’t possible to save manyto- many data for an instance until the instance exists in the database.
To work around this problem, every time you save a form using commit=False, Django adds a save_m2m() method to your ModelForm subclass. After you’ve manually saved the instance produced by the form, you can invoke save_m2m() to save the many-to-many form data. For example:

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

#Calling save_m2m() is only required if you use save(commit=False). When you use a simple save() on a form, all data – including many-to-many data – is saved without the need for any additional method calls. For example:
# 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()

Other than the save() and save_m2m() methods, a ModelForm works exactly the same way as any other forms form. 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.

使用commit=False的一个副作用就是数据中有多对多字段时, 当commit=False时, 多对多关系不能立即保存,因为无法对不存在的对象建立多对多关系, 而后续再调用save方法时也不会保存多对多关系;
解决该问题的方法就是使用save_m2m()方法;如上例所示, 注意,只有在以commit=False的情况下调用save()方法时才会用到该方法, 而其他情况下调用save()方法会自动保存多对多字段;

除了save()和save_m2m()方法以外,model form的其他表现和正常form相同, 例如is_multipart()方法会判断是否需要文件上传;

9.Selecting the fields to use选择使用的字段

It is strongly recommended that you explicitly set all fields that should be edited in the form using the fields attribute. Failure to do so can easily lead to security problems when a form unexpectedly allows a user to set certain fields, especially when new fields are added to a model. Depending on how the form is rendered, the problem may not
even be visible on the web page.
The alternative approach would be to include all fields automatically, or blacklist only some. This fundamental approach is known to be much less secure and has led to serious exploits on major websites (e.g. GitHub).

1)强烈建议您使用fields属性显式设置应在表单中编辑的所有字段。 当表单意外地允许用户设置某些字段时,特别是当新字段添加到模型时,如果不这样做很容易导致安全问题。 根据表单的呈现方式,问题甚至可能在网页上不可见。
替代方法是自动包括所有字段,或仅将一些字段列入黑名单。 众所周知,这种基本方法的安全性较低,并导致主要网站(例如GitHub)的严重漏洞。

2)解决方案:

There are, however, two shortcuts available for cases where you can guarantee these security concerns do not apply to you:

1.Set the fields attribute to the special value ‘all’ to indicate that all fields in the model should be used. For example:

from django.forms import ModelForm
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = '__all__'

2.Set the exclude attribute of the ModelForm’s inner Meta class to a list of fields to be excluded from the form. For example:

class PartialAuthorForm(ModelForm):
class Meta:
model = Author
exclude = ['title']
#Since the Author model has the 3 fields name, title and birth_date, this will result in the fields name and birth_date being present on the form.

解决方案两步, 见上述示例;使用fields属性和exclude属性;

3)其他事项:

If either of these are used, the order the fields appear in the form will be the order the fields are defined in the model, with ManyToManyField instances appearing last.
In addition, Django applies the following rule: if you set editable=False on the model field, any form created from the model via ModelForm will not include that field.

如果使用了上述任意一个, 那么字段在表单中的顺序将会是model中定义的顺序, 但多对多字段会放到最后; 另外, 如果model中 设置了editable为false, 那么该字段在modelform中将不会包括;

Note: Any fields not included in a form by the above logic will not be set by the form’s save() method. Also, if you manually add the excluded fields back to the form, they will not be initialized from the model instance.
Django will prevent any attempt to save an incomplete model, so if the model does not allow the missing fields to be empty, and does not provide a default value for the missing fields, any attempt to save() a ModelForm with missing fields will fail. To avoid this failure, you must instantiate your model with initial values for the missing, but required fields:

author = Author(title='Mr')
form = PartialAuthorForm(request.POST, instance=author)
form.save()

#Alternatively, you can use save(commit=False) and manually set any extra required fields:
form = PartialAuthorForm(request.POST)
author = form.save(commit=False)
author.title = 'Mr'
author.save()

注意: 不在上述表单中包含的任何字段都不会被save()方法设置;如果你手动将字段添加到form中, model实例将无法对其初始化;
django将会避免save任何不完整的model实例, 如果model不允许form中未展示的字段为空,并且没有默认值, 那么save()方法会失败;解决此问题的两种方式见上例;

10.Overriding the default fields 覆盖默认字段

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

The widgets dictionary accepts either widget instances (e.g., Textarea(…)) or classes (e.g., Textarea).

1)要想覆盖默认字段, 可使用widget属性, widget为一个字典, 值可以是widget实例或类, 键为要定制的相关字段;

2):

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. For example if you wanted to customize the wording of all user facing strings for the name field:

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

类似的, 你可以指定labels, help_texts, 或error_messages来定制相关内容, 见上例;

3):

You can also specify field_classes to customize the type of fields instantiated by the form. For example, if you wanted to use MySlugFormField for the slug field, you could do the following:

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的相关字段, 可以使用field_classes属性;

4):

Finally, if you want complete control over of a field – including its type, validators, required, etc. – you can do this by declaratively specifying fields like you would in a regular Form. If you want to specify a field’s validators, you can do so by defining the field declaratively and setting its validators parameter:

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

如果你想完全自定制form字段, 那么直接显式的声明该字段, 就像通常的form一样,见上例

5):

Note: When you explicitly instantiate a form field like this, it is important to understand how ModelForm and regular Form are related.
ModelForm is a regular Form which can automatically generate certain fields. The fields that are automatically generated depend on the content of the Meta class and on which fields have already been defined declaratively.
Basically, ModelForm will only generate fields that are missing from the form, or in other words, fields that weren’t defined declaratively.
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.
Similarly, fields defined declaratively do not draw their attributes like max_length or required from the corresponding model. If you want to maintain the behavior specified in the model, you must set the relevant arguments explicitly when declaring the form field.

当你像4)中那样显性的指定字段时, 了解modelform和form如何关联十分重要; modelform是可以自动生成字段的form, 自动生成的字段取决于Meta中的定义和已经显式指定的字段;
基本上,model form只会生成未显式指定的字段; 显式指定的字段会保持原样, 任何在Meta中定义的属性都会无效, Meta中的属性只对字段生成的字段有效;
类似的, 显式指定的字段也不会附带相关model中的属性, 类似max_length, required等, 如果想保留这些属性, 必须自己添加!

下面是一个示例:

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

#and you want to do some custom validation for headline, while keeping the blank and help_text values as specified, you might define ArticleForm like this:
class ArticleForm(ModelForm):
headline = MyFormField(
max_length=200,
required=False,
help_text='Use puns liberally',
)
class Meta:
model = Article
fields = ['headline', 'content']

You must ensure that the type of the form field can be used to set the contents of the corresponding model field. When they are not compatible, you will get a ValueError as no implicit conversion takes place.

最后,你必须确保自定义的字段可以和相关model字段兼容, 否则会抛出ValueError异常;

11.Enabling localization of fields 字段的本地化

By default, the fields in a ModelForm will not localize their data. To enable localization for fields, you can use the localized_fields attribute on the Meta class.

>>> from django.forms import ModelForm
>>> from myapp.models import Author
>>> class AuthorForm(ModelForm):
... class Meta:
... model = Author
... localized_fields = ('birth_date',)

If localized_fields is set to the special value ‘__ all__’, all fields will be localized.

默认情况下, modelForm中的字段不会本地化它们的数据, 如果你想进行本地化 ,可以使用localized_fields, 见上例,如果该参数设置为__ all__, 那所有字段都会本地化;

12.Form inheritance 表单继承

As with basic forms, you can extend and reuse ModelForms by inheriting them. This is useful if you need to declare extra fields or extra methods on a parent class for use in a number of forms derived from models. For example, using the previous ArticleForm class:

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

This creates a form that behaves identically to ArticleForm, except there’s some extra validation and cleaning for the pub_date field.
You can also subclass the parent’s Meta inner class if you want to change the Meta.fields or Meta.exclude lists:

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

This adds the extra method from the EnhancedArticleForm and modifies the original ArticleForm.Meta to remove one field.

和基本表单一样可以通过继承的方式扩展和重用modelform, 当你需要在父类的基础上声明额外字段和方法以用于多个表单时会很有用;同样Meta类也可以被继承, 见上例;

下面是一些使用继承的注意事项:

There are a couple of things to note, however.
• Normal Python name resolution rules apply. If you have multiple base classes that declare a Meta inner class, only the first one will be used. This means the child’s Meta, if it exists, otherwise the Meta of the first parent, etc.
• It’s possible to inherit from both Form and ModelForm simultaneously, however, you must ensure that ModelForm appears first in the MRO. This is because these classes rely on different metaclasses and a class can only have one metaclass.
• It’s possible to declaratively remove a Field inherited from a parent class by setting the name to be None on the subclass.
You can only use this technique to opt out from a field defined declaratively by a parent class; it won’t prevent the ModelForm metaclass from generating a default field. To opt-out from default fields, see Selecting the fields to use.

13.Providing initial values

As with regular forms, it’s possible to specify initial data for forms by specifying an initial parameter when instantiating the form. Initial values provided this way will override both initial values from the form field and values from an attached model instance. For example:

>>> article = Article.objects.get(pk=1)
>>> article.headline
'My headline'
>>> form = ArticleForm(initial={'headline': 'Initial headline'}, instance=article)
>>> form['headline'].value()
'Initial headline'

和通常的form一样, 可以通过指定initial参数的方式初始化表格, 初始值会覆盖form字段中的初始值和模型实例的值;见上例

14.ModelForm factory function 工厂函数

You can create forms from a given model using the standalone function modelform_factory(), instead of using a class definition. This may be more convenient if you do not have many customizations to make:

>>> from django.forms import modelform_factory
>>> from myapp.models import Book
>>> BookForm = modelform_factory(Book, fields=("author", "title"))

This can also be used to make simple modifications to existing forms, for example by specifying the widgets to be used for a given field:

>>> from django.forms import Textarea
>>> Form = modelform_factory(Book, form=BookForm,
... widgets={"title": Textarea()})

The fields to include can be specified using the fields and exclude keyword arguments, or the corresponding attributes on the ModelForm inner Meta class. Please see the ModelForm Selecting the fields to use documentation. . . . or enable localization for specific fields:

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

利用工厂函数可以快速的生成表单而不用进行类的定义, 见上例;

15.formset相关内容待后续补充!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值