如果你正在构建一个数据库驱动的应用,那么你应该会有与Django 的模型紧密映射的表单。举个例子,你也许会有个BlogComment 模型,并且你还想创建一个表单让大家提交评论到这个模型中。 在这种情况下,在表单中定义字段将是冗余的,因为你已经在模型中定义了字段。 基于这个原因,Django 提供一个辅助类来让你可以从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)生成的表单类中将具有和指定的模型字段对应的表单字段,顺序为fields 属性中指定的顺序。
每个模型字段有一个对应的默认表单字段。比如,模型中的CharField 表现成表单中的CharField。模型中的ManyToManyField 字段会表现成MultipleChoiceField 字段。
ForeignKey 和 ManyToManyField 字段类型属于特殊情况:
ForeignKey 表示成django.forms.ModelChoiceField,它是一个ChoiceField,其选项是模型的查询集。ManyToManyField 表示成django.forms.ModelMultipleChoiceField,它是一个MultipleChoiceField,其选项是模型的查询集。此外,生成的每个表单字段都有以下属性集:
如果模型字段设置blank=True,那么表单字段的required 设置为False。否则,required=True。表单字段的label 设置为模型字段的verbose_name,并将第一个字母大写。表单字段的help_text 设置为模型字段的help_text。如果模型字段设置了choices,那么表单字段的Widget 将设置成Select,其选项来自模型字段的choices。选项通常会包含空选项,并且会默认选择。如果字段是必选的,它会强制用户选择一个选项。如果模型字段的blank=False 且具有一个显示的default 值,将不会包含空选项(初始将选择default 值)。一个完整的例子:
from django.db import models from django.forms import ModelForm TITLE_CHOICES = ( ('MR', 'Mr.'), ('MRS', 'Mrs.'), ('MS', 'Ms.'), ) class Author(models.Model): name = models.CharField(max_length=100) title = models.CharField(max_length=3, choices=TITLE_CHOICES) birth_date = models.DateField(blank=True, null=True) def __str__(self): # __unicode__ on Python 2 return self.name class Book(models.Model): name = models.CharField(max_length=100) authors = models.ManyToManyField(Author) class AuthorForm(ModelForm): class Meta: model = Author fields = ['name', 'title', 'birth_date'] class BookForm(ModelForm): class Meta: model = Book fields = ['name', 'authors']以上大体等于(唯一不同的是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())验证模型表单主要有两步:
验证表单验证模型实例与普通的表单验证类型类似,模型表单的验证在调用is_valid() 或访问errors 属性时隐式调用,或者通过full_clean() 显式调用,尽管在实际应用中你将很少使用后一种方法。
重写clean() 方法 可以重写模型表单的clean() 来提供额外的验证,方法和普通的表单一样。 模型表单实例包含一个instance 属性,表示与它绑定的模型实例。
与模型验证的交互 作为验证过程的一部分,模型表单将调用与表单字段对应的每个模型字段的clean() 方法。如果你已经排除某些模型字段,这些字段不会运行验证。
模型error_messages 的注意事项 表单字段级别或表单级别的错误信息永远比模型字段级别的错误信息优先。 模型字段的错误信息只用于模型验证步骤引发ValidationError 的时候,且不会有对应的表单级别的错误信息。
每个模型表单还具有一个save() 方法。这个方法根据表单绑定的数据创建并保存数据库对象。模型表单的子类可以用关键字参数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()强烈建议你使用fields 属性显式设置所有将要在表单中编辑的字段。如果不这样做,当表单不小心允许用户设置某些特定的字段,特别是有的字段添加到模型中的时候,将很容易导致安全问题。这些问题可能在网页上根本看不出来,它与表单的渲染方式有关。
另外一种方式是自动包含所有的字段,或者排除某些字段。这种基本方式的安全性要差很多,而且已经导致大型的网站受到严重的利用(例如 GitHub)。
然而,有两种简单的方法保证你不会出现这些安全问题: 1.设置fields 属性为特殊的值’all’ 以表示需要使用模型的所有字段。例如:
from django.forms import ModelForm class AuthorForm(ModelForm): class Meta: model = Author fields = '__all__'2.设置ModelForm 内联的Meta 类的exclude 属性为一个要从表单中排除的字段的列表。例如:
class PartialAuthorForm(ModelForm): class Meta: model = Author exclude = ['title']ModelForm 还提供更多的灵活性,让你可以改变给定的模型字段对应的表单字段的类型和Widget。
使用内部类Meta 的widgets 属性可以指定一个字段的自定义Widget。它是映射字段名到Widget 类或实例的一个字典。 例如,Author 的name 属性为CharField,如果你希望它表示成一个<textarea> 而不是默认的<input type="text">,你可以覆盖字段默认的Widget:
from django.forms import ModelForm, Textarea from myapp.models import Author class AuthorForm(ModelForm): class Meta: model = Author fields = ('name', 'title', 'birth_date') widgets = { 'name': Textarea(attrs={'cols': 80, 'rows': 20}), }在基本的表单里,你可以通过继承ModelForms来扩展和重用他们。当你的form是通过models生成的,而且需要在父类的基础上声明额外的field和method,这种继承是方便的。例如,使用以前的ArticleForm 类:
>>> class RestrictedArticleForm(EnhancedArticleForm): ... class Meta(ArticleForm.Meta): ... exclude = ('body',)作为一个有参数的表单, 在实例化一个表单时可以通过指定initial字段来指定表单中数据的初始值. 这种方式指定的初始值将会同时替换掉表单中的字段和值. 例如:
>>> article = Article.objects.get(pk=1) >>> article.headline 'My headline' >>> form = ArticleForm(initial={'headline': 'Initial headline'}, instance=article) >>> form['headline'].value() 'Initial headline'你可以用单独的函数 modelform_factory() 来代替使用类定义来从模型直接创建表单。这在不需要很多自定义的情况下应该是更方便的。
>>> from django.forms.models import modelform_factory >>> from myapp.models import Book >>> BookForm = modelform_factory(Book, fields=("author", "title"))class models.BaseModelFormSet 与普通表单集一样, 它是Django提供的几个有力的表单集类来简化模型操作。
默认的, 如果你使用model生成formset,formset会使用一个包含模型全部对象的queryset(例如:Author.objects.all()). 你可以使用queryset参数重写这一行为:
>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith='O'))默认情况下,当你使用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)模型表单集与表单集十分类似,假设我们想要提供一个表单集来编辑Author模型实例:
from django.forms.models import modelformset_factory from django.shortcuts import render_to_response 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_to_response("manage_authors.html", { "formset": formset, })在Django模板中有三种方式来渲染表单集。 第一种方式,你可以让表单集完成大部分的工作:
<form method="post" action=""> {{ formset }} </form>其次,你可以手动渲染formset,但让表单处理自己:
<form method="post" action=""> {{ formset.management_form }} {% for form in formset %} {{ form }} {% endfor %} </form>第三,您可以手动呈现每个字段:
<form method="post" action=""> {{ formset.management_form }} {% for form in formset %} {% for field in form %} {{ field.label_tag }} {{ field }} {% endfor %} {% endfor %} </form>