HTML表單
在HTML
中缰犁,表單是<form>...</form>
之間元素的集合淳地,它們允許訪問者輸入文本怖糊、選擇選項、操作對象等等颇象,然后將信息發(fā)送回服務(wù)器伍伤。
某些表單的元素---文本框和復(fù)選框---非常簡單而且內(nèi)建于HTML
本身。其它的表單會復(fù)雜些:例如彈出一個日期選擇對話框的界面遣钳、允許你移動滾動條的界面扰魂、使用JavaScript
和CSS
以及HTML
表單元素<input>
來實(shí)現(xiàn)操作控制的界面。
與<input>
元素一樣蕴茴,一個表單必須指定兩樣?xùn)|西:
- 目的地:響應(yīng)用戶輸入數(shù)據(jù)的
URL
劝评; - 方式: 發(fā)送數(shù)據(jù)所使用的
HTTP
方法;
例如倦淀,Django Admin
站點(diǎn)的登錄表單包含幾個<input>
元素:type='text'
用于用戶名付翁,type='password'
用于密碼,type='submit'
用于Log in
按鈕晃听。它還包含一些用戶看不到的隱藏的文本字段,Django
使用它們來決定下一步的行為砰识。
它還告訴瀏覽器表單數(shù)據(jù)應(yīng)該發(fā)往<form>
的action
屬性指定的URL---/admin/
能扒,而且應(yīng)該使用method
屬性指定的HTTP
方法---post
。
當(dāng)觸發(fā)<input type='submit' value='Log in'>
元素時辫狼,數(shù)據(jù)將發(fā)送給/admin/
初斑。
GET和POST
處理表單時只會用到GET
和POST
方法。
Django
的登錄表單使用POST
方法膨处,在這個方法中瀏覽器組合表單數(shù)據(jù)见秤,對它們進(jìn)行編碼以用于傳輸,將它們發(fā)送到服務(wù)器然后接收它的響應(yīng)真椿。
相反鹃答,GET
組合提交的數(shù)據(jù)為一個字符串,然后使用它來生成一個URL
突硝。這個URL
將包含數(shù)據(jù)發(fā)送的地址以及數(shù)據(jù)的鍵和值测摔。
GET
和POST
用于不同的目的。
用于改變系統(tǒng)狀態(tài)的請求---例如解恰,給數(shù)據(jù)庫帶來變化的請求---應(yīng)該使用POST
锋八。GET
只應(yīng)該用于不會影響系統(tǒng)狀態(tài)的請求。
Django在表單中的角色
處理表單是一件很復(fù)雜的事情护盈⌒矗考慮一下Django
的Admin
站點(diǎn),不同類型的大量數(shù)據(jù)項需要在一個表單中準(zhǔn)備好腐宋、渲染成HTML
紊服、使用一個方便的界面編輯檀轨、返回給服務(wù)器、驗(yàn)證并清除围苫,然后保存或者向后繼續(xù)處理裤园。
Django
的表單功能可以簡化并自動化大部分這些工作,并且還可以比大部分程序自己所編寫的代碼更安全剂府。
Django
會處理表單工作中的三個顯著不同的部分:
- 準(zhǔn)備數(shù)據(jù)拧揽、重構(gòu)數(shù)據(jù),以便下一步渲染腺占;
- 為數(shù)據(jù)創(chuàng)建
HTML
表單淤袜; - 接收并處理客戶端提交的表單和數(shù)據(jù);
可以手工編寫代碼來實(shí)現(xiàn)衰伯,但是Django
可以幫你完成所有這些工作铡羡。
Django中的表單
HTML
的<form>
只是其機(jī)制的一部分。
在一個Web
應(yīng)用中意鲸,‘表單’可能是指HTML <form>
烦周、或者生成它的Django
的Form
、或者提交時發(fā)送的結(jié)構(gòu)化數(shù)據(jù)怎顾、或者這些部分的總和读慎。
Django的Form類
表單系統(tǒng)的核心部分是Django
的Form
類。Django
的模型描述一個對象的邏輯結(jié)構(gòu)槐雾、行為以及展現(xiàn)給我們的方式夭委,與此類似, Form
類描述一個表單并決定它如何工作和展現(xiàn)募强。
就像模型類的屬性映射到數(shù)據(jù)庫的字段一樣株灸,表單類的字段會映射到HTML
的<input>
表單的元素(ModelForm
通過一個Form
映射模型類的字段到HTML
表單的<input>
元素。Django
的Admin
站點(diǎn)就是基于這個)擎值。
表單的字段本身也是類慌烧,它們管理表單的數(shù)據(jù)并在表單提交時進(jìn)行驗(yàn)證。DateField
和FileField
處理的數(shù)據(jù)類型差別很大鸠儿,必須完成不同的事情杏死。
表單字段在瀏覽器中呈現(xiàn)給用戶的是一個HTML
的'widget'
---用戶界面的一個片段。每個字段類型都有一個合適的默認(rèn)Widget
類捆交,需要時可以覆蓋淑翼。
實(shí)例化、處理和渲染表單
在Django
中渲染一個對象時品追,我們通常:
- 在視圖中獲得它(例如玄括,從數(shù)據(jù)庫中獲取);
- 將它傳遞給模板上下文;
- 使用模板變量將它擴(kuò)展為
HTML
標(biāo)記語言
當(dāng)我們在視圖中處理模型實(shí)例時肉瓦,我們一般從數(shù)據(jù)庫中獲取它遭京。當(dāng)我們處理表單時胃惜,一般在視圖中實(shí)例化它。
構(gòu)建一個表單
在Django中構(gòu)建一個表單
Form類
我們已經(jīng)計劃好了我們的HTML
表單應(yīng)該呈現(xiàn)的樣子哪雕。在Django
中船殉,我們的起始點(diǎn)是這里:
forms.py
from django import forms
class NameForm(forms.Form):
your_name = forms.CharField(label='Your name', max_length=100)
它定義一個Form
類,只帶有一個字段(your_name)
斯嚎。我們已經(jīng)對這個字段使用一個友好的標(biāo)簽利虫,當(dāng)渲染時它將出現(xiàn)在<label>
中。
Form
的實(shí)例具有一個is_valid()
方法堡僻,它為所有的字段運(yùn)行驗(yàn)證程序糠惫。當(dāng)調(diào)用這個方法時,如果所有的字段都包含合法的數(shù)據(jù)钉疫,它將:
返回
True
硼讽;-
將表單的數(shù)據(jù)放到
cleaned_data
屬性中;
完整的表單牲阁,第一次渲染時固阁,看上去將像:<label for='your_name'>Your name:</label> <input id='your_name' type='text' name='your_name' maxlength='100' required/>
注意它不包含<form>
標(biāo)簽和提交按鈕,我們必須自己在模板中手動提供它們城菊。
視圖
發(fā)送給Django
網(wǎng)站的表單數(shù)據(jù)通過一個視圖處理备燃,一般和發(fā)布這個表單的是同一個視圖。這允許我們重用一些相同的邏輯役电。
我們需要在URL
對應(yīng)的視圖中實(shí)例化我們將要發(fā)布的表單。
views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect
from .forms import NameForm
def get_name(request):
#if this is a POST request we need to process the form data
if request.method == 'POST':
#create a form instance and populate it with data from the request:
form = NameForm(request.POST)
#check whether it's valid:
if form.is_valid():
#process the data in form.cleaned_data as required
#...
#redirect to a new URL
return HttpResponseRedirect('/thanks/')
#if a GET(or any other method) we will create a blank form
else:
form = NameForm()
return render(request, 'name.html', {'form' : form})
如果訪問視圖的是一個GET
請求棉胀,它將創(chuàng)建一個空的表單實(shí)例并將它放到要渲染的模板上下文中法瑟。只是我們在第一次訪問該URL
時預(yù)期發(fā)生的情況。
如果表單的提交使用POST
請求唁奢,那么視圖將再次創(chuàng)建一個表單實(shí)例并使用請求中的數(shù)據(jù)填充它:form = NameForm(request.POST)
霎挟。這叫做“綁定數(shù)據(jù)至表單”(它現(xiàn)在是一個綁定的表單)。
我們調(diào)用表單的is_valid()
方法:如果它不為True
麻掸,我們將帶著這個表單返回到模板酥夭。這時表單不再為空(未綁定),所以HTML
表單將用之前提交的數(shù)據(jù)填充脊奋,然后可以根據(jù)要求編輯并改正它熬北。
如果is_valid()
為Ture
,我們將能夠在cleaned_data
屬性中找到所有合法的表單數(shù)據(jù)诚隙。在發(fā)送HTTP
重定向給瀏覽器告訴它下一步的去向之前讶隐,我們可以用這個數(shù)據(jù)來更新數(shù)據(jù)庫或者做其它處理。
模板
我們不需要在name.html
模板中做很多工作久又,最簡單的例子是:
<form action="/your-name/" method="post">
{%csrf_token%}
{{form}}
<input type='submit' value="Submit" />
</form>
根據(jù){{form}}
巫延,所有的表單字段和它們的屬性將通過Django
的模板語言拆分成HTML標(biāo)記語言效五。
現(xiàn)在我們有一個可以工作的網(wǎng)頁表單,它通過Django Form
描述炉峰、通過視圖處理并渲染成一個HTML <form>
畏妖。
Django Form類詳解
所有的表單類都作為django.forms.Form
的子類創(chuàng)建,包括你在Django
管理站點(diǎn)中遇到的ModelForm
疼阔。
模型和表單
實(shí)際上戒劫,如果你的表單打算直接用來添加和編輯Django
的模型,ModelForm
可以節(jié)省你的許多時間竿开、精力和代碼谱仪,因?yàn)樗鼘⒏鶕?jù)Model
類構(gòu)建一個表單以及適當(dāng)?shù)淖侄魏蛯傩浴?/p>
綁定的和未綁定的表單實(shí)例
綁定的和未綁定的表單之間的區(qū)別非常重要:
- 未綁定的表單沒有關(guān)聯(lián)的數(shù)據(jù)。當(dāng)渲染給用戶時否彩,它將為空或者包含默認(rèn)的值疯攒。
- 綁定的表單具有提交地數(shù)據(jù),因此可以用來檢驗(yàn)數(shù)據(jù)是否合法列荔。如果渲染一個不合法的綁定的表單敬尺,它將包含內(nèi)聯(lián)的錯誤信息,告訴用戶如何糾正數(shù)據(jù)贴浙。
表單的is_bound
屬性將告訴你一個表單是否具有綁定的數(shù)據(jù)砂吞。
字段詳解
考慮一個比上面的迷你示例更有用的一個表單,在一個網(wǎng)站上實(shí)現(xiàn)“contact me”功能:
forms.py
from django import forms
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField(widget=forms.Textarea)
sender = forms.EmailField()
cc_myself = forms.BooleanField(required=False)
我們前面的表單只使用一個字段your_name
崎溃,它是一個CharField
蜻直。在這個例子中,我們的表單具有四個字段:subject袁串、message概而、sender和cc_myself。共用到三種字段類型:CharField囱修、EmailField和BooleanField赎瑰。
窗口小部件
每個表單字段都有一個對應(yīng)的Widget
類,它對應(yīng)一個HTML
表單Widget
破镰,例如<input type='text'>
餐曼。
在大部分情況下,字段都具有一個合理的默認(rèn)Widget
鲜漩。例如源譬,默認(rèn)情況下,CharField
具有一個TextInput Widget
孕似,它在HTML
中生成一個<input type='text'>
瓶佳。如果你需要<textarea>
,在定義表單字段時你應(yīng)該指定一個合適的Widget
鳞青,例如我們定義的message
字段霸饲。
字段的數(shù)據(jù)
不管表單提交的是什么數(shù)據(jù)为朋,一旦通過調(diào)用is_valid()
成功驗(yàn)證(is_valid()
返回True
),驗(yàn)證后的表單數(shù)據(jù)將位于form.cleaned_data
字典中厚脉。這些數(shù)據(jù)已經(jīng)為Python
的類型习寸。
注意,此時傻工,依然可以從request.POST
中直接訪問到未驗(yàn)證的數(shù)據(jù)霞溪,但是訪問驗(yàn)證后的數(shù)據(jù)更好一些。
下面是在視圖中如何處理表單數(shù)據(jù):
views.py
from django.core.mail import send_mail
if form.is_valid():
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
sender = form.cleaned_data['sender']
cc_myself = form.cleaned_data['cc_myself']
recipients = ['info@example.com']
if cc_myself:
recipients.append(sender)
send_mail(subject, message, sender, recipient)
return HttpResponseRedirect('/thanks/')
有些字段類型需要一些額外的處理中捆。例如鸯匹,使用表單上傳的文件需要不同地處理(它們可以從request.FILES
獲取,而不是request.POST
)泄伪。
使用表單模板
你需要做的就是將表單實(shí)例放進(jìn)模板的上下文殴蓬。如果你的表單在Context中叫做form,那么{{form}}將正確的渲染它的<label>和<input>元素蟋滴。
表單渲染的選項
不要忘記染厅,表單的輸出不包含<form>標(biāo)簽和表單的submit按鈕,必須自己提供它們津函。
對于<label>/<input>對肖粮,還有幾個輸出選項:
- {{form.as_table}}以表格的形式將它們渲染在<tr>標(biāo)簽中;
- {{form.as_p}}將它們渲染在<p>標(biāo)簽中尔苦;
- {{form.as_ul}} 將它們渲染在<li>標(biāo)簽中涩馆;
注意,你必須自己提供<table>或<ul>
元素允坚。
下面是ContactForm
實(shí)例的輸出{{form.as_p}}
魂那。
<p><label for="id_subject">Subject:</label>
<input id="id_subject" type="text" name="subject" maxlength="100" required /></p>
<p><label for="id_message">Message:</label>
<textarea name="message" id="id_message" required></textarea></p>
<p><label for="id_sender">Sender:</label>
<input type="email" name="sender" id="id_sender" required /></p>
<p><label for="id_cc_myself">Cc myself:</label>
<input type="checkbox" name="cc_myself" id="id_cc_myself" /></p>
每個表單字段具有一個ID屬性并設(shè)置為id_<field-name>。
手工渲染字段
我們沒有必要非要讓Django來分拆表單的字段:如果我們喜歡屋讶,我們可以手工來做(這樣允許你重新對字段排序)冰寻。每個字段都是表單的一個屬性须教,可以使用{{form.name_of_field}}訪問皿渗,并將在Django模板中正確地渲染。例如:
{{ form.non_field_errors }}
<div class="fieldWrapper">
{{ form.subject.errors }}
<label for="{{ form.subject.id_for_label }}">Email subject:</label>
{{ form.subject }}
</div>
<div class="fieldWrapper">
{{ form.message.errors }}
<label for="{{ form.message.id_for_label }}">Your message:</label>
{{ form.message }}
</div>
<div class="fieldWrapper">
{{ form.sender.errors }}
<label for="{{ form.sender.id_for_label }}">Your email address:</label>
{{ form.sender }}
</div>
<div class="fieldWrapper">
{{ form.cc_myself.errors }}
<label for="{{ form.cc_myself.id_for_label }}">CC yourself?</label>
{{ form.cc_myself }}
</div>
完整的<label>元素還可以使用label_tag()
生成轻腺,例如:
<div class="fieldWrapper">
{{ form.subject.errors }}
{{ form.subject.label_tag }}
{{ form.subject }}
</div>
渲染表單的錯誤信息
當(dāng)然乐疆,這個便利性的代價是更多的工作。直到現(xiàn)在贬养,我們沒有擔(dān)心如何展示錯誤信息挤土,因?yàn)镈jango已經(jīng)幫我們處理好。在下面的例子中误算,我們將自己處理每個字段的錯誤和表單整體的各種錯誤仰美。注意迷殿,表單和模板頂部的{{form.non_field_errors}}
查找每個字段的錯誤。
使用{{form.name_of_field.errors}}顯示表單錯誤的一個清單咖杂,并渲染成一個ul庆寺。看上去可能像:
<ul class="errorlist">
<li>Sender is required.</li>
</ul>
這個ul有一個errorlist的CSS樣式表诉字,你可以用它來定義外觀懦尝。如果你希望進(jìn)一步自定義錯誤信息的顯示,你可以迭代它們來實(shí)現(xiàn):
{% if form.subject.errors %}
<ol>
{% for error in form.subject.errors %}
<li><strong>{{ error|escape }}</strong></li>
{% endfor %}
</ol>
{% endif %}
迭代表單字段
如果你為你的每個表單字段使用相同的HTML壤圃,你可以使用{%for%}循環(huán)迭代每個字段來減少重復(fù)的代碼陵霉。
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}
迭代隱藏和可見的字段
如果你正在手工布局模板中的一個表單,而不是依賴Django 默認(rèn)的表單布局伍绳,你可能希望將<input type="hidden"> 字段與非隱藏的字段區(qū)別對待踊挠。例如,因?yàn)殡[藏的字段不會顯示墨叛,在該字段旁邊放置錯誤信息可能讓你的用戶感到困惑 —— 所以這些字段的錯誤應(yīng)該有區(qū)別地來處理止毕。
Django 提供兩個表單方法,它們允許你獨(dú)立地在隱藏的和可見的字段上迭代:hidden_fields() 和visible_fields()漠趁。下面是使用這兩個方法對前面一個例子的修改:
{# Include the hidden fields #}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{# Include the visible fields #}
{% for field in form.visible_fields %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
這個示例沒有處理隱藏字段中的任何錯誤信息屈留。通常,隱藏字段中的錯誤意味著表單被篡改问窃,因?yàn)檎5谋韱翁顚懖粫淖兯鼈兏熬H欢阋部梢院苋菀椎貫檫@些表單錯誤插入一些錯誤信息顯示出來甥绿。
可重用的表單模板
如果你的網(wǎng)站在多個地方對表單使用相同的渲染邏輯字币,你可以保存表單的循環(huán)到一個單獨(dú)的模板中來減少重復(fù),然后在其它模板中使用include標(biāo)簽來重用它:
# In your form template:
{% include "form_snippet.html" %}
# In form_snippet.html:
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
如果傳遞到模板上下文中的表單對象具有一個不同的名稱共缕,你可以使用include標(biāo)簽的with參數(shù)來對它起個別名:
{% include "form_snippet.html" with form=comment_form %}
綁定的表單和未綁定的表單
表單要么是綁定的洗出,要么是未綁定的。
- 如果是綁定的图谷,那么能夠驗(yàn)證數(shù)據(jù)翩活,并渲染表單及其數(shù)據(jù)成HTML。
- 如果是未綁定的便贵,那么它不能夠完成驗(yàn)證(因?yàn)闆]有可驗(yàn)證的數(shù)據(jù))菠镇,但是仍然能渲染成空白的表單成HTML。
class Form
若要創(chuàng)建一個未綁定的表單實(shí)例承璃,只需要簡單地實(shí)例化該類:
>>>f = ContactForm()
若要綁定數(shù)據(jù)到表單利耍,可以將數(shù)據(jù)以字典的形式傳遞給表單類的構(gòu)造函數(shù)的第一個參數(shù):
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True}
>>> f = ContactForm(data)
在這個字典中,鍵為字段的名稱,它們對應(yīng)于表單類中的屬性隘梨。值為需要驗(yàn)證的數(shù)據(jù)程癌。它們通常為字符串,但是沒有強(qiáng)制要求必須是字符串轴猎。傳遞的數(shù)據(jù)類型取決于字段席楚。
Form.is_bound
如果在運(yùn)行時你要區(qū)分綁定的表單和未綁定的表單,可以檢查下表單is_bound屬性的值税稼。
>>> f = ContactForm()
>>> f.is_bound
False
>>> f = ContactForm({'subject': 'hello'})
>>> f.is_bound
True
注意烦秩,傳遞一個空的字典將創(chuàng)建一個帶有空數(shù)據(jù)的綁定的表單:
>>> f = ContactForm({})
>>> f.is_bound
True
如果你有一個綁定的表單實(shí)例但是想修改下數(shù)據(jù),或者你想綁定一個未綁定的表單到某些數(shù)據(jù)郎仆,你需要創(chuàng)建另外一個表單列表只祠。Form實(shí)例的數(shù)據(jù)沒有辦法修改。表單實(shí)例一旦創(chuàng)建扰肌,你應(yīng)該將它的數(shù)據(jù)視為不可變的抛寝,無論它有沒有數(shù)據(jù)。
使用表單來驗(yàn)證數(shù)據(jù)
Form.clean()
當(dāng)你需要為相互依賴的字段添加自定義的驗(yàn)證時曙旭,你可以實(shí)現(xiàn)表單的clean()方法盗舰。
Form.is_valid()
表單對象的首要任務(wù)就是驗(yàn)證數(shù)據(jù)。對于綁定的表單實(shí)例桂躏,可以調(diào)用is_valid()方法來執(zhí)行驗(yàn)證并返回一個表示數(shù)據(jù)是否合法的布爾值钻趋。
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
True
讓我們試下非法的數(shù)據(jù)。下面的情形中剂习,subject 為空(默認(rèn)所有字段都是必需的)且sender 是一個不合法的郵件地址:
>>> data = {'subject': '',
... 'message': 'Hi there',
... 'sender': 'invalid email address',
... 'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
False
Form.errors
訪問errors屬性可以獲得錯誤信息的一個字典蛮位。
>>> f.errors
{'sender': ['Enter a valid email address.'], 'subject': ['This field is required.']}
在這個字典中,鍵為字段的名稱鳞绕,值為表示錯誤信息的Unicode字符串組成的列表失仁。錯誤信息保存在列表中是因?yàn)樽侄慰赡苡卸鄠€錯誤信息。
你可以訪問errors而不必須先調(diào)用is_valid()们何。表單的數(shù)據(jù)將在第一次調(diào)用is_valid()或者訪問errors時驗(yàn)證萄焦。
驗(yàn)證只會調(diào)用一次,無論你訪問errors或者調(diào)用is_valid()多少次冤竹。這意味著拂封,如果驗(yàn)證過程有副作用,這些副作用將只觸發(fā)一次贴见。
Form.non_field_errors()
這個方法返回Form.errors中不是與特定字段相關(guān)聯(lián)的錯誤烘苹。它包含在Form.clean()中引發(fā)的ValidationError和使用Form.add_error(None,'...')添加的錯誤躲株。
動態(tài)的初始值
Form.initial
表單字段的初始值使用initial聲明片部。例如,你可能希望使用當(dāng)前會話的用戶名填充username字段。
使用Form的initial參數(shù)可以實(shí)現(xiàn)档悠。該參數(shù)是字段名到初始值的一個字典廊鸥。只需要包含你期望給出初始值的字段,不需要包含表單中的所有字段辖所。例如:
>>> f = ContactForm(initial={'subject': 'Hi there!'})
這些值只顯示在沒有綁定的表單中惰说,即使沒有提供特定值它們也不會作為后備的值。
注意缘回,如果字段有定義initial,而實(shí)例化表單時也提供initial吆视,那么后面的initial將優(yōu)先。在下面的例子中酥宴,initial在字段和表單實(shí)例化中都有定義啦吧,此時后者具有優(yōu)先權(quán)。
>>> from django import forms
>>> class CommentForm(forms.Form):
... name = forms.CharField(initial='class')
... url = forms.URLField()
... comment = forms.CharField()
>>> f = CommentForm(initial={'name': 'instance'}, auto_id=False)
>>> print(f)
<tr><th>Name:</th><td><input type="text" name="name" value="instance" required /></td></tr>
<tr><th>Url:</th><td><input type="url" name="url" required /></td> </tr>
<tr><th>Comment:</th><td><input type="text" name="comment" required /></td></tr>
檢查表單數(shù)據(jù)是否改變
Form.has_changed()
當(dāng)你需要檢查表單的數(shù)據(jù)是否從初始數(shù)據(jù)發(fā)生改變時拙寡,可以使用表單的has_changed()方法授滓。
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True}
>>> f = ContactForm(data, initial=data)
>>> f.has_changed()
False
當(dāng)提交表單時,我們可以重新構(gòu)建表單并提供初始值肆糕,這樣可以實(shí)現(xiàn)比較:
>>> f = ContactForm(request.POST, initial=data)
>>> f.has_changed()
如果request.POST中的數(shù)據(jù)與inital中的不同般堆,has_changed()將為True,否則為False诚啃。計算的結(jié)果是通過調(diào)用表單每個字段的Field.has_changed()得到的淮摔。
訪問'clean'的數(shù)據(jù)
Form.cleaned_data
表單類中的每個字段不僅負(fù)責(zé)驗(yàn)證數(shù)據(jù),還負(fù)責(zé)‘清潔’它們---將它們轉(zhuǎn)換為正確的格式始赎。這是個非常好用的功能噩咪,因?yàn)樗试S字段以多種方式輸入數(shù)據(jù),并總能得到一致的輸出极阅。
一旦你創(chuàng)建一個表單實(shí)例并通過驗(yàn)證后胃碾,你就可以通過它的cleaned_data屬性訪問清潔的數(shù)據(jù):
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}
如果你的數(shù)據(jù)沒有通過驗(yàn)證,cleaned_data字典中只包含合法的字段:
>>> data = {'subject': '',
... 'message': 'Hi there',
... 'sender': 'invalid email address',
... 'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
False
>>> f.cleaned_data
{'cc_myself': True, 'message': 'Hi there'}
cleaned_data 始終只 包含表單中定義的字段筋搏,即使你在構(gòu)建表單 時傳遞了額外的數(shù)據(jù)仆百。在下面的例子中,我們傳遞一組額外的字段給ContactForm 構(gòu)造函數(shù)奔脐,但是cleaned_data 將只包含表單的字段:
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True,
... 'extra_field_1': 'foo',
... 'extra_field_2': 'bar',
... 'extra_field_3': 'baz'}
>>> f = ContactForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data # Doesn't contain extra_field_1, etc.
{'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'}
當(dāng)表單合法時俄周,cleaned_data 將包含所有字段的鍵和值,即使傳遞的數(shù)據(jù)不包含某些可選字段的值髓迎。在下面的例子中峦朗,傳遞的數(shù)據(jù)字典不包含nick_name 字段的值,但是cleaned_data 任然包含它排龄,只是值為空:
>>> from django import forms
>>> class OptionalPersonForm(forms.Form):
... first_name = forms.CharField()
... last_name = forms.CharField()
... nick_name = forms.CharField(required=False)
>>> data = {'first_name': 'John', 'last_name': 'Lennon'}
>>> f = OptionalPersonForm(data)
>>> f.is_valid()
True
>>> f.cleaned_data
{'nick_name': '', 'first_name': 'John', 'last_name': 'Lennon'}
在上面的例子中波势,cleaned_data 中nick_name 設(shè)置為一個空字符串,這是因?yàn)閚ick_name 是CharField而 CharField 將空值作為一個空字符串。每個字段都知道自己的“空”值 —— 例如尺铣,DateField 的空值是None 而不是一個空字符串拴曲。關(guān)于每個字段空值的完整細(xì)節(jié),參見“內(nèi)建的Field 類”一節(jié)中每個字段的“空值”提示凛忿。
表單必填行和錯誤行的樣式表
Form.error_css_class
Form.required_css_class
將必填的表單行和有錯誤的表單行定義不同的樣式表特別常見澈灼。例如,你想將必填的表單行以粗體顯示店溢,將錯誤以紅色顯示叁熔。
表單類具有一對鉤子,可以使用它們來添加class屬性給必填的行或者有錯誤的行:只需要簡單地設(shè)置Form.error_css_class或 Form.required_css_class屬性:
from django import forms
class ContactForm(forms.Form):
error_css_class = 'error'
required_css_class = 'required'
# ... and the rest of your fields here
一旦你設(shè)置好床牧,將根據(jù)需要者疤,設(shè)置行的"error" 或"required" CSS類型。 其HTML 看上去將類似:
>>> f = ContactForm(data)
>>> print(f.as_table())
<tr class="required"><th><label class="required" for="id_subject">Subject:</label> ...
<tr class="required"><th><label class="required" for="id_message">Message:</label> ...
<tr class="required error"><th><label class="required" for="id_sender">Sender:</label> ...
<tr><th><label for="id_cc_myself">Cc myself:<label> ...
>>> f['subject'].label_tag()
<label class="required" for="id_subject">Subject:</label>
>>> f['subject'].label_tag(attrs={'class': 'foo'})
<label for="id_subject" class="foo required">Subject:</label>
從模型創(chuàng)建表單
ModelForm
如果你正在構(gòu)建一個數(shù)據(jù)庫驅(qū)動的應(yīng)用叠赦,那么你應(yīng)該會有與Django的模型緊密映射的表單驹马。舉個例子,你也許會有個BlogComment模型除秀,并且你還想創(chuàng)建一個表單讓大家提交評論到這個模型中糯累。在這種情況下,在表單中定義定義字段是冗余的册踩,因?yàn)槟阋呀?jīng)在模型中定義了字段泳姐。
基于這個原因,Django提供一個輔助類來讓你從Django的模型創(chuàng)建表單暂吉。例如胖秒,
>>> 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)
字段類型
生成的表單類中將具有和指定的模型字段對應(yīng)的表單字段。字段順序?yàn)閒ields屬性中指定的順序慕的。
每個模型字段有一個對應(yīng)的默認(rèn)表單字段阎肝。比如,模型中的CharField對應(yīng)表單中的CharField肮街。模型中的ManyToManyField字段會表現(xiàn)成MultipleChoiceField字段风题。
模型表單的驗(yàn)證
驗(yàn)證模型表單主要有兩步:
- 驗(yàn)證表單;
- 驗(yàn)證模型實(shí)例嫉父;
與普通的表單驗(yàn)證類似沛硅,模型表單的驗(yàn)證在調(diào)用is_valid()或者訪問errors屬性時隱式調(diào)用,或者通過full_clean()顯式調(diào)用绕辖,盡管在實(shí)際應(yīng)用中很少使用后一種方法摇肌。
模型的驗(yàn)證Model.full_clean()在表單驗(yàn)證這一步的內(nèi)部觸發(fā),緊跟在表單的clean()方法調(diào)用之后仪际。
save()方法
每個模型表單還具有一個save()方法围小。這個方法根據(jù)表單綁定的數(shù)據(jù)創(chuàng)建并保存數(shù)據(jù)庫對象昵骤。模型表單的子類可以用關(guān)鍵字參數(shù)instance接收一個已經(jīng)存在的模型實(shí)例。如果提供吩抓,save()將更新這個實(shí)例。如果沒有提供赴恨,save()將創(chuàng)建模型的一個新實(shí)例疹娶。
>>> 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()
注意,如果表單沒有驗(yàn)證伦连,save()調(diào)用將通過檢查form.errors來進(jìn)行驗(yàn)證雨饺。如果表單中的數(shù)據(jù)不合法,將引發(fā)ValuelError惑淳。
save() 接受一個可選的commit 關(guān)鍵字參數(shù)额港,其值為True 或False。如果save() 時commit=False歧焦,那么它將返回一個還沒有保存到數(shù)據(jù)庫的對象移斩。這種情況下,你需要調(diào)用返回的模型實(shí)例的save()绢馍。這是很有用的向瓷,如果你想在保存之前自定義一些處理了,或者你想使用特定的模型保存選項舰涌。
使用commit=False 的另外一個副作用是在模型具有多對多關(guān)系的時候猖任。如果模型具有多對多關(guān)系而且當(dāng)你保存表單時指定commit=False,Django 不會立即為多對多關(guān)系保存表單數(shù)據(jù)瓷耙。這是因?yàn)橹挥袑?shí)例在數(shù)據(jù)庫中存在時才可以保存實(shí)例的多對多數(shù)據(jù)朱躺。
為了解決這個問題,每當(dāng)你使用commit=False 保存表單時搁痛,Django 將添加一個save_m2m() 方法到你的模型表單子類长搀。在你手工保存有表單生成的實(shí)例之后,你可以調(diào)用save_m2m() 來保存多對多的表單數(shù)據(jù)鸡典。例如:
# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)
# Create, but don't save the new author instance.
>>> new_author = f.save(commit=False)
# Modify the author in some way.
>>> new_author.some_field = 'some_value'
# Save the new instance.
>>> new_author.save()
# Now, save the many-to-many data for the form.
>>> f.save_m2m()
save_m2m() 只在你使用save(commit=False) 時才需要盈滴。當(dāng)你直接使用save(),所有的數(shù)據(jù) —— 包括多對多數(shù)據(jù) —— 都將保存而不需要任何額外的方法調(diào)用轿钠。例如:
# Create a form instance with POST data.
>>> a = Author()
>>> f = AuthorForm(request.POST, instance=a)
# Create and save the new author instance. There's no need to do anything else.
>>> new_author = f.save()
處理關(guān)系的字段Fields which handle relationships
兩個字段可用于表示模型之間的關(guān)系:ModelChoiceField和ModelMultipleChoiceField
巢钓。這兩個字段都需要單個queryset
參數(shù),用于創(chuàng)建字段的選擇疗垛。在表單驗(yàn)證時症汹,這些字段將把一個模型對象(在ModelChoiceField
的情況下)或多個模型對象(在ModelMultipleChoiceField
的情況下)放置到表單的cleaned_data
字典。
對于更復(fù)雜的用法贷腕,可以在聲明表單字段時指定queryset=None
背镇,然后在表單的__init__()
方法中填充queryset
咬展。
class FooMultipleChoiceForm(forms.Form):
foo_select = forms.ModelMultipleChoiceField(queryset=None)
def __init__(self, *args, **kwargs):
super(FooMultipleChoiceForm, self).__init__(*args, **kwargs)
self.fields['foo_select'].queryset = ...
ModelChoiceField
class ModelChoiceField(**kwargs)
- 默認(rèn)的Widget: Select
- 空值: None
- 規(guī)范化為: 一個模型實(shí)例
- 驗(yàn)證給定的id是否存在于查詢集中
- 錯誤信息的鍵: required, invalid_choice
可以選擇一個單獨(dú)的模型對象,適用于表示一個外鍵字段瞒斩。ModelChoiceField默認(rèn)widget不適用選擇數(shù)量很大的情況破婆,在大于100項時應(yīng)該避免使用它。
需要一個單獨(dú)參數(shù):
queryset: 將導(dǎo)出字段選擇的模型對象的QuerySet胸囱,將用于驗(yàn)證用戶的選擇祷舀。
ModelChoiceField有兩個可選參數(shù):
-
empty_label: 默認(rèn)情況下,ModelChoiceField使用的<select>小部件將在列表頂部有一個空選項烹笔。您可以使用empty_label屬性更改此標(biāo)簽的文本(默認(rèn)為"________")裳扯,也可以完全禁用空白標(biāo)簽通過將empty_label設(shè)置為None:
#A custom empty label field1 = forms.ModelChoiceField(queryset=..., empty_label="(Nothing)") #No empty label field2 = forms.ModelChoiceField(queryset=..., empty_label=None)
請注意如果需要用到ModelChoiceField有一個默認(rèn)的初始值,則不會創(chuàng)建空選項(不管empty_label的值)谤职。
-
to_field_name: 這個參數(shù)用于指定要用作字段小部件選項的值的字段饰豺。確保它是模型的唯一字段,否則選定的值可以匹配多個對象允蜈。默認(rèn)情況下冤吨,它設(shè)置為None,在這種情況下饶套,將使用每個對象的主鍵锅很。
#No custom to_field_name field1 = forms.ModelChoiceField(queryset=...)
會渲染成:
<select id='id_field1' name='field1'>
<option value='obj1.pk'>Object1</option>
<option value='obj2.pk'>Object2</option>
...
</select>
和:
#to_field_name provided
field2 = forms.ModelChoiceField(queryset=... to_field_name='name')
會生成:
<select id='id_field2' name='field2'>
<option value='obj1.name'>Object1</option>
<option value='obj2.name'>Object2</option>
...
</select>
ModelMultipleChoiceField
- 默認(rèn)的Widget: SelectMultiple
- 控制: QuerySet(self.queryset.none())
- 規(guī)范化為: 模型實(shí)例的一個QuerySet。
- 錯誤信息的鍵: required, list, invalid_choice, invalid_pk_value
允許選擇適合于表示多對多關(guān)系的一個或多個模型對象凤跑。queryset是必需的參數(shù)爆安。
queryset:將導(dǎo)出字段選擇的模型對象的QuerySet,將用于驗(yàn)證用戶的選擇仔引。
表單驗(yàn)證和字段驗(yàn)證
表單驗(yàn)證發(fā)生在數(shù)據(jù)清洗時扔仓。如果需要自定義這個過程,有幾個不同的地方可以修改咖耘,每個地方的目的不一樣翘簇。表單處理過程中要運(yùn)行三種類別的驗(yàn)證方法:它們通常在你調(diào)用表單的is_valid()
方法時執(zhí)行。還有其它方法可以觸發(fā)驗(yàn)證過程(訪問errors屬性
或直接調(diào)用full_clean()
)儿倒,但是通常情況下不需要版保。
一般情況下,如果處理的數(shù)據(jù)有問題夫否,每個驗(yàn)證方法都會引發(fā)ValidationError
彻犁,并將相關(guān)信息傳遞給ValidationError
構(gòu)造器。如果沒有引發(fā)ValidationError
凰慈,這些方法應(yīng)該返回驗(yàn)證后的(規(guī)整化的汞幢,清洗后的)數(shù)據(jù)的Python
對象。
大部分驗(yàn)證應(yīng)該可以使用validators
完成微谓,它們可以很容易地重用森篷。Validators
是簡單的函數(shù)(或可調(diào)用對象)输钩,它們接收一個參數(shù)并對非法的輸入拋出ValidationError
。Validators
在字段的to_python
和validate
方法調(diào)用之后運(yùn)行仲智。
表單的驗(yàn)證分成幾個步驟买乃,它們可以定制或覆蓋:
- 字段的
to_python()
方法是驗(yàn)證的第一步。它將值強(qiáng)制轉(zhuǎn)換為正確的數(shù)據(jù)類型钓辆,如果不能轉(zhuǎn)換則引發(fā)ValidationError
剪验。這個方法從Widget
接收原始的值并返回轉(zhuǎn)換后的值。例如,FloatField
將數(shù)據(jù)轉(zhuǎn)換為Python
的float
或引發(fā)ValidationError
岩馍。 - 字段的
validate()
方法處理字段的特別的驗(yàn)證碉咆,這種驗(yàn)證不適合validator
抖韩。它接收一個已經(jīng)轉(zhuǎn)換成正確數(shù)據(jù)類型的值蛀恩,并在發(fā)現(xiàn)錯誤時引發(fā)ValidationError
。這個方法不返回任何東西且不應(yīng)該改變?nèi)魏沃得 .?dāng)您遇到不能或不想放在validator
中的驗(yàn)證邏輯時双谆,應(yīng)該覆蓋它來處理驗(yàn)證。 - 字段的
run_validators()
方法運(yùn)行字段所有的Validator
席揽,并將所有的錯誤信息聚合成一個單一的ValidationError
顽馋。你應(yīng)該不需要覆蓋這個方法。 - Filed子類的
clean()
方法幌羞。它負(fù)責(zé)以正確的順序運(yùn)行to_python
寸谜、validate
和run_validators
并傳播它們的錯誤。如果任何時刻属桦、任何方法引發(fā)ValidationError
熊痴,驗(yàn)證將停止并引發(fā)這個錯誤。這個方法返回驗(yàn)證后的數(shù)據(jù)聂宾,這個數(shù)據(jù)在后面將插入到表單的cleaned_data
字典中果善。 - 表單子類中的
clean_<fieldname>
方法---<fieldname>
通過表單中的字段名稱替換。這個方法完成于特定屬性相關(guān)的驗(yàn)證系谐,這個驗(yàn)證與字段的類型無關(guān)巾陕。這個方法沒有任何輸入的參數(shù)。你需要查找self.cleaned_data
中該字段的值纪他,記住此時它已經(jīng)是一個Python對象而不是表單中提交的原始字符串(它位于cleaned_data
中是因?yàn)樽侄蔚?code>clean()方法已經(jīng)驗(yàn)證過一次數(shù)據(jù))鄙煤。
例如,如果你想驗(yàn)證名為serialnumber
的CharField
的內(nèi)容是否唯一茶袒,clean_serialnumber()
將是實(shí)現(xiàn)這個功能的理想之處馆类。你需要的不是一個特別的字段(它只是一個CharField
),而是一個特定于表單字段的特定驗(yàn)證弹谁,并規(guī)整化數(shù)據(jù)乾巧。
這個方法返回的值將代替cleaned_data
中已經(jīng)存在的值句喜,因此它必須是cleaned_data
中字段的值或一個新的清洗后的值。 - 表單子類的
clean()
方法沟于。這個方法可以實(shí)現(xiàn)需要同時訪問表單多個字段的驗(yàn)證咳胃。這里你可以驗(yàn)證如果提供字段A,那么字段B必須包含一個合法的郵件地址以及類似的功能旷太。這個方法可以返回一個完全不同的字典展懈,該字典將用作cleaned_data
。
因?yàn)樽侄蔚尿?yàn)證方法在調(diào)用clean()
時會運(yùn)行供璧,你還可以訪問表單的errors
屬性存崖,它包含驗(yàn)證每個字段時的所有錯誤。
注意睡毒,你覆蓋的Form.clean()
引發(fā)的任何錯誤將不會與任何特定的字段關(guān)聯(lián)来惧。它們位于一個特定的‘字段’(叫做__all__
)中,如果需要可以通過non_field_errors()
方法訪問演顾。如果你想添加一個特定字段的錯誤到表單中供搀,需要調(diào)用add_error()
。
還要注意钠至,覆蓋ModelForm
子類的clean()
方法需要特殊的考慮葛虐。
這些方法按照以上給出的順序執(zhí)行,一次驗(yàn)證一個字段棉钧。也就是說屿脐,對于表單中的每個字段(按照它們在表單定義中出現(xiàn)的順序),先運(yùn)行Field.clean()
宪卿,然后運(yùn)行clean_<fieldname>()
的诵。每個字段的這兩個方法都執(zhí)行完之后,最后運(yùn)行Form.clean()
方法愧捕,無論前面的方法是否拋出過異常奢驯。
下面由上面每個方法的示例。
我們已經(jīng)提到過次绘,所有這些方法都可以拋出ValidationError
瘪阁。對于每個字段,如果Field.clean()
方法拋出ValidationError邮偎,那么將不會調(diào)用該字段對應(yīng)的clean_<fieldname>()
方法管跺。但是,剩余的字段的驗(yàn)證方法仍然會執(zhí)行禾进。
在實(shí)踐中應(yīng)用驗(yàn)證
使用Validator
Django的表單(以及模型)字段支持使用簡單的函數(shù)和類用于驗(yàn)證豁跑,它們叫做Validator。Validator是可調(diào)用對象或函數(shù)泻云,它接收一個值艇拍,如果該值合法則什么也不返回狐蜕,否則拋出ValidationError。它們可以通過字段的validators參數(shù)傳遞給字段的構(gòu)造函數(shù)卸夕,或者定義在Field類的default_validators屬性中层释。
簡單的Validator可以用于在字段內(nèi)部驗(yàn)證值,讓我們看下Django的SlugField:
from django.forms import CharField
form django.core import validators
class SlugField(CharField):
default_validators = [validators.validate_slug]
正如你所看到的快集,SlugField只是一個帶有自定義Validator的CharField贡羔,它們驗(yàn)證提交的文本符合某些字符規(guī)則。這也可以在字段定義時實(shí)現(xiàn)个初,所以:
slug = forms.SlugField()
等同于:
slug = forms.CharField(validators=[validtors.validate_slug])
常見的情形乖寒,例如驗(yàn)證郵件地址和正則表達(dá)式,可以使用Django中已經(jīng)存在的Validator類處理院溺。
表單字段的默認(rèn)驗(yàn)證
讓我們首先創(chuàng)建一個自定義的表單字段楣嘁,它驗(yàn)證其輸入是一個由逗號分隔的郵件地址組成的字符串。完整的類像這樣:
from django import forms
from django.core.validators import validate_mail
class MultiEmailField(forms.Field):
def to_python(self, value):
"""Nomalize data to a list of strings."""
#Return an empty list if no input was given.
if not value:
return []
return value.split(',')
def validate(self, value):
"""Check if value consists only of valid emails."""
#Use the parent's handling of required fields, etc.
super(MultiEmailField, self).validate(value)
for email in value:
validate_email(email)
使用這個字段的每個表單都將在處理該字段數(shù)據(jù)之前運(yùn)行這些方法覆获。這個驗(yàn)證特定于該類型的字段马澈,與后面如何使用它無關(guān)瓢省。
讓我們來創(chuàng)建一個簡單的ContactForm來向你演示如何使用這個字段:
class ContactForm(forms.Form):
subject = forms.CharField(max_lenght=100)
message = forms.CharField()
sender = forms.EmailField()
recipients = MultiEmailField()
cc_myself = forms.BooleanField(required=False)
只需要簡單地使用MultiEmailField弄息,就和其它表達(dá)字段一樣。當(dāng)調(diào)用表單的is_valid()方法時勤婚,MultiEmailField.clean()方法將作為驗(yàn)證過程的一部分運(yùn)行摹量,它將調(diào)用自定義的to_python()和validate()方法。
驗(yàn)證特定字段屬性
繼續(xù)上面的例子馒胆,假設(shè)在ContactForm中缨称,我們想確保recipients字段始終包含"fred@example.com"。這是特定于我們這個表達(dá)的驗(yàn)證祝迂,所以我們不打算將它放在通用的MultiEmailField類中睦尽,我們將編寫一個運(yùn)行在recipients字段上的驗(yàn)證方法,像這樣:
from django import forms
class ContactForm(forms.Form):
#Everything as before.
...
def clean_recipients(self):
data = self.cleaned_data['recipients']
if 'fred@example.com' not in data:
raise forms.ValidationError("You have forgotten about Fred!")
#Always return a value to use as the new cleaned data, even if this method didn't change it.
return data
驗(yàn)證相互依賴的字段
假設(shè)我們添加另外一個需求到我們的ContactForm表單中:如果cc_myself
字段為True
型雳,那么subject
必須包含單詞"help"当凡。我們的這個驗(yàn)證包含多個字段,所以表單的clean()
方法是個不錯的地方纠俭。注意沿量,我們這里討論的是表單的clean()
方法,之前我們編寫過字段的clean()
方法冤荆。區(qū)別字段和表單之間的差別非常重要朴则。字段是單個數(shù)據(jù),表單是字段的集合钓简。
在調(diào)用表單clean()
方法的時候乌妒,所有字段的驗(yàn)證方法已經(jīng)執(zhí)行完汹想,所以self.cleaned_data
填充的是目前為止已經(jīng)合法的數(shù)據(jù)。所以你需要記住這個事實(shí)撤蚊,你需要驗(yàn)證的字段可能沒有通過初始的字段檢查欧宜。
在這一步,有兩種方法報告錯誤拴魄。最簡單的方法是在表單的頂端顯示錯誤冗茸。你可以在clean()
方法中拋出ValidationError
來創(chuàng)建錯誤。例如匹中,
from django import forms
class ContactForm(forms.Form):
#Everythign as before.
...
def clean(self):
cleaned_data = super(ContactForm, self).clean()
cc_myself = cleaned_data.get("cc_myself")
subject = cleaned_data.get("subject")
if cc_myself and subject:
# Only do something if both fields are valid so far.
if "help" not in subject:
raise forms.ValidationError(
"Did not send for 'help' in the subject despite "
"CC'ing yourself."
)
在這段代碼中夏漱,如果拋出驗(yàn)證錯誤,表單將在表單的頂部顯示(通常是)描述該問題的一個錯誤信息顶捷。
注意挂绰,示例代碼中super(ContactForm, self).clean()的調(diào)用是為了維持父類中的驗(yàn)證邏輯。如果你的表單繼承自另外一個在clean()方法中沒有返回一個cleaned_data字典的表單服赎,那么不要把cleand_data聯(lián)系到super()的結(jié)果而要使用self.cleaned_data葵蒂。
def clean(self):
super(ContactForm, self).clean()
cc_myself = self.cleaned_data.get('cc_myself')
...
第二種方法涉及將錯誤消息關(guān)聯(lián)到某個字段。在這種情況下重虑,讓我們在表單的顯示中分別關(guān)聯(lián)一個錯誤信息到“subject” 和“cc_myself” 行践付。在實(shí)際應(yīng)用中要小心,因?yàn)樗赡軐?dǎo)致表單的輸出變得令人困惑缺厉。我們只是向你展示這里可以怎么做永高,在特定的情況下,需要你和你的設(shè)計人員確定什么是好的方法提针。我們的新代碼(代替前面的示例)像這樣:
from django import forms
class ContactForm(forms.Form):
# Everything as before.
...
def clean(self):
cleaned_data = super(ContactForm, self).clean()
cc_myself = cleaned_data.get("cc_myself")
subject = cleaned_data.get("subject")
if cc_myself and subject and "help" not in subject:
msg = "Must put 'help' in subject when cc'ing yourself."
self.add_error('cc_myself', msg)
self.add_error('subject', msg)
add_error()
的第二個參數(shù)可以是一個簡單的字符串命爬,但更傾向是ValidationError
的一個實(shí)例
編寫Validator
驗(yàn)證器是一個可調(diào)用的對象,它接受一個值辐脖,并在不符合一些規(guī)則時候拋出ValidationError異常饲宛。驗(yàn)證器有助于在不同類型的字段之間重復(fù)使用驗(yàn)證邏輯。
例如嗜价,這個驗(yàn)證器只允許偶數(shù):
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
def validate_even(value):
if value % s != 0:
raise ValidationError(
_('%(value)s is not an even number'),
params={'value': value},
)
你可以通過字段的validators參數(shù)將它添加到模型字段中:
from django.db import models
class MyModel(models.Model):
even_field = models.IntegerField(validators=[validate_even])
由于值在驗(yàn)證器運(yùn)行之前會轉(zhuǎn)化為Python艇抠,你可以在表單上使用相同的驗(yàn)證器:
from django import forms
class MyForm(forms.Form):
even_field = forms.IntegerField(validators=[validate_even])
你可以可以使用帶有__call__()
方法的類,實(shí)現(xiàn)更復(fù)雜或可配置的驗(yàn)證器炭剪。
Validator如何運(yùn)行
參考:
- http://python.usyiyi.cn/translate/django_182/topics/forms/index.html
- http://python.usyiyi.cn/translate/django_182/ref/forms/api.html
- http://python.usyiyi.cn/translate/django_182/topics/forms/modelforms.html
- http://python.usyiyi.cn/translate/django_182/ref/forms/fields.html
- http://python.usyiyi.cn/translate/django_182/ref/forms/validation.html