HTML表單
在HTML中,表單是<form>...</form>之間元素的集合,它們?cè)试S訪問者輸入文本、選擇選項(xiàng)、操作對(duì)象等等,然后將信息發(fā)送回服務(wù)器。
某些表單的元素---文本框和復(fù)選框---非常簡(jiǎn)單而且內(nèi)建于HTML本身。其它的表單會(huì)復(fù)雜些:例如彈出一個(gè)日期選擇對(duì)話框的界面、允許你移動(dòng)滾動(dòng)條的界面、使用JavaScript和CSS以及HTML表單元素<input>來實(shí)現(xiàn)操作控制的界面。
與<input>元素一樣,一個(gè)表單必須指定兩樣?xùn)|西:
- 目的地:響應(yīng)用戶輸入數(shù)據(jù)的
URL; - 方式: 發(fā)送數(shù)據(jù)所使用的
HTTP方法;
例如,Django Admin站點(diǎn)的登錄表單包含幾個(gè)<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í),數(shù)據(jù)將發(fā)送給/admin/。
GET和POST
處理表單時(shí)只會(huì)用到GET和POST方法。
Django的登錄表單使用POST方法,在這個(gè)方法中瀏覽器組合表單數(shù)據(jù),對(duì)它們進(jìn)行編碼以用于傳輸,將它們發(fā)送到服務(wù)器然后接收它的響應(yīng)。
相反,GET組合提交的數(shù)據(jù)為一個(gè)字符串,然后使用它來生成一個(gè)URL。這個(gè)URL將包含數(shù)據(jù)發(fā)送的地址以及數(shù)據(jù)的鍵和值。
GET和POST用于不同的目的。
用于改變系統(tǒng)狀態(tài)的請(qǐng)求---例如,給數(shù)據(jù)庫帶來變化的請(qǐng)求---應(yīng)該使用POST。GET只應(yīng)該用于不會(huì)影響系統(tǒng)狀態(tài)的請(qǐng)求。
Django在表單中的角色
處理表單是一件很復(fù)雜的事情。考慮一下Django的Admin站點(diǎn),不同類型的大量數(shù)據(jù)項(xiàng)需要在一個(gè)表單中準(zhǔn)備好、渲染成HTML、使用一個(gè)方便的界面編輯、返回給服務(wù)器、驗(yàn)證并清除,然后保存或者向后繼續(xù)處理。
Django的表單功能可以簡(jiǎn)化并自動(dòng)化大部分這些工作,并且還可以比大部分程序自己所編寫的代碼更安全。
Django會(huì)處理表單工作中的三個(gè)顯著不同的部分:
- 準(zhǔn)備數(shù)據(jù)、重構(gòu)數(shù)據(jù),以便下一步渲染;
- 為數(shù)據(jù)創(chuàng)建
HTML表單; - 接收并處理客戶端提交的表單和數(shù)據(jù);
可以手工編寫代碼來實(shí)現(xiàn),但是Django可以幫你完成所有這些工作。
Django中的表單
HTML的<form>只是其機(jī)制的一部分。
在一個(gè)Web應(yīng)用中,‘表單’可能是指HTML <form>、或者生成它的Django的Form、或者提交時(shí)發(fā)送的結(jié)構(gòu)化數(shù)據(jù)、或者這些部分的總和。
Django的Form類
表單系統(tǒng)的核心部分是Django的Form類。Django的模型描述一個(gè)對(duì)象的邏輯結(jié)構(gòu)、行為以及展現(xiàn)給我們的方式,與此類似, Form類描述一個(gè)表單并決定它如何工作和展現(xiàn)。
就像模型類的屬性映射到數(shù)據(jù)庫的字段一樣,表單類的字段會(huì)映射到HTML的<input>表單的元素(ModelForm通過一個(gè)Form映射模型類的字段到HTML表單的<input>元素。Django的Admin站點(diǎn)就是基于這個(gè))。
表單的字段本身也是類,它們管理表單的數(shù)據(jù)并在表單提交時(shí)進(jìn)行驗(yàn)證。DateField和FileField處理的數(shù)據(jù)類型差別很大,必須完成不同的事情。
表單字段在瀏覽器中呈現(xiàn)給用戶的是一個(gè)HTML的'widget'---用戶界面的一個(gè)片段。每個(gè)字段類型都有一個(gè)合適的默認(rèn)Widget類,需要時(shí)可以覆蓋。
實(shí)例化、處理和渲染表單
在Django中渲染一個(gè)對(duì)象時(shí),我們通常:
- 在視圖中獲得它(例如,從數(shù)據(jù)庫中獲取);
- 將它傳遞給模板上下文;
- 使用模板變量將它擴(kuò)展為
HTML標(biāo)記語言
當(dāng)我們?cè)谝晥D中處理模型實(shí)例時(shí),我們一般從數(shù)據(jù)庫中獲取它。當(dāng)我們處理表單時(shí),一般在視圖中實(shí)例化它。
構(gòu)建一個(gè)表單
在Django中構(gòu)建一個(gè)表單
Form類
我們已經(jīng)計(jì)劃好了我們的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)
它定義一個(gè)Form類,只帶有一個(gè)字段(your_name)。我們已經(jīng)對(duì)這個(gè)字段使用一個(gè)友好的標(biāo)簽,當(dāng)渲染時(shí)它將出現(xiàn)在<label>中。
Form的實(shí)例具有一個(gè)is_valid()方法,它為所有的字段運(yùn)行驗(yàn)證程序。當(dāng)調(diào)用這個(gè)方法時(shí),如果所有的字段都包含合法的數(shù)據(jù),它將:
返回
True;-
將表單的數(shù)據(jù)放到
cleaned_data屬性中;
完整的表單,第一次渲染時(shí),看上去將像:<label for='your_name'>Your name:</label> <input id='your_name' type='text' name='your_name' maxlength='100' required/>
注意它不包含<form>標(biāo)簽和提交按鈕,我們必須自己在模板中手動(dòng)提供它們。
視圖
發(fā)送給Django網(wǎng)站的表單數(shù)據(jù)通過一個(gè)視圖處理,一般和發(fā)布這個(gè)表單的是同一個(gè)視圖。這允許我們重用一些相同的邏輯。
我們需要在URL對(duì)應(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})
如果訪問視圖的是一個(gè)GET請(qǐng)求,它將創(chuàng)建一個(gè)空的表單實(shí)例并將它放到要渲染的模板上下文中。只是我們?cè)诘谝淮卧L問該URL時(shí)預(yù)期發(fā)生的情況。
如果表單的提交使用POST請(qǐng)求,那么視圖將再次創(chuàng)建一個(gè)表單實(shí)例并使用請(qǐng)求中的數(shù)據(jù)填充它:form = NameForm(request.POST)。這叫做“綁定數(shù)據(jù)至表單”(它現(xiàn)在是一個(gè)綁定的表單)。
我們調(diào)用表單的is_valid()方法:如果它不為True,我們將帶著這個(gè)表單返回到模板。這時(shí)表單不再為空(未綁定),所以HTML表單將用之前提交的數(shù)據(jù)填充,然后可以根據(jù)要求編輯并改正它。
如果is_valid()為Ture,我們將能夠在cleaned_data屬性中找到所有合法的表單數(shù)據(jù)。在發(fā)送HTTP重定向給瀏覽器告訴它下一步的去向之前,我們可以用這個(gè)數(shù)據(jù)來更新數(shù)據(jù)庫或者做其它處理。
模板
我們不需要在name.html模板中做很多工作,最簡(jiǎn)單的例子是:
<form action="/your-name/" method="post">
{%csrf_token%}
{{form}}
<input type='submit' value="Submit" />
</form>
根據(jù){{form}},所有的表單字段和它們的屬性將通過Django的模板語言拆分成HTML標(biāo)記語言。
現(xiàn)在我們有一個(gè)可以工作的網(wǎng)頁表單,它通過Django Form描述、通過視圖處理并渲染成一個(gè)HTML <form>。
Django Form類詳解
所有的表單類都作為django.forms.Form的子類創(chuàng)建,包括你在Django管理站點(diǎn)中遇到的ModelForm。
模型和表單
實(shí)際上,如果你的表單打算直接用來添加和編輯Django的模型,ModelForm可以節(jié)省你的許多時(shí)間、精力和代碼,因?yàn)樗鼘⒏鶕?jù)Model類構(gòu)建一個(gè)表單以及適當(dāng)?shù)淖侄魏蛯傩浴?/p>
綁定的和未綁定的表單實(shí)例
綁定的和未綁定的表單之間的區(qū)別非常重要:
- 未綁定的表單沒有關(guān)聯(lián)的數(shù)據(jù)。當(dāng)渲染給用戶時(shí),它將為空或者包含默認(rèn)的值。
- 綁定的表單具有提交地?cái)?shù)據(jù),因此可以用來檢驗(yàn)數(shù)據(jù)是否合法。如果渲染一個(gè)不合法的綁定的表單,它將包含內(nèi)聯(lián)的錯(cuò)誤信息,告訴用戶如何糾正數(shù)據(jù)。
表單的is_bound屬性將告訴你一個(gè)表單是否具有綁定的數(shù)據(jù)。
字段詳解
考慮一個(gè)比上面的迷你示例更有用的一個(gè)表單,在一個(gè)網(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)
我們前面的表單只使用一個(gè)字段your_name,它是一個(gè)CharField。在這個(gè)例子中,我們的表單具有四個(gè)字段:subject、message、sender和cc_myself。共用到三種字段類型:CharField、EmailField和BooleanField。
窗口小部件
每個(gè)表單字段都有一個(gè)對(duì)應(yīng)的Widget類,它對(duì)應(yīng)一個(gè)HTML表單Widget,例如<input type='text'>。
在大部分情況下,字段都具有一個(gè)合理的默認(rèn)Widget。例如,默認(rèn)情況下,CharField具有一個(gè)TextInput Widget,它在HTML中生成一個(gè)<input type='text'>。如果你需要<textarea>,在定義表單字段時(shí)你應(yīng)該指定一個(gè)合適的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的類型。
注意,此時(shí),依然可以從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>元素。
表單渲染的選項(xiàng)
不要忘記,表單的輸出不包含<form>標(biāo)簽和表單的submit按鈕,必須自己提供它們。
對(duì)于<label>/<input>對(duì),還有幾個(gè)輸出選項(xiàng):
- {{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>
每個(gè)表單字段具有一個(gè)ID屬性并設(shè)置為id_<field-name>。
手工渲染字段
我們沒有必要非要讓Django來分拆表單的字段:如果我們喜歡,我們可以手工來做(這樣允許你重新對(duì)字段排序)。每個(gè)字段都是表單的一個(gè)屬性,可以使用{{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>
渲染表單的錯(cuò)誤信息
當(dāng)然,這個(gè)便利性的代價(jià)是更多的工作。直到現(xiàn)在,我們沒有擔(dān)心如何展示錯(cuò)誤信息,因?yàn)镈jango已經(jīng)幫我們處理好。在下面的例子中,我們將自己處理每個(gè)字段的錯(cuò)誤和表單整體的各種錯(cuò)誤。注意,表單和模板頂部的{{form.non_field_errors}}查找每個(gè)字段的錯(cuò)誤。
使用{{form.name_of_field.errors}}顯示表單錯(cuò)誤的一個(gè)清單,并渲染成一個(gè)ul??瓷先タ赡芟瘢?/p>
<ul class="errorlist">
<li>Sender is required.</li>
</ul>
這個(gè)ul有一個(gè)errorlist的CSS樣式表,你可以用它來定義外觀。如果你希望進(jìn)一步自定義錯(cuò)誤信息的顯示,你可以迭代它們來實(shí)現(xiàn):
{% if form.subject.errors %}
<ol>
{% for error in form.subject.errors %}
<li><strong>{{ error|escape }}</strong></li>
{% endfor %}
</ol>
{% endif %}
迭代表單字段
如果你為你的每個(gè)表單字段使用相同的HTML,你可以使用{%for%}循環(huán)迭代每個(gè)字段來減少重復(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 %}
迭代隱藏和可見的字段
如果你正在手工布局模板中的一個(gè)表單,而不是依賴Django 默認(rèn)的表單布局,你可能希望將<input type="hidden"> 字段與非隱藏的字段區(qū)別對(duì)待。例如,因?yàn)殡[藏的字段不會(huì)顯示,在該字段旁邊放置錯(cuò)誤信息可能讓你的用戶感到困惑 —— 所以這些字段的錯(cuò)誤應(yīng)該有區(qū)別地來處理。
Django 提供兩個(gè)表單方法,它們?cè)试S你獨(dú)立地在隱藏的和可見的字段上迭代:hidden_fields() 和visible_fields()。下面是使用這兩個(gè)方法對(duì)前面一個(gè)例子的修改:
{# 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 %}
這個(gè)示例沒有處理隱藏字段中的任何錯(cuò)誤信息。通常,隱藏字段中的錯(cuò)誤意味著表單被篡改,因?yàn)檎5谋韱翁顚懖粫?huì)改變它們。然而,你也可以很容易地為這些表單錯(cuò)誤插入一些錯(cuò)誤信息顯示出來。
可重用的表單模板
如果你的網(wǎng)站在多個(gè)地方對(duì)表單使用相同的渲染邏輯,你可以保存表單的循環(huán)到一個(gè)單獨(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 %}
如果傳遞到模板上下文中的表單對(duì)象具有一個(gè)不同的名稱,你可以使用include標(biāo)簽的with參數(shù)來對(duì)它起個(gè)別名:
{% 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
若要?jiǎng)?chuàng)建一個(gè)未綁定的表單實(shí)例,只需要簡(jiǎn)單地實(shí)例化該類:
>>>f = ContactForm()
若要綁定數(shù)據(jù)到表單,可以將數(shù)據(jù)以字典的形式傳遞給表單類的構(gòu)造函數(shù)的第一個(gè)參數(shù):
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True}
>>> f = ContactForm(data)
在這個(gè)字典中,鍵為字段的名稱,它們對(duì)應(yīng)于表單類中的屬性。值為需要驗(yàn)證的數(shù)據(jù)。它們通常為字符串,但是沒有強(qiáng)制要求必須是字符串。傳遞的數(shù)據(jù)類型取決于字段。
Form.is_bound
如果在運(yùn)行時(shí)你要區(qū)分綁定的表單和未綁定的表單,可以檢查下表單is_bound屬性的值。
>>> f = ContactForm()
>>> f.is_bound
False
>>> f = ContactForm({'subject': 'hello'})
>>> f.is_bound
True
注意,傳遞一個(gè)空的字典將創(chuàng)建一個(gè)帶有空數(shù)據(jù)的綁定的表單:
>>> f = ContactForm({})
>>> f.is_bound
True
如果你有一個(gè)綁定的表單實(shí)例但是想修改下數(shù)據(jù),或者你想綁定一個(gè)未綁定的表單到某些數(shù)據(jù),你需要?jiǎng)?chuàng)建另外一個(gè)表單列表。Form實(shí)例的數(shù)據(jù)沒有辦法修改。表單實(shí)例一旦創(chuàng)建,你應(yīng)該將它的數(shù)據(jù)視為不可變的,無論它有沒有數(shù)據(jù)。
使用表單來驗(yàn)證數(shù)據(jù)
Form.clean()
當(dāng)你需要為相互依賴的字段添加自定義的驗(yàn)證時(shí),你可以實(shí)現(xiàn)表單的clean()方法。
Form.is_valid()
表單對(duì)象的首要任務(wù)就是驗(yàn)證數(shù)據(jù)。對(duì)于綁定的表單實(shí)例,可以調(diào)用is_valid()方法來執(zhí)行驗(yàn)證并返回一個(gè)表示數(shù)據(jù)是否合法的布爾值。
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
True
讓我們?cè)囅路欠ǖ臄?shù)據(jù)。下面的情形中,subject 為空(默認(rèn)所有字段都是必需的)且sender 是一個(gè)不合法的郵件地址:
>>> data = {'subject': '',
... 'message': 'Hi there',
... 'sender': 'invalid email address',
... 'cc_myself': True}
>>> f = ContactForm(data)
>>> f.is_valid()
False
Form.errors
訪問errors屬性可以獲得錯(cuò)誤信息的一個(gè)字典。
>>> f.errors
{'sender': ['Enter a valid email address.'], 'subject': ['This field is required.']}
在這個(gè)字典中,鍵為字段的名稱,值為表示錯(cuò)誤信息的Unicode字符串組成的列表。錯(cuò)誤信息保存在列表中是因?yàn)樽侄慰赡苡卸鄠€(gè)錯(cuò)誤信息。
你可以訪問errors而不必須先調(diào)用is_valid()。表單的數(shù)據(jù)將在第一次調(diào)用is_valid()或者訪問errors時(shí)驗(yàn)證。
驗(yàn)證只會(huì)調(diào)用一次,無論你訪問errors或者調(diào)用is_valid()多少次。這意味著,如果驗(yàn)證過程有副作用,這些副作用將只觸發(fā)一次。
Form.non_field_errors()
這個(gè)方法返回Form.errors中不是與特定字段相關(guān)聯(lián)的錯(cuò)誤。它包含在Form.clean()中引發(fā)的ValidationError和使用Form.add_error(None,'...')添加的錯(cuò)誤。
動(dòng)態(tài)的初始值
Form.initial
表單字段的初始值使用initial聲明。例如,你可能希望使用當(dāng)前會(huì)話的用戶名填充username字段。
使用Form的initial參數(shù)可以實(shí)現(xiàn)。該參數(shù)是字段名到初始值的一個(gè)字典。只需要包含你期望給出初始值的字段,不需要包含表單中的所有字段。例如:
>>> f = ContactForm(initial={'subject': 'Hi there!'})
這些值只顯示在沒有綁定的表單中,即使沒有提供特定值它們也不會(huì)作為后備的值。
注意,如果字段有定義initial,而實(shí)例化表單時(shí)也提供initial,那么后面的initial將優(yōu)先。在下面的例子中,initial在字段和表單實(shí)例化中都有定義,此時(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ā)生改變時(shí),可以使用表單的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)提交表單時(shí),我們可以重新構(gòu)建表單并提供初始值,這樣可以實(shí)現(xiàn)比較:
>>> f = ContactForm(request.POST, initial=data)
>>> f.has_changed()
如果request.POST中的數(shù)據(jù)與inital中的不同,has_changed()將為True,否則為False。計(jì)算的結(jié)果是通過調(diào)用表單每個(gè)字段的Field.has_changed()得到的。
訪問'clean'的數(shù)據(jù)
Form.cleaned_data
表單類中的每個(gè)字段不僅負(fù)責(zé)驗(yàn)證數(shù)據(jù),還負(fù)責(zé)‘清潔’它們---將它們轉(zhuǎn)換為正確的格式。這是個(gè)非常好用的功能,因?yàn)樗试S字段以多種方式輸入數(shù)據(jù),并總能得到一致的輸出。
一旦你創(chuàng)建一個(gè)表單實(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í)傳遞了額外的數(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)表單合法時(shí),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è)置為一個(gè)空字符串,這是因?yàn)閚ick_name 是CharField而 CharField 將空值作為一個(gè)空字符串。每個(gè)字段都知道自己的“空”值 —— 例如,DateField 的空值是None 而不是一個(gè)空字符串。關(guān)于每個(gè)字段空值的完整細(xì)節(jié),參見“內(nèi)建的Field 類”一節(jié)中每個(gè)字段的“空值”提示。
表單必填行和錯(cuò)誤行的樣式表
Form.error_css_class
Form.required_css_class
將必填的表單行和有錯(cuò)誤的表單行定義不同的樣式表特別常見。例如,你想將必填的表單行以粗體顯示,將錯(cuò)誤以紅色顯示。
表單類具有一對(duì)鉤子,可以使用它們來添加class屬性給必填的行或者有錯(cuò)誤的行:只需要簡(jiǎn)單地設(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)建一個(gè)數(shù)據(jù)庫驅(qū)動(dòng)的應(yīng)用,那么你應(yīng)該會(huì)有與Django的模型緊密映射的表單。舉個(gè)例子,你也許會(huì)有個(gè)BlogComment模型,并且你還想創(chuàng)建一個(gè)表單讓大家提交評(píng)論到這個(gè)模型中。在這種情況下,在表單中定義定義字段是冗余的,因?yàn)槟阋呀?jīng)在模型中定義了字段。
基于這個(gè)原因,Django提供一個(gè)輔助類來讓你從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)
字段類型
生成的表單類中將具有和指定的模型字段對(duì)應(yīng)的表單字段。字段順序?yàn)閒ields屬性中指定的順序。
每個(gè)模型字段有一個(gè)對(duì)應(yīng)的默認(rèn)表單字段。比如,模型中的CharField對(duì)應(yīng)表單中的CharField。模型中的ManyToManyField字段會(huì)表現(xiàn)成MultipleChoiceField字段。
模型表單的驗(yàn)證
驗(yàn)證模型表單主要有兩步:
- 驗(yàn)證表單;
- 驗(yàn)證模型實(shí)例;
與普通的表單驗(yàn)證類似,模型表單的驗(yàn)證在調(diào)用is_valid()或者訪問errors屬性時(shí)隱式調(diào)用,或者通過full_clean()顯式調(diào)用,盡管在實(shí)際應(yīng)用中很少使用后一種方法。
模型的驗(yàn)證Model.full_clean()在表單驗(yàn)證這一步的內(nèi)部觸發(fā),緊跟在表單的clean()方法調(diào)用之后。
save()方法
每個(gè)模型表單還具有一個(gè)save()方法。這個(gè)方法根據(jù)表單綁定的數(shù)據(jù)創(chuàng)建并保存數(shù)據(jù)庫對(duì)象。模型表單的子類可以用關(guān)鍵字參數(shù)instance接收一個(gè)已經(jīng)存在的模型實(shí)例。如果提供,save()將更新這個(gè)實(shí)例。如果沒有提供,save()將創(chuàng)建模型的一個(gè)新實(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() 接受一個(gè)可選的commit 關(guān)鍵字參數(shù),其值為True 或False。如果save() 時(shí)commit=False,那么它將返回一個(gè)還沒有保存到數(shù)據(jù)庫的對(duì)象。這種情況下,你需要調(diào)用返回的模型實(shí)例的save()。這是很有用的,如果你想在保存之前自定義一些處理了,或者你想使用特定的模型保存選項(xiàng)。
使用commit=False 的另外一個(gè)副作用是在模型具有多對(duì)多關(guān)系的時(shí)候。如果模型具有多對(duì)多關(guān)系而且當(dāng)你保存表單時(shí)指定commit=False,Django 不會(huì)立即為多對(duì)多關(guān)系保存表單數(shù)據(jù)。這是因?yàn)橹挥袑?shí)例在數(shù)據(jù)庫中存在時(shí)才可以保存實(shí)例的多對(duì)多數(shù)據(jù)。
為了解決這個(gè)問題,每當(dāng)你使用commit=False 保存表單時(shí),Django 將添加一個(gè)save_m2m() 方法到你的模型表單子類。在你手工保存有表單生成的實(shí)例之后,你可以調(diào)用save_m2m() 來保存多對(duì)多的表單數(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) 時(shí)才需要。當(dāng)你直接使用save(),所有的數(shù)據(jù) —— 包括多對(duì)多數(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
兩個(gè)字段可用于表示模型之間的關(guān)系:ModelChoiceField和ModelMultipleChoiceField。這兩個(gè)字段都需要單個(gè)queryset參數(shù),用于創(chuàng)建字段的選擇。在表單驗(yàn)證時(shí),這些字段將把一個(gè)模型對(duì)象(在ModelChoiceField的情況下)或多個(gè)模型對(duì)象(在ModelMultipleChoiceField的情況下)放置到表單的cleaned_data字典。
對(duì)于更復(fù)雜的用法,可以在聲明表單字段時(shí)指定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ī)范化為: 一個(gè)模型實(shí)例
- 驗(yàn)證給定的id是否存在于查詢集中
- 錯(cuò)誤信息的鍵: required, invalid_choice
可以選擇一個(gè)單獨(dú)的模型對(duì)象,適用于表示一個(gè)外鍵字段。ModelChoiceField默認(rèn)widget不適用選擇數(shù)量很大的情況,在大于100項(xiàng)時(shí)應(yīng)該避免使用它。
需要一個(gè)單獨(dú)參數(shù):
queryset: 將導(dǎo)出字段選擇的模型對(duì)象的QuerySet,將用于驗(yàn)證用戶的選擇。
ModelChoiceField有兩個(gè)可選參數(shù):
-
empty_label: 默認(rèn)情況下,ModelChoiceField使用的<select>小部件將在列表頂部有一個(gè)空選項(xiàng)。您可以使用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)
請(qǐng)注意如果需要用到ModelChoiceField有一個(gè)默認(rèn)的初始值,則不會(huì)創(chuàng)建空選項(xiàng)(不管empty_label的值)。
-
to_field_name: 這個(gè)參數(shù)用于指定要用作字段小部件選項(xiàng)的值的字段。確保它是模型的唯一字段,否則選定的值可以匹配多個(gè)對(duì)象。默認(rèn)情況下,它設(shè)置為None,在這種情況下,將使用每個(gè)對(duì)象的主鍵。
#No custom to_field_name field1 = forms.ModelChoiceField(queryset=...)
會(huì)渲染成:
<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')
會(huì)生成:
<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í)例的一個(gè)QuerySet。
- 錯(cuò)誤信息的鍵: required, list, invalid_choice, invalid_pk_value
允許選擇適合于表示多對(duì)多關(guān)系的一個(gè)或多個(gè)模型對(duì)象。queryset是必需的參數(shù)。
queryset:將導(dǎo)出字段選擇的模型對(duì)象的QuerySet,將用于驗(yàn)證用戶的選擇。
表單驗(yàn)證和字段驗(yàn)證
表單驗(yàn)證發(fā)生在數(shù)據(jù)清洗時(shí)。如果需要自定義這個(gè)過程,有幾個(gè)不同的地方可以修改,每個(gè)地方的目的不一樣。表單處理過程中要運(yùn)行三種類別的驗(yàn)證方法:它們通常在你調(diào)用表單的is_valid()方法時(shí)執(zhí)行。還有其它方法可以觸發(fā)驗(yàn)證過程(訪問errors屬性或直接調(diào)用full_clean()),但是通常情況下不需要。
一般情況下,如果處理的數(shù)據(jù)有問題,每個(gè)驗(yàn)證方法都會(huì)引發(fā)ValidationError,并將相關(guān)信息傳遞給ValidationError構(gòu)造器。如果沒有引發(fā)ValidationError,這些方法應(yīng)該返回驗(yàn)證后的(規(guī)整化的,清洗后的)數(shù)據(jù)的Python對(duì)象。
大部分驗(yàn)證應(yīng)該可以使用validators完成,它們可以很容易地重用。Validators是簡(jiǎn)單的函數(shù)(或可調(diào)用對(duì)象),它們接收一個(gè)參數(shù)并對(duì)非法的輸入拋出ValidationError。Validators在字段的to_python和validate方法調(diào)用之后運(yùn)行。
表單的驗(yàn)證分成幾個(gè)步驟,它們可以定制或覆蓋:
- 字段的
to_python()方法是驗(yàn)證的第一步。它將值強(qiáng)制轉(zhuǎn)換為正確的數(shù)據(jù)類型,如果不能轉(zhuǎn)換則引發(fā)ValidationError。這個(gè)方法從Widget接收原始的值并返回轉(zhuǎn)換后的值。例如,FloatField將數(shù)據(jù)轉(zhuǎn)換為Python的float或引發(fā)ValidationError。 - 字段的
validate()方法處理字段的特別的驗(yàn)證,這種驗(yàn)證不適合validator。它接收一個(gè)已經(jīng)轉(zhuǎn)換成正確數(shù)據(jù)類型的值,并在發(fā)現(xiàn)錯(cuò)誤時(shí)引發(fā)ValidationError。這個(gè)方法不返回任何東西且不應(yīng)該改變?nèi)魏沃?。?dāng)您遇到不能或不想放在validator中的驗(yàn)證邏輯時(shí),應(yīng)該覆蓋它來處理驗(yàn)證。 - 字段的
run_validators()方法運(yùn)行字段所有的Validator,并將所有的錯(cuò)誤信息聚合成一個(gè)單一的ValidationError。你應(yīng)該不需要覆蓋這個(gè)方法。 - Filed子類的
clean()方法。它負(fù)責(zé)以正確的順序運(yùn)行to_python、validate和run_validators并傳播它們的錯(cuò)誤。如果任何時(shí)刻、任何方法引發(fā)ValidationError,驗(yàn)證將停止并引發(fā)這個(gè)錯(cuò)誤。這個(gè)方法返回驗(yàn)證后的數(shù)據(jù),這個(gè)數(shù)據(jù)在后面將插入到表單的cleaned_data字典中。 - 表單子類中的
clean_<fieldname>方法---<fieldname>通過表單中的字段名稱替換。這個(gè)方法完成于特定屬性相關(guān)的驗(yàn)證,這個(gè)驗(yàn)證與字段的類型無關(guān)。這個(gè)方法沒有任何輸入的參數(shù)。你需要查找self.cleaned_data中該字段的值,記住此時(shí)它已經(jīng)是一個(gè)Python對(duì)象而不是表單中提交的原始字符串(它位于cleaned_data中是因?yàn)樽侄蔚?code>clean()方法已經(jīng)驗(yàn)證過一次數(shù)據(jù))。
例如,如果你想驗(yàn)證名為serialnumber的CharField的內(nèi)容是否唯一,clean_serialnumber()將是實(shí)現(xiàn)這個(gè)功能的理想之處。你需要的不是一個(gè)特別的字段(它只是一個(gè)CharField),而是一個(gè)特定于表單字段的特定驗(yàn)證,并規(guī)整化數(shù)據(jù)。
這個(gè)方法返回的值將代替cleaned_data中已經(jīng)存在的值,因此它必須是cleaned_data中字段的值或一個(gè)新的清洗后的值。 - 表單子類的
clean()方法。這個(gè)方法可以實(shí)現(xiàn)需要同時(shí)訪問表單多個(gè)字段的驗(yàn)證。這里你可以驗(yàn)證如果提供字段A,那么字段B必須包含一個(gè)合法的郵件地址以及類似的功能。這個(gè)方法可以返回一個(gè)完全不同的字典,該字典將用作cleaned_data。
因?yàn)樽侄蔚尿?yàn)證方法在調(diào)用clean()時(shí)會(huì)運(yùn)行,你還可以訪問表單的errors屬性,它包含驗(yàn)證每個(gè)字段時(shí)的所有錯(cuò)誤。
注意,你覆蓋的Form.clean()引發(fā)的任何錯(cuò)誤將不會(huì)與任何特定的字段關(guān)聯(lián)。它們位于一個(gè)特定的‘字段’(叫做__all__)中,如果需要可以通過non_field_errors()方法訪問。如果你想添加一個(gè)特定字段的錯(cuò)誤到表單中,需要調(diào)用add_error()。
還要注意,覆蓋ModelForm子類的clean()方法需要特殊的考慮。
這些方法按照以上給出的順序執(zhí)行,一次驗(yàn)證一個(gè)字段。也就是說,對(duì)于表單中的每個(gè)字段(按照它們?cè)诒韱味x中出現(xiàn)的順序),先運(yùn)行Field.clean(),然后運(yùn)行clean_<fieldname>()。每個(gè)字段的這兩個(gè)方法都執(zhí)行完之后,最后運(yùn)行Form.clean()方法,無論前面的方法是否拋出過異常。
下面由上面每個(gè)方法的示例。
我們已經(jīng)提到過,所有這些方法都可以拋出ValidationError。對(duì)于每個(gè)字段,如果Field.clean()方法拋出ValidationError,那么將不會(huì)調(diào)用該字段對(duì)應(yīng)的clean_<fieldname>()方法。但是,剩余的字段的驗(yàn)證方法仍然會(huì)執(zhí)行。
在實(shí)踐中應(yīng)用驗(yàn)證
使用Validator
Django的表單(以及模型)字段支持使用簡(jiǎn)單的函數(shù)和類用于驗(yàn)證,它們叫做Validator。Validator是可調(diào)用對(duì)象或函數(shù),它接收一個(gè)值,如果該值合法則什么也不返回,否則拋出ValidationError。它們可以通過字段的validators參數(shù)傳遞給字段的構(gòu)造函數(shù),或者定義在Field類的default_validators屬性中。
簡(jiǎn)單的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只是一個(gè)帶有自定義Validator的CharField,它們驗(yàn)證提交的文本符合某些字符規(guī)則。這也可以在字段定義時(shí)實(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)建一個(gè)自定義的表單字段,它驗(yàn)證其輸入是一個(gè)由逗號(hào)分隔的郵件地址組成的字符串。完整的類像這樣:
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)
使用這個(gè)字段的每個(gè)表單都將在處理該字段數(shù)據(jù)之前運(yùn)行這些方法。這個(gè)驗(yàn)證特定于該類型的字段,與后面如何使用它無關(guān)。
讓我們來創(chuàng)建一個(gè)簡(jiǎn)單的ContactForm來向你演示如何使用這個(gè)字段:
class ContactForm(forms.Form):
subject = forms.CharField(max_lenght=100)
message = forms.CharField()
sender = forms.EmailField()
recipients = MultiEmailField()
cc_myself = forms.BooleanField(required=False)
只需要簡(jiǎn)單地使用MultiEmailField,就和其它表達(dá)字段一樣。當(dāng)調(diào)用表單的is_valid()方法時(shí),MultiEmailField.clean()方法將作為驗(yàn)證過程的一部分運(yùn)行,它將調(diào)用自定義的to_python()和validate()方法。
驗(yàn)證特定字段屬性
繼續(xù)上面的例子,假設(shè)在ContactForm中,我們想確保recipients字段始終包含"fred@example.com"。這是特定于我們這個(gè)表達(dá)的驗(yàn)證,所以我們不打算將它放在通用的MultiEmailField類中,我們將編寫一個(gè)運(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è)我們添加另外一個(gè)需求到我們的ContactForm表單中:如果cc_myself字段為True,那么subject必須包含單詞"help"。我們的這個(gè)驗(yàn)證包含多個(gè)字段,所以表單的clean()方法是個(gè)不錯(cuò)的地方。注意,我們這里討論的是表單的clean()方法,之前我們編寫過字段的clean()方法。區(qū)別字段和表單之間的差別非常重要。字段是單個(gè)數(shù)據(jù),表單是字段的集合。
在調(diào)用表單clean()方法的時(shí)候,所有字段的驗(yàn)證方法已經(jīng)執(zhí)行完,所以self.cleaned_data填充的是目前為止已經(jīng)合法的數(shù)據(jù)。所以你需要記住這個(gè)事實(shí),你需要驗(yàn)證的字段可能沒有通過初始的字段檢查。
在這一步,有兩種方法報(bào)告錯(cuò)誤。最簡(jiǎn)單的方法是在表單的頂端顯示錯(cuò)誤。你可以在clean()方法中拋出ValidationError來創(chuàng)建錯(cuò)誤。例如,
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)證錯(cuò)誤,表單將在表單的頂部顯示(通常是)描述該問題的一個(gè)錯(cuò)誤信息。
注意,示例代碼中super(ContactForm, self).clean()的調(diào)用是為了維持父類中的驗(yàn)證邏輯。如果你的表單繼承自另外一個(gè)在clean()方法中沒有返回一個(gè)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')
...
第二種方法涉及將錯(cuò)誤消息關(guān)聯(lián)到某個(gè)字段。在這種情況下,讓我們?cè)诒韱蔚娘@示中分別關(guān)聯(lián)一個(gè)錯(cuò)誤信息到“subject” 和“cc_myself” 行。在實(shí)際應(yīng)用中要小心,因?yàn)樗赡軐?dǎo)致表單的輸出變得令人困惑。我們只是向你展示這里可以怎么做,在特定的情況下,需要你和你的設(shè)計(jì)人員確定什么是好的方法。我們的新代碼(代替前面的示例)像這樣:
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() 的第二個(gè)參數(shù)可以是一個(gè)簡(jiǎn)單的字符串,但更傾向是ValidationError的一個(gè)實(shí)例
編寫Validator
驗(yàn)證器是一個(gè)可調(diào)用的對(duì)象,它接受一個(gè)值,并在不符合一些規(guī)則時(shí)候拋出ValidationError異常。驗(yàn)證器有助于在不同類型的字段之間重復(fù)使用驗(yàn)證邏輯。
例如,這個(gè)驗(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)行之前會(huì)轉(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