1. 程式人生 > >django模型表單ModelForm

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) 

用法的核心是:

  1. 首先從django.forms匯入ModelForm;
  2. 編寫一個自己的類,繼承ModelForm;
  3. 在新類裡,設定元類Meta;
  4. 在Meta中,設定model屬性為你要關聯的ORM模型,這裡是Article;
  5. 在Meta中,設定fields屬性為你要在表單中使用的欄位列表;
  6. 列表裡的值,應該是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_datebirth_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_messageshelp_textslabels屬性,比如:

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.fieldsMeta.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'