- 定義 ModelForm
- 定制 ModelForm
- 值得一提的一些 Field 轉化
- 初始化 ModelForm
- 校驗 ModelForm
- 儲存 ModelForm 對象
- 定義一個 Form 來新建、更新實例
什么是 ModelForm
- Model 在 Django 對應數據庫模型
- 一個 Model 擁有多個 Model.Field
- Form 在 Django 對應表單
- 一個 Form 擁有多個 Form.Field
ModelForm 即基於 Model 的 Form,把 Model 中的 Field 根據下圖中的映射關系自動轉化為 Form 中的 Field。
為什么使用 ModelForm
利用 Model 生成 Form,提高 Model 復用性
如何使用 ModelForm
定義 ModelForm
舉一個書籍管理例子
# Model |
這個 Model 中定義了兩個字段
- title
- 儲存書籍標題
- 數據類型是 char
- 最大長度 20
- 數據庫唯一值限制,即不能儲存兩本相同標題的書
- author
- 儲存書籍的作者
- 數據類型是外鍵,指向 Model
Author
下面我們用 ModelForm 構建表單
# ModelForm |
和下面手動構建表單的代碼等效
class ArticleForm(forms.Form): |
定制 ModelForm
很多情況下自動生成的 ModelForm 並不能滿足設計要求,下面我們來講一下如何定制
定制有兩種方式
- Meta
- 使用 Model 轉化的時候自定義轉化規則
- 自定義字段
- 定義額外的 Field,會覆蓋 Model 自動生成的 Field
Meta
ModelForm 是通過 Meta 來把 Model.Field 自動轉化為 Form.Field 的,其中涉及到幾步轉化
- validators 不變
- 添加 widget 屬性
- 即前端的渲染方式
- 修改 Model 包含的字段
- 通過 fields 來拿指定字段
- 通過 exclude 來排除指定字段
- 修改錯誤信息
我們通過下面的例子來看一下如何通過 Meta 來定制 ModelForm
class ArticleForm(forms.ModelForm): |
Meta 的缺點是不能修改字段的 validators,如果需要自定義 validators,需要在 Meta 外部重新定義一個同名 Field 來覆蓋自動生成的 Field
在 Form 中另外定義 Field
這是 Form 中定義 Field 的通用方法,在 ModelForm 中它有兩個作用
- 補充 Model 沒有的 Field 到 Form
- 覆蓋 Model 中的同名 Field 定義
且看下面的例子,Article 中已經包含了 title 字段,我們在 ModelForm 中重新定義了它,把 CharField 改為了 ChoiceField,並且自定義了 validators。
覆蓋 title 的時候,把 title 從 Meta 中 exclude 掉是可選的,去不去掉的區別在於,你是否需要它為你校驗
unique=True
這個數據庫級限制。
在這里我們需要校驗,因為 ModelForm 校驗通過后我需要把它存入數據庫,如果這里沒有校驗的話,碰到同標題的書數據庫就會在儲存時報錯,我們希望把這步校驗放在 ModelForm 的校驗中,而不是在通過校驗后再用try... catch...
來捕獲它。
class ArticleForm(forms.ModelForm): |
值得一提的一些 Field 轉化
AutoField
該 Field 不會出現在 ModelForm 表單中。
所有
editable=False
的 Field 都不會出現在 ModelForm 中。
BooleanField
由於表單提交時統一識別為 string,而 BooleanField
是用 python 中的 bool
來判斷的,所以只要傳了任意非空值,BooleanField
都會當做 True 來處理,而如果傳了空值,由於 forms.Field
默認屬性是 required=True
,會校驗失敗,所以如果你需要一個可以填 False 的 Field,那么你需要在 Form 中手動設置這個 Field 的 required=False
。
ForeignKey
ForeignKey 自動轉化為 ModelChoiceField,用下拉選項菜單渲染,默認渲染出來的選項顯示為對應 Field 的 __str__
,提交的值為對應 Field 的 id
,這些都可以定制。
在后端接收提交的時候會自動在對應的 Model 中用 id 去找,如果沒找到則拋出 ValidationError。
ManyToManyField
ManyToManyField 自動轉化為 ModelMultipleChoiceField,用多選框渲染,同樣默認渲染出來的選項顯示為對應 Field 的 __str__
,提交的值為對應 Field 的 id
值。
比如有個叫 group 的 ManyToManyField,選中了 'finance'
'develop'
這兩個選項,他們的 id 分別為 1 和 2,那么世界上提交的表單 QueryString 就是 group=1&group=2
初始化 ModelForm
form = ArticleForm(request.POST) |
article = Article.objects.get(pk=1) |
- instance
- 給 ModelForm 初始化 Model 實例,后續的操作都作用在這個實例上
- initial
- 給 ModelForm 初始值
- 如果和 instance 同時被定義,同名 field 的值覆蓋 instance 中的值
數據加載的先后順序為 instance, initial, request.POST
校驗 ModelForm
Form 只會檢查內部定義過的 Field,request.POST 中其余 keyword 都會被無視和過濾掉,即不會出現在返回的 cleaned_data 中。
form = ArticleForm(request.POST) |
is_valid()
會調用 full_clean()
來對表單進行全面校驗,它又分成三步(定義在基類 Form 中)
- 根據每個 Field 注冊的 validators 做單個 Field 的校驗 (比如 title 字段就會校驗是否超出最大允許長度 20) 其中在
Field.clean()
執行過后提供了鈎子clean_[field_name]
,可以自定義該 function 來注冊自己的校驗方法。 - 根據 Form 定義的 Field 之間的依賴關系做整個表單的校驗,鈎子為
clean()
,默認為空。 - 自定義校驗通過后的表單處理,鈎子為
_post_clean()
- 這一步中,ModelForm 做了一些額外的檢驗:如果定義在 Meta 中的 Field 有
unique=True
這個限制,那么 ModelForm 會按照現有數據庫中的數據對其校驗,看這個 Field 的值是否已存在,如果已存在,則拋出一個IntegrityError
。實際操作中如果強制不校驗 unique 的話,可以把該字段從 Meta 中移除,在 ModelForm 中重新定義該字段。
- 這一步中,ModelForm 做了一些額外的檢驗:如果定義在 Meta 中的 Field 有
儲存 ModelForm 對象
調用 save()
的時候可以傳入 commit=False
來避免立即儲存,從而通過后續的修改或補充來得到完整的 Model 實例后再儲存到數據庫。
如果初始化的時候傳入了 instance,那么調用 save()
的時候會用 ModelForm 中定義過的字段值覆蓋綁定實例的相應字段,並寫入數據庫。
save()
同樣會幫你儲存 ManyToManyField,如果 save 時使用了 commit=False
,那么 ManyToManyField 的儲存需要等該條目存入數據庫之后手動調用 ModelForm 的 save_m2m()
方法。
定義一個 Form 來新建、更新實例
通常的步驟分為如下幾步
- 檢測該對象是否已在數據庫
- 如果已存在,那么手動獲取該實例,然后更改相關 field 內容,最后使用
update()
方法保存到數據庫 - 如果不存在,新建一個 Model 實例並修改至完整的 Model,調用
save()
方法保存到數據庫
- 如果已存在,那么手動獲取該實例,然后更改相關 field 內容,最后使用
寫成代碼的話是這樣子
f = AuthorForm(request.POST) |
太麻煩了!!其實 Django 中已經有 update_or_create
方法已經實現了上述所有功能,可以避免這個 try ... except ...
判斷實例是否已存在,我們來看這個例子
# forms.py |
# views.py |
其中 update_or_create
通過檢測所有非 defaults 的字段,在上述例子中就是 id=form.cleaned_data['id']
一項是否已存在於數據庫而判斷是用 update()
還是 create()
,而不論是 update()
還是 create()
,都會使用 cleaned_data 作為數據源來寫入數據庫。