1. 程式人生 > >Django forms組件【對form舒心l了】

Django forms組件【對form舒心l了】

include 想想 ctype \n 賦值 使用 chan lean 在哪裏

目錄

  • Django forms組件
  • bound and unbound form instance
  • forms渲染有關
    • 隱藏一個字段,不渲染它
  • form 校驗
  • form類
    • ModelForm
    • 利用ModelForm關鍵就在於model‘s field mapping to form‘s field
    • ModelForm.save() 詳解
    • class Meta !!!重寫覆蓋默認的modelField字段(即自定義一些modelform屬性)
    • form有關多選擇Field的使用
  • form‘s fields are themselves classes
    • Field class
    • 將form校驗錯誤信息改為中文。
    • BoundField class
    • FileField /ImageField /DateField
  • API
  • widgets class
  • rendering form error messages
  • 定義自己form 實例 包括自定義 局部和全局 鉤子
  • reusable form templates
  • 遺留問題
  • 碰到的錯誤
  • 總結

Django forms組件

Handle (掌控)一個form是非常復雜的工程,需要做很多功能:不同的類型的數據要有不同的渲染;校驗數據;獲取檢驗後的幹凈數據,並將數據反序列化為相應數據類型如時間對象;保存傳遞給處理程序等等。Django的forms組件就完成了這些復雜的工作,提供方便的操作form的接口API給我們。

Form對象有很多的API,參考本文API段落。其實每個API都是對應了Form的一個特點。如:form.auto_id 對應就是設置form中表單標簽的id屬性;form.errors 對應了form的校驗和錯誤信息。等等。官方文檔在講解Form對象API時,也是按照form的功能和特點,來分類介紹每種API的。

bound and unbound form instance

綁定數據的form實例和沒綁定數據的form實例,他們之間的區別是非常重要的,這影響到了,同一個api或者屬性,在templates 引擎渲染和weight作用時所表現出來的內容是不同的。
一個form instance 要麽綁定要麽沒綁定

  • 綁定了數據的: 可以調用is_valid等校驗api;並且通過該實例可以渲染出html。並且包括inline error messages 校驗失敗的錯誤可以渲染到表單後,已提示表單提交用戶。
  • 沒綁定數據的:不能校驗(因為沒綁定數據),but it can still render the blank form as html但是它還是能渲染空白內容的表單空間。沒有綁定的form是沒有cleaned_data屬性的。訪問的話會拋出異常。
  • 什麽是綁定數據行為?通過form類實例化form對象時,需要提供一個字典類型(映射類型)的數據作為第一個位置參數,如{‘name‘:‘ZJQ‘, ‘age‘: 300} 或 request.POST等。這樣初始化的form對象就是綁定了數據的form實例,即使提供一個空的{} 也算是提供了。 沒有提供這樣一個參數,則實例化出來的是一個沒有綁定數據的form實例。
  • 綁定數據的form對象或者沒有綁定數據的form對象,可以改變其綁定數據值或者添加綁定數據嗎?答案是:NO!. 一旦一個Form 實例對象創建了,要知道它的數據是immutable不可改變的,無論是綁定還是非綁定數據from對象。

forms渲染有關

註意:form對象叠代出來的數據類型。form對象是可叠代的對象,叠代出的是boundfield對象。form對象又是字典類型對象,key是字段名,value是boundfield對象。所以要獲取boundfield對象有兩種途徑,通過for叠代,或者通過字典key訪問。以下的field名字沒特殊說明,都是boundfield對象。至於獲取boundfield對象,剛剛也提到了。下面就來使用它的屬性和方法吧:

  1. field.label 是label值,不包括label標簽
  2. field.label_tag() 就是一個返回label標簽的方法,包含了label值;在渲染標簽是指定參數attrs={‘class‘:‘foo‘} 就能指定標簽css class,還可以指定label_suffix=‘::’ 來設置添加label值的後綴.
  3. form相當於整個表單,打印form對象就是一個HTML字符串。
  4. field 打印就是一個表單控件的HTML字符串。
  5. form是可以叠代的,叠代出就是boundfield對象。叠代順序就是form定義的field的順序。如果要訪問某個具體的定義form時的field對象(非boundfield對象)通過form.fields[‘字段名‘] 可以得到。通過boundfield.field也可以拿到對應的字段對象。(區分form定義時的字段對象,和實例化後的boundfield對象)
  6. 關於檢驗失敗的錯誤信息: 通過field.errors拿到。這個拿到的是一個錯誤集合(或者說錯誤列表),通過訪問改錯誤列表才能拿到錯誤。其它拿錯誤的方式也是一樣的。
  7. forms對象在template中的渲染是不會有<\form>標簽的。因為form不止可以渲染成表單,還可以渲染成table({{ form.as_table }});如下:
There are other output options though for the <label>/<input> pairs:

{{ form.as_table }} will render them as table cells wrapped in <tr> tags
{{ form.as_p }} will render them wrapped in <p> tags
{{ form.as_ul }} will render them wrapped in <li> tags
相應的,都必須自己提供table或這ul
  1. field.id_for_label 這個是獲取label應該設置的對應input的id。
  2. field.errors 打印的話會渲染表單錯誤為一個無序列表,列表的ul會有一個class=‘errorlist‘ ,這個需要用戶來定義這個 css class 應該這樣顯示。 由於這個其實是一個錯誤列表,所以循環來自己渲染錯誤,通過循環叠代,拿到具體的錯誤字符串。
  3. form.non_field_errors()表示表單校驗時的非field錯誤,即全局鉤子錯誤或自己添加的錯誤。
  4. field.value() 就可以拿到表單具體的value所對應的值或非綁定設置的初始化值。
  5. 在python代碼中打印form對象都是由<\tr><\th>包裹的,而template中使用是沒有這些標簽包裹的。
  6. form.errors 是一個字典(區別對比field.errors),包含所有字段的錯誤,key就是字段名,對應的value是一個錯誤列表。特別註意一個全局鉤子的錯誤放在一個key叫做‘__all__‘中。註意獲取form.errors就會觸發form的校驗,類似is_valid() 觸發一樣。同時校驗過程也只會發生過一次,對於一個form對象。form.errors有很多的接口,可以獲取為json字符串form.errors.as_json();參考:https://docs.djangoproject.com/en/2.0/ref/forms/api/#django.forms.Form.errors
  7. 更多模版中渲染有關可以參考:https://docs.djangoproject.com/en/2.0/topics/forms/#looping-over-the-form-s-fields
  8. form 渲染時的註意事項:
    技術分享圖片

隱藏一個字段,不渲染它

pass

form 校驗

所謂校驗,就是綁定到form對象的數據,校驗其是否符合定義的約束條件。
關於校驗方面,要明白的點:

  1. 對於綁定了數據的form對象,可以進行校驗其綁定的數據是否匹配form定義的字段的類型和其它約束。form_obj.is_valid() 和 form_obj.errors 的調用執行 都會隱式觸發form_obj的校驗過程;而想顯示的調用校驗可以通過from_obj.full_clean()。要明白一個form的校驗過程只會進行一次,校驗過了就不會在校驗了,直接返回結果。所以form_obj.is_valid() 和 form_obj.errors 誰先調用,誰觸發有且僅有一次的校驗過程。如果有單個字段對象,可以測試調用單個字段對象的clean(校驗數據) 方式,參數就是要校驗的數據。
  2. 校驗數據完後,結果無非兩種情況:a) 校驗通過,django對校驗過了的form_obj,會將通過了的數據放入form_obj.cleaned_data 字典中。b) 校驗不通過, django對這沒通過的form_obj 也會將部分校驗通過的放入from_obj.cleaned_data 。對於校驗失敗了的字段,會將錯誤存放到form_obj.errors 字典中,字典{‘字段名1‘:[‘錯誤信息1‘,‘錯誤信息2‘]}。
  3. 問題:綁定了數據了的字段,會校驗哪些方面?第一,定義字段時的一些約束;第二,局部鉤子;第三,全局鉤子;所以錯誤信息的字典,主鍵key是字段,錯誤信息是一個列表。而全局不是單個字段的,所以Key是不是一個字段名而是‘__all__’ 作為字典key。
  4. 沒校驗通過的form_obj可以用於渲染,將錯誤信息和驗證過的信息都渲染到form表單中,不會出現form表單沒有校驗通過,就將部分校驗通過的數據也清空掉,會保留校驗通過的數據,只清空沒有通過的字段的數據。而且沒通過的錯誤信息還用於渲染到表單頁面中,提醒提交表單的用戶錯誤。
  5. 除了form.errors存放全部的校驗錯誤信息外。每個field對象也有一個errors屬性,裏面存放了字段對象的錯誤信息,是以一個list列表存放的。
  6. form校驗的錯誤信息的返回格式還可以有多種,有form.errros.as_json()得到一個json字符串,特別是對於ajax提交的form數據,響應錯誤通過這種方式。
  7. form錯誤是會有一個ul標簽來組織錯誤信息的。
  8. 可以通過直接實例化一個Field對象,通過調用其clean(傳入值) 來校驗數據是否符合。

form類

繼承關系:
技術分享圖片

  1. from django import forms 導入模塊
  2. 繼承Form類,構造一個自己的表單類。類似於Models類,django通過model操作數據庫表。Form對象這是一個表單對象,通過該對象來操縱表單處理的過程,如校驗表單字段,渲染表單字段。主要就對這兩方面進行操縱。
  3. 關於提交的表單數據的校驗,提供了自定義全局和局部鉤子,提供了豐富的內置Field類和其對應的widget來約束表單提交的數據。(插曲:所謂鉤子,就是訪問入口規定好了,我們就添加入口裏面的東西就可以了)
  4. 局部鉤子註意獲取到校驗值,進行校驗後,符合要返回該值,不符合拋出一個指定的異常 ValidationError 異常
  5. 全局鉤子主要用於每個空間的值都局部校驗後,進行一個全局校驗,比如兩次密碼是否一樣。這樣就不必在從clean_data取出來比較了。如果校驗成功過,註意返回的是clean_data,失敗同樣拋出ValidationError異常。全局校驗錯誤信息是存在form.errrors 的__all__的一個key對應的列表中。
  6. is_valid clean_name errors
  7. 關於渲染表單 form為每個field提供了相對應的一個默認widget。當然也可以自定義,在定義form字段是,可以帶入參數widget指定widget類或該類的實例對象。如果傳入的是widget類,那麽會自動實例一個默認的widget對象用於字段渲染。如果傳入的是實例,就按照實例的渲染方式進行渲染。
  8. 表單渲染主要就是field對應的widget的作用。當然內置的多種widget都可以傳入相同的參數來改變渲染效果,如attrs={‘class‘:‘form-control‘} 就會給相應標簽添加屬性。
  9. 表單渲染添加css class可以通過widget。而<\label> 和 錯誤 通過定義form類是添加類屬性 error_css_class 和 required_css_class 明天實驗這兩個hook鉤子????
  10. 其實還是不要用完整的,就用他們的label值和錯誤值,只用field的渲染就好了。
  11. ValidationError導入使用from django.coreexceptions import ValidationError
  12. 內置widget都在forms模塊中。

form 的實例,可以是空,也可以提前填充數據。歸納總結form實例化數據主要來自三個方面:

  1. 來自model instance
  2. 來自其它數據源
  3. 來自用戶提交的表單數據。這種情況通常給用戶一個空form後,用戶提交,如果無效,再返回一個綁定了數據的form給用戶。

ModelForm

出現modelform 這種form類的情況是這樣的:
如果你正在開發基於數據庫的web app, 很有可能, 你會創建一個forms 是幾乎映射到一個django models的。例如,你可能有一個BlogComment model, 然後,你想創建一個form 讓用戶通過這個form提交博客評論到BlogComment model的表中。在這個例子中,定義form的field types是一種很冗余的做法(django 哲學之避免冗余),因為你已經定義了model的field type了,可以復用給form用。

因為這個原因, django 提供了一個很有幫助的 class 可以讓我們創建一個Form class 通過一個django 的model。這樣就復用了django model 中的field的定義。

代碼實例:

>>> from django.forms import ModelForm
>>> from myapp.models import Article  # 導入自己建好的django model  

# 創建form class
>>> class ArticleForm(ModelForm):
...     class Meta:
...         model = Article
...         fields = [‘pub_date‘, ‘headline‘, ‘content‘, ‘reporter‘]

# 創建一個form 用於添加文章
>>> form = ArticleForm()

# 創建一個form 用於改變一個存在的文章
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(instance=article)  # 

# 上面創建的兩個form都可以用於渲染到模版中,分別用於新增和修改。

小結:註意這裏modelform和普通form實例化的不同。這裏傳入一個model object instance作為將用於初始化顯示的數據。也可以像普通 form一樣,傳入initail參數。如果兩個參數都傳遞了的話,那麽就變成了第三種情況,不過initial會覆蓋instance的初始化。
如果實例化綁定數據時,提供了instance參數,那麽在save時就是一個update操作數據庫。如果只是給了一個類字典的數據沒有instance,那麽就是insert新增數據到數據庫。
所以,在實例化modelform時,instance參數除了會影響save()的行為,還會影響初始化參數initail的效果。

利用ModelForm關鍵就在於model‘s field mapping to form‘s field

每一個model field 有一個與之對應的缺省form field。例如, 一個在model中的CharField 被表示為 一個在form中的CharField. 而一個model ManyToManyField 被表示為 一個form的ModelMultipleChoiceField.

缺省對應關系如下圖:
技術分享圖片

正如你所想的,ForeignKey 和 ManyToManyField model field 類型是特殊情況(OneToOne這則不會有這樣的特殊情況):

  • ForeignKey 通過django.forms.ModelChoiceField所表示,這個實際是一個Choice Field,特殊是它的choices 是一個model QuerySet 也就是一個查詢出的queryset結果。對於這種ModelChoiceField。在modelform對象層面和ModelChoiceField層面,進行數據綁定和數據clean()校驗是不同的。modelform層面實例化是要提供一個queryset作為代替choice參數,利用queryset生成choices。

  • ManyToManyField 通過django.forms.ModelMutipleChoiceField 所表示,這個實際是一個MultipleChoiceField,只是它的choices參數是變為了queryset參數,提供一個queryset對象。因為這個queryset對象可以構建出choice。
  • 至於兩種Field在校驗後,會將單個model對象(對於ModelChoiceField) 和 多個model對象(對於ModelMutipleChoiceField) 存入到cleand_data中。供後續使用。
  • 而且這兩類Field對象,就有了queryset屬性,這是一個queryset對象。通過這個queryset的API就可以得到對應關聯的model的信息了。如:BoundField.field.queryset.model 就是model class了。這個是訪問關聯model的重要途徑了。
  • ModelMutipleChoiceField和ModelChoiceField 對應的choice顯示調用的queryset中model對象的__str__() 方法的結果,所以model定義時,約定都是要定義__str()__方法。
  • ModelMutipleChoiceField和ModelChoiceField都有一個可選參數,empty_label 主要用於控制對應select表單的一個空白選項的顯示。默認是‘-------------‘

此外,每一個通過model方式生成的modelform field 會設置如下屬性:

  1. 如果 model field 有 blank=True, 相應的form將設置required=False。否則,required=True.
  2. 這個繼承自ModelForm的form的field 的label屬性會被設置為model field的verbose_name屬性,並且值將是首字母大寫
  3. 而help_text 屬性值兩者都有,就一一對應了。
  4. 如果 model field 有choices 屬性設置,這是fomr field‘s widget 將被設置為Select,該form字段的choices將來自model字段的choices。這個choices 一般會包括一個blank choice代表的是model的默認值。如果field是required,將強制用戶做出選擇。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).

一個完整的實例定義ModelForm:

# model的 
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) # 註意model和form的字段中都有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‘]

ModelForm.save() 詳解

  • modelform由於和model產生了關系,所以多出了api來操作model。比如說form.save()可以保存或者更新form數據到數據庫中。這裏研究研究。這是modelform非常重要的一點,因為我們得到form正確數據後,是要同步到數據庫中的,我們不可能將數據再一一ORM操作到數據庫中。所以對於form提交的數據提供了這個save() 同步操作到數據庫中。但是要註意對應數據庫的新增修改操作,在save時是有不同邏輯的,你自己想想也是,如果是提交的數據和庫中存量的數據有約束沖突,那必須解決這個沖突;至於新增就簡單了直接插入insert就可以了。
  • save() 創建並保存一個model object instance (利用綁定到該modelform的數據)。綁定到modelform數據有兩種方式,一種是普通方式,一種是綁定一個相同model類的實例對象(這種方式多用於修改視圖的表單)。對於普通方式,save() 將創建一個new instance of the specificed model 也就是利用提供的數據,實例出一個model object,然後save就會保存新增加一個。對於綁定了對象的方式,如果提供了數據且提供了instance實例,這是update這個實例對象。對於普通模式在ORM操作時出現了主鍵等沖突,就會save()操作報錯。
  • 通過modelform的save操作對應的model對象,關更新操作在實例化modelform時必須帶上instance參數指明是更新的哪個model 對象數據,不然會編程新增,失去了想要修改操作的意圖。如form(request.POST, instance=model_obj) 這樣實例化modelform表單對象才行。
  • 自定義集成成ModelForm的類,其class Meta中的fields是使用哪些對應model的字段應用到modelform中。如果fields = ‘__all__‘就是全部model字段應用到。
  • save() 接受一個可選的參數commit,參數取值可以是True or False,如果是False,那麽方法這個save方法會返回一個model 對象,而不會同步到數據庫中,這是就只有手動調用model對象的save() 方法去同步到數據庫中。這樣就提供了一個方式,可以修改對象,再提交到庫中。還有一種情況,如果有一個manytomany字段,創建對象,建立關系可能需要先構建關聯表中的數據後,才能保存。這時候可以調用modelform.save_m2m()方法保存對象並建立關系數據到中間表。對於commit=True,就沒有上面說的兩種情況,就直接同步數據庫中。

小結:modelform初始化時可以使用initial初始化數據嗎?可以的,如果還提供了instance參數用於初始化的話,那麽initial優先於instance參數中的值。
modelform的方法和屬性除了增加save和save_m2m區別之外,其它和普通form對象API一樣。

class Meta !!!重寫覆蓋默認的modelField字段(即自定義一些modelform屬性)

通過class Meta可以定義覆蓋默認的一些modelField的元素。
大致在Meta中的屬性有:
model = 映射的model class
fields = [‘fieldname1‘, ‘fieldname2‘...] 全部可以設置為[‘__all__‘]
widgets = {‘fieldname‘: widget_obj,...}
labels = {‘fieldname‘:label_value} 設置渲染時的label值
help_text = {‘fieldname‘: help_string}
error_messages = {‘fieldname‘: {‘校驗code‘:錯誤信息}} 通過這個可以改變錯誤信息為自定義中文
field_classes = {‘fieldname‘: FieldClass}

如果要完全覆蓋一個字段,就在modelfrom中建立一個字段的定義就會完全覆蓋modelform默認生成的。

參考:https://docs.djangoproject.com/en/2.0/topics/forms/modelforms/#overriding-the-default-fields

form有關多選擇Field的使用

form表單中可以使用input-checkbox 和 select-option 及 input-radio 來實現多值或者提供選擇項給用戶展示。
form表單基本上可以對應數據庫中一個表的一條數據。因為數據庫中一個表的數據可能關聯到其它表的數據(就是常說的manyTomany,manyToone,oneToone)。要通過form表單,操控數據庫一條數據,那麽表單就要有展示或者操控數據關系的方式。這種方式就是表單的上面提到的三種表單控件了。

再看回django的form組件。主要就是ChoiceField/ModelChoiceField/ModelMultipleChoiceField的使用。
三者的區別:

  • ChoiceField對應參數choice,就是要提供一個choice參數。
  • ModelChoiceField對應的是一個queryset參數。**主要利用的是queryset中model對象的pk和對象的__str__的輸出。**這個很重要,開始在使用時,要提供一個queryset參數。
  • ModelMultipleChoiceField對應的也是queryset參數,類似ModelChoiceField只不其widget是一個MultipleSelectWidget罷了。

上面三種Field對應的表單控件默認都是Select,
而對於要使用input-check,就要給字段重新賦值widget參數為一個Check類型的widget。

form‘s fields are themselves classes

Field class

Field類實例化對象時,核心參數就五個:

  1. required 是否是必須有數據。用於校驗
  2. label 表單的的貼條。主要用於貼示 數據是什麽信息。默認是字段名。
  3. widget 主要是表單的渲染,和部分校驗。
  4. initial 初始化數據。用於初始化默認值。為後續has_changed()提供對比依據。
  5. help_text
    通過form訪問Field對象:form.fields[‘field名字‘]

將form校驗錯誤信息改為中文。

由於錯誤提示校驗是分類的,每種類型字段有哪幾種校驗錯誤,可以到官網查詢https://docs.djangoproject.com/en/2.1/ref/forms/fields/#built-in-field-classes。
知道要改變哪種類型的錯誤提示後,就在定義field是設置error_messages={‘錯誤類型‘: 錯誤信息!}

BoundField class

這個BoundField 類 ,主要用於展示HTML 或者 用於訪問form實例的一個Field對象 屬性。

BoundField Used to display HTML or access attributes for a single field of a Form instance.
通過BoundField.field 訪問到Field對象。
這個類的__str__() 就是展示 字段的HTML。所以打印BoundField對象就輸出了HTML。
通過form訪問BoundField對象,可以遍歷,也可通過字典key操作,因為form是一個類字典的類型。key就是字段字符串啦。

form中的field負責管理表單數據和表單數據的校驗當一個表單被提交後。

FileField /ImageField /DateField

和其它的Field不同,有兩個特別的Field類型:DateField類與FileField(類似於model中的FileField和ImageField字段比較特別,因為都涉及到文件對象)

在前端頁面,需要通過form上傳文件,就需要確定form標簽的enctype定義了正確的值“multipart/form-data” 現代瀏覽器對於有文件的上傳都會使用這種編碼。
這樣,才能使用正確的格式編碼 form表單中的文件對象和其它數據 到http body中,然後通過http協議傳輸到服務端,服務端也能正確通過編碼方式進行解碼,才能正確解析出文件對象和其它數據。

  • DateField https://docs.djangoproject.com/en/2.0/ref/forms/fields/#django.forms.DateField

    在normalized轉化到python 對象時,需要特別註意該字段。這個字段會將用戶表單中填入的字符串,轉化為date對象。(用戶的鍵入只能是字符串形式)。
    這個轉化過程肯定也是要有依據的,得按照依據規則來,不可能用戶隨便輸入什麽字符都能轉換換成date對象是吧。所以這個字段在初始化時,需要一個可選參數就是input_formats。提供專業的to_python和to_html 的格式。提供了這些格式,用戶輸入的時間字符串,就需要按照列表中的格式化提供時間字符串。同時,綁定了值的渲染到頁面也是按照其中的格式來的。由於這個時間格式的表示範式,全球各地是不同的,所以會根據整個django項目的F10N參數,來判定默認的input_formats規則是什麽。如果F10N=True ,那麽input_formats只能用全球同一認可的格式。如下:
    [‘%Y-%m-%d‘, # ‘2006-10-25‘
    ‘%m/%d/%Y‘, # ‘10/25/2006‘
    ‘%m/%d/%y‘] # ‘10/25/06‘
    如果F10N=False,那麽就會有更多的格式支持。
    [‘%b %d %Y‘, # ‘Oct 25 2006‘
    ‘%b %d, %Y‘, # ‘Oct 25, 2006‘
    ‘%d %b %Y‘, # ‘25 Oct 2006‘
    ‘%d %b, %Y‘, # ‘25 Oct, 2006‘
    ‘%B %d %Y‘, # ‘October 25 2006‘
    ‘%B %d, %Y‘, # ‘October 25, 2006‘
    ‘%d %B %Y‘, # ‘25 October 2006‘
    ‘%d %B, %Y‘] # ‘25 October, 2006‘

  • ImageField

    對於該字段,實例化時除了帶入request.POST外,還需要request.FILES. 也就是要通過form 來handle 上傳的文件,需要將文件綁定到form相應的imagefield。

  • FileField https://docs.djangoproject.com/en/2.0/ref/forms/fields/#django.forms.FileField

    對於該字段,實例化時除了帶入request.POST外,還需要request.FILES. 也就是要通過form 來handle 上傳的文件,需要將文件綁定到form相應的filefield。

FileField可選參數max_length限制文件對象的文件名。allow_empty_file,文件內容可以為空。

  • 由於FiledField 和ImageField處理類似,這裏就已ImageField的form綁定 上傳文件為例,來演示實例化這一個form:
# Bound form with an image field
>>> from django.core.files.uploadedfile import SimpleUploadedFile
>>> data = {‘subject‘: ‘hello‘,
...         ‘message‘: ‘Hi there‘,
...         ‘sender‘: ‘[email protected]‘,
...         ‘cc_myself‘: True}
>>> file_data = {‘mugshot‘: SimpleUploadedFile(‘face.jpg‘, <file data>)}
## 這裏的file_data中的value是一個SimpleuploadedFile對象,對象實例,提供了文件的名字和文件的句柄作為參數。
>>> f = ContactFormWithMugshot(data, file_data)

而在實戰中,request為我們handle好了像SimpleuploadFile對象,就是request.FILES.

f = ContactFormWithMugshot(request.POST, request.FILES)

問題來了,那如果是一個modelform呢?怎麽實例化這樣一個帶有文件對象的modelform?答:同普通form一樣,多帶入一個request.FILES。
modelform 是哪個字段映射到FileField字段呢?
答:也是form的FileField對應
那實例化提供了SimpleuploadFile對象後,有怎麽通過save()保存到對應的數據庫表中記錄呢?就算不是modelform,普通的form,是怎麽將上傳的文件保存在哪裏呢?
猜測,這些可能就是UploadFile對象封裝了這些繁瑣的事情了吧?後續驗證。

API

特別提醒:註意將form instance api 與 bound field api 對比查看。
form instance api

  1. form.has_change() 返回True或False。和初始化對比變化。
  2. form.fields是一個字典類型,存放這form定義時的field對象,註意不是boundfield對象(也就不是叠代form對象的產物)。
  3. 打印form 輸出一個HTML
  4. form.label_suffix 設置每個label值後面跟什麽字符串,默認是冒號‘:’
  5. form[‘字段名‘] 得到對應的BoundField對象。
  6. form.fields[‘字段名‘] 得到對應的Field對象。
  7. form.is_valid() 返回bool值 True校驗通過(包括自定義鉤子);Flase校驗失敗。
  8. form.add_error(field, error) 給參數指定的字段添加錯誤。如果字段是校驗過的,添加錯誤會將字段數據從form.cleaned_data中刪除。
  9. form.errors.as_json() 返回json字符串格式的錯誤信息
  10. form.has_error(field, code=None) 判定字段是否有指定code的錯誤。
  11. form.non_field_errors() 返回 全局鉤子校驗失敗的錯誤信息列表或者通過form.has_error(None,‘...) 添加的。記住這個方法,對於容易忘記全局鉤子錯誤的KEY是什麽的人,是福利。
  12. form.cleaned_data 得到校驗幹凈的數據,數據會格式化為對應的python對象類型。
  13. form.initial 是初始化數據字典。註意初始化是是不會將form變為綁定數據的form的。
  14. form.error_css_class 屬性定義是在form中的類屬性,主要是給每行的<\tr>標簽添加錯誤信息時的class。
  15. form.required_css_class 屬性定義是在form中的類屬性,主要就是給<\label>標簽添加css class
  16. form.auto_id 有三種值,True,Flase,String。這個主要控制label標簽for屬性 和 input等標簽中的id屬性的。如果是True,值就會是字段名。如果是False就不會有id屬性。如果是‘id_for_%s‘ 這一類的格式化字符串,那麽%s會被字段名替換,構成一個id值給標簽屬性中用。
  17. form.use_required_attribute 設置為True這默認全部都有required屬性,如果是False默認全部都沒有required屬性。但是對單個字段定義時的required是沒有影響的。
  18. form.field_order 設置一個列表,加入字段來定義渲染是字段的順序。
  19. form.order_fields(field_order) 通過這個api可以隨時改變form中字段的順序。
  20. form.is_multipart() 返回True是 說明form中有文件類字段,False這是普通form.

bound field api

  1. boundfield.has_change()
  2. boundfield.field 而遍歷form時得到的foundfield對象也可以得到form定義時的field對象,就是boundfield.field。
  3. 打印boundfield 輸出一個HTML
  4. boundfield.label_suffix 設置當前label值後面跟什麽字符串,默認是冒號‘:’
  5. boundfield.value() 返回當前字段的值,綁定了數據的,就返回綁定了的值;不然返回初始化時提供的值,再不然就返回一個None。
  6. boundfield.auto_id 設置field自己的id形式。
  7. boundfield.data 類似boundfield.value() 方法,都是返回對應的數據。區別是,只有綁定數據才會有值,其它任何情況都是None.小結就是:value()拿到渲染後能看到的值。data拿到綁定了的值。
  8. boundfield.errors 是一個類列表對象。這就到了__str__ 和__repr__ 的區別了.打印的話會call str方法,輸出html字符串。只是值的化就是走repr,打印出來就是一個字典字面值。這個同form.errors是一樣的。
  9. boundfield.form 就是boundfield所在的form對象
  10. boundfield.help_text 就是field定義是的help_text.如果有定義,可以在渲染是放在input後面作為提示幫助信息。
  11. boundfield.html_name
  12. boundfield.id_for_label 對應字段的id屬性值。用於自定義<\label>標簽
  13. boundfield.is_hidden 判定是否是隱藏字段
  14. boundfield.label 字段的label自,默認是字段名
  15. boundfield.name field字段的名字。
  16. boundfield.label_tag(contents=None, attrs=None, label_suffix=None)
  17. boundfield.as_hidden()
  18. boundfield.as_widget()
  19. boundfield.css_classes()

小結:對比後發現,無論是has_changed, as_table ,initail參數等。這些都是form 與 boundfield之間的關系。如form level 的調用會去調用boundfield的對應的調用,has_changed();還是form level的initial初始化字典,影響field的初始化。就連form.as_table / form.as_ul 等,對應都是boundfield的 as_...方式的調用。還有,如果整個form容器裏面的boundfield對象都要改變的屬性或者特點,那麽通過form instance api 進行容器空間全局控制,如果是單個boundfield的改變,就通過對象自己,同時會覆蓋form全局的設置。

widgets class

每種類型的Field class 都有一個默認對應的Widget class。
為什麽這樣設計?為什麽不Field class 把這個幹完呢?
因為一個Field class可以有多種Widget表現,即Field 可以和Widget任意匹配,這樣能適應的情況就更多了,不然就有超多的細分類型的Field class。同時也可以把一些功能解耦出去。
後面用的多了再總結這一part

rendering form error messages

django官方也一直沒定下怎麽渲染表單驗證錯誤信息。

關於設置錯誤校驗錯誤信息為中文:
pass 就是通過error_messages

定義自己form 實例 包括自定義 局部和全局 鉤子

from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError


wid_01 = widgets.TextInput(attrs={"class":"form-control"})
wid_02 = widgets.PasswordInput(attrs={"class":"form-control"})
wid_03 = widgets.EmailInput(attrs={"class":"form-control"})

class RegForms(forms.Form):
    username = forms.CharField(min_length=4, widget=wid_01)
    password = forms.CharField(min_length=4, widget=wid_01)
    r_password = forms.CharField(min_length=4, widget=wid_01)
    email = forms.EmailField(widget=wid_03)
    telephone = forms.CharField(required=False,widget=wid_01)
    school = forms.ChoiceField(choices=[(‘male‘, ‘男‘), (‘female‘, ‘女‘)])
    age = forms.BooleanField()
    area = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple(attrs={‘class‘: ‘checkbox-inline‘}), choices=((‘china‘, ‘中國‘), (‘america‘, ‘美國‘), (‘english‘, ‘英國‘)))

    def clean_telephone(self):
        val = self.cleaned_data.get(‘telephone‘)
        if len(val) == 11 :
            return val
        else:
            raise ValidationError(‘手機號格式錯誤!‘)

    def clean(self):
        pwd = self.cleaned_data.get(‘password‘)
        r_pwd = self.cleaned_data.get(‘r_password‘)

        if pwd and r_pwd:
            if pwd == r_pwd :
                return self.cleaned_data
            else:
                raise ValidationError(‘兩次密碼不相同!‘)
        return self.cleaned_data

註意:這裏拋出錯誤是不規範,也是官方不推薦的,官方推薦拋入ValidationError方式,參考:https://docs.djangoproject.com/en/2.1/ref/forms/validation/#raising-validationerror

reusable form templates

使用include和 include 。。。 with form = comment_form

遺留問題

  1. 查看form類的元類 DeclarativeFieldsMetaclass
  2. 怎麽給label標簽添加class屬性,或者只有取label值。以解決,第一form對象的required_css_class屬性設置。或者定義form類是添加required_css_class類屬性。

碰到的錯誤

  1. form.is_valid()是一個方法不是一個屬性,千萬不要忘記後加上()呀。因為is_valid()調用後才會有clean_data數據(其實獲取form.errors也會產生clean_data數據)。
  2. 有關前端的遺漏只是點,就是form標簽有一個屬性叫 novalidate .加上它之後,前端就不會對required表單進行提示不能為空。添加它的作用,主要是用於方便測試後端form對象對表單的校驗空值的功能,而不是前端就提示了。

總結

  • 發現form就是容器,存放field對象。form和field看成兩個Level。很多時候form有的方法,field對象也有。如都有has_changed();都可以設置initial參數等。
  • form的校驗數據,不僅僅是校驗,還有清洗數據的作用,比如將提交的字符串,轉換成對應field類型的數據對象。如日期字符串,通過cleaned_data後,得到的是一個datetime.date的對象。
  • 我覺得,在定義form類時,字段賦值的是一個如CharField的對象。這個對象有包含了Widget對象。也就是CharField對象主要用於校對和渲染的功能。而form實例化後,form叠代出來的是一個叫bounfield的對象,這個對象應該是綁定數據的一個對象,這個boundfield對象是包裹了CharField對象數據的。通過boundfield對象是不能改變其綁定的數據的,但是渲染是可以改變的,就可以通過改變CharField對象的屬性,來改變最後利用boundfield對象渲染的輸出。如:
You can access the fields of Form instance from its fields attribute:

>>> for row in f.fields.values(): print(row)
...
<django.forms.fields.CharField object at 0x7ffaac632510>
<django.forms.fields.URLField object at 0x7ffaac632f90>
<django.forms.fields.CharField object at 0x7ffaac3aa050>
>>> f.fields[‘name‘]
<django.forms.fields.CharField object at 0x7ffaac6324d0>
You can alter the field of Form instance to change the way it is presented in the form:

>>> f.as_table().split(‘\n‘)[0]
‘<tr><th>Name:</th><td><input name="name" type="text" value="instance" required></td></tr>‘
>>> f.fields[‘name‘].label = "Username"  ## 這裏修改了CharField對象,最後使用影響了渲染。雖然影響不了綁定的數據。
>>> f.as_table().split(‘\n‘)[0]
‘<tr><th>Username:</th><td><input name="name" type="text" value="instance" required></td></tr>‘

技術分享圖片

官方文檔也說明了boundfield就是wraps包裹form定義的field的。作用就是展示出

  • 所以說要特別註意在form中,說道的field是class定義時的field,還是實例化後form對象叠代出的boundfield對象。form和boundfield兩個不同level都是可以得到class定義時的field對象的;通過改變class定義時的field對象的屬性,可以影響最後form渲染的效果。
  • 有個心得:Model class 的屬性是Field對象(特別是關聯Field對象)。Model instance 的屬性是值或者Manager管理器;Form class 的屬性是Field對象。Form instance 叠代出來的是BoundField對象。

Django forms組件【對form舒心l了】