django模型表單ModelForm
如果你正在構建一個數據庫驅動的應用,那麼你可能會有與Django的模型緊密對映的表單。比如,你有個BlogComment模型,並且你還想建立一個表單讓大家提交評論到這個模型中。在這種情況下,寫一個forms.Form類,然後在表單類中定義欄位,這種一般建立表單的做法是冗餘的,因為你已經在ORM模型model中定義了欄位的屬性和功能,完全沒必要重新寫一遍欄位。
一、核心用法
基於這個原因,Django提供一個輔助類幫助我們利用Django的ORM模型model建立Form。
像下面這樣:
>>> from django.forms import ModelForm
>>> from myapp.models import Article # 建立表單類 >>> class ArticleForm(ModelForm): ... class Meta: ... model = Article ... fields = ['pub_date', 'headline', 'content', 'reporter'] # 建立一個表單,用於新增文章 >>> form = ArticleForm() # 建立表單修改已有的文章 >>> article = Article.objects.get(pk=1) >>> form = ArticleForm(instance=article)
用法的核心是:
- 首先從django.forms匯入ModelForm;
- 編寫一個自己的類,繼承ModelForm;
- 在新類裡,設定元類Meta;
- 在Meta中,設定model屬性為你要關聯的ORM模型,這裡是Article;
- 在Meta中,設定fields屬性為你要在表單中使用的欄位列表;
- 列表裡的值,應該是ORM模型model中的欄位名。
上面的例子中,因為model和form比較簡單,欄位數量少,看不出這麼做的威力和效率。但如果是那種大型專案,每個模型的欄位數量幾十上百,這麼做的收益將非常巨大,而且後面還有一招提高效率的大殺器,也就是一步儲存資料的操作。
二、 欄位型別
生成的Form類中將具有和指定的模型欄位對應的表單欄位,順序為fields屬性列表中指定的順序。
每個模型欄位有一個對應的預設表單欄位。比如,模型中的CharField表現成表單中的CharField。模型中的ManyToManyField欄位會表現成MultipleChoiceField欄位。下面是完整的對映列表:
模型欄位 | 表單欄位 |
---|---|
AutoField | 在Form類中無法使用 |
BigAutoField | 在Form類中無法使用 |
BigIntegerField | IntegerField,最小-9223372036854775808,最大9223372036854775807. |
BooleanField | BooleanField |
CharField | CharField,同樣的最大長度限制。如果model設定了null=True,Form將使用empty_value |
CommaSeparatedIntegerField | CharField |
DateField | DateField |
DateTimeField | DateTimeField |
DecimalField | DecimalField |
EmailField | EmailField |
FileField | FileField |
FilePathField | FilePathField |
FloatField | FloatField |
ForeignKey | ModelChoiceField |
ImageField | ImageField |
IntegerField | IntegerField |
IPAddressField | IPAddressField |
GenericIPAddressField | GenericIPAddressField |
ManyToManyField | ModelMultipleChoiceField |
NullBooleanField | NullBooleanField |
PositiveIntegerField | IntegerField |
PositiveSmallIntegerField | IntegerField |
SlugField | SlugField |
SmallIntegerField | IntegerField |
TextField | CharField,並帶有widget=forms.Textarea引數 |
TimeField | TimeField |
URLField | URLField |
可以看出,Django在設計model欄位和表單欄位時存在大量的相似和重複之處。
ManyToManyField和 ForeignKey欄位型別屬於特殊情況:
-
ForeignKey被對映成為表單類的django.forms.ModelChoiceField,它的選項是一個模型的QuerySet,也就是可以選擇的物件的列表,但是隻能選擇一個。
-
ManyToManyField被對映成為表單類的django.forms.ModelMultipleChoiceField,它的選項也是一個模型的QuerySet,也就是可以選擇的物件的列表,但是可以同時選擇多個,多對多嘛。
同時,在表單屬性設定上,還有下面的對映關係:
- 如果模型欄位設定blank=True,那麼表單欄位的required設定為False。 否則,required=True。
- 表單欄位的label屬性根據模型欄位的verbose_name屬性設定,並將第一個字母大寫。
- 如果模型的某個欄位設定了editable=False屬性,那麼它表單類中將不會出現該欄位。道理很簡單,都不能編輯了,還放在表單裡提交什麼?
- 表單欄位的
help_text
設定為模型欄位的help_text
。 - 如果模型欄位設定了choices引數,那麼表單欄位的widget屬性將設定成Select框,其選項來自模型欄位的choices。選單中通常會包含一個空選項,並且作為預設選擇。如果該欄位是必選的,它會強制使用者選擇一個選項。 如果模型欄位具有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']
上面的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())
四、ModelForm的驗證
驗證ModelForm主要分兩步:
- 驗證表單
- 驗證模型例項
與普通的表單驗證類似,模型表單的驗證也是呼叫is_valid()
方法或訪問errors屬性。模型的驗證(Model.full_clean())緊跟在表單的clean()方法呼叫之後。通常情況下,我們使用Django內建的驗證器就好了。如果需要,可以重寫模型表單的clean()來提供額外的驗證,方法和普通的表單一樣。
五、ModelForm的欄位選擇
強烈建議使用ModelForm的fields屬性,在賦值的列表內,一個一個將要使用的欄位新增進去。這樣做的好處是,安全可靠。
然而,有時候,欄位太多,或者我們想偷懶,不願意一個一個輸入,也有簡單的方法:
__all__
:
將fields屬性的值設為__all__
,表示將對映的模型中的全部欄位都新增到表單類中來。
from django.forms import ModelForm
class AuthorForm(ModelForm): class Meta: model = Author fields = '__all__'
exclude屬性:
表示將model中,除了exclude屬性中列出的欄位之外的所有欄位,新增到表單類中作為表單欄位。
class PartialAuthorForm(ModelForm):
class Meta: model = Author exclude = ['title']
因為Author模型有3個欄位name、birth_date
和birth_date
,上面的例子會讓title和name出現在表單中。
六、自定義ModelForm欄位
在前面,我們有個表格,展示了從模型到模型表單在欄位上的對映關係。通常,這是沒有什麼問題,直接使用,按預設的來就行了。但是,有時候可能這種預設對映關係不是我們想要的,或者想進行一些更加靈活的定製,那怎麼辦呢?
使用Meta類內部的widgets屬性!
widgets屬性接收一個數據字典。其中每個元素的鍵必須是模型中的欄位名之一,鍵值就是我們要自定義的內容了,具體格式和寫法,參考下面的例子。
例如,如果你想要讓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}), # 關鍵是這一行 }
上面還展示了新增樣式引數的格式。
如果你希望進一步自定義欄位,還可以指定Meta類內部的error_messages
、help_texts
和labels
屬性,比如:
from django.utils.translation import ugettext_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, }
最後,如果你想完全控制一個欄位,包括它的型別,驗證器,是否必填等等。可以顯式地宣告或指定這些性質,就像在普通表單中一樣。比如,如果想要指定某個欄位的驗證器,可以顯式定義欄位並設定它的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']
七、啟用欄位本地化
預設情況下,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__
這個特殊的值,所有的欄位都將本地化。
八、表單的繼承
ModelForms是可以被繼承的。子模型表單可以新增額外的方法和屬性,比如下面的例子:
>>> class EnhancedArticleForm(ArticleForm): ... def clean_pub_date(self): ... ...
以上建立了一個ArticleForm的子類EnhancedArticleForm,並增加了一個clean_pub_date
方法。
還可以修改Meta.fields
或Meta.exclude
列表,只要繼承父類的Meta類,如下所示:
>>> 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'