關(guān)于這頁文檔
這頁文檔簡單介紹Web 表單的基本概念和它們在Django 中是如何處理的。關(guān)于表單API 某方面的細(xì)節(jié),請參見表單 API、表單的字段和表單和字段的檢驗。
除非你計劃構(gòu)建的網(wǎng)站和應(yīng)用只是發(fā)布內(nèi)容而不接受訪問者的輸入,否則你將需要理解并使用表單。
Django 提供廣泛的工具和庫來幫助你構(gòu)建表單來接收網(wǎng)站訪問者的輸入,然后處理以及響應(yīng)輸入。
在HTML中,表單是位于<form>...</form>
之間的元素的集合,它們允許訪問者輸入文本、選擇選項、操作對象和控制等等,然后將信息發(fā)送回服務(wù)器。
某些表單的元素 —— 文本輸入和復(fù)選框 —— 非常簡單而且內(nèi)建于HTML 本身。其它的表單會復(fù)雜些;例如彈出一個日期選擇對話框的界面、允許你移動滾動條的界面、使用JavaScript 和CSS 以及HTML 表單<input>
元素來實現(xiàn)操作控制的界面。
與<input>
元素一樣,一個表單必須指定兩樣?xùn)|西:
例如,Django Admin 站點的登錄表單包含幾個<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
方法。
Django 的登錄表單使用POST 方法,在這個方法中瀏覽器組合表單數(shù)據(jù)、對它們進(jìn)行編碼以用于傳輸、將它們發(fā)送到服務(wù)器然后接收它的響應(yīng)。
相反,GET 組合提交的數(shù)據(jù)為一個字符串,然后使用它來生成一個URL。這個URL 將包含數(shù)據(jù)發(fā)送的地址以及數(shù)據(jù)的鍵和值。如果你在Django 文檔中做一次搜索,你會立即看到這點,此時將生成一個https://docs.djangoproject.com/search/?q=forms&release=1
形式的URL。
GET
和POST
用于不同的目的。
用于改變系統(tǒng)狀態(tài)的請求 —— 例如,給數(shù)據(jù)庫帶來變化的請求 —— 應(yīng)該使用POST
。GET
只應(yīng)該用于不會影響系統(tǒng)狀態(tài)的請求。
GET
還不適合密碼表單,因為密碼將出現(xiàn)在URL 中,以及瀏覽器的歷史和服務(wù)器的日志中,而且都是以普通的文本格式。它還不適合數(shù)據(jù)量大的表單和二進(jìn)制數(shù)據(jù),例如一張圖片。使用GET 請求作為管理站點的表單具有安全隱患:攻擊者很容易模擬表單請求來取得系統(tǒng)的敏感數(shù)據(jù)。POST
,如果與其它的保護(hù)措施結(jié)合將對訪問提供更多的控制,例如Django 的CSRF 保護(hù)。
另一個方面,GET
適合網(wǎng)頁搜索這樣的表單,因為這種表示一個GET
請求的URL 可以很容易地作為書簽、分享和重新提交。
Django 在表單中的角色
處理表單是一件很復(fù)雜的事情??紤]一下Django 的Admin 站點,不同類型的大量數(shù)據(jù)項需要在一個表單中準(zhǔn)備好、渲染成HTML、使用一個方便的界面編輯、返回給服務(wù)器、驗證并清除,然后保存或者向后繼續(xù)處理。
Django 的表單功能可以簡化并自動化大部分這些工作,而且還可以比大部分程序員自己所編寫的代碼更安全。
Django 會處理表單工作中的三個顯著不同的部分:
可以手工編寫代碼來實現(xiàn),但是Django 可以幫你完成所有這些工作。
我們已經(jīng)簡短講述HTML 表單,但是HTML的<form>
只是其機制的一部分。
在一個Web 應(yīng)用中,‘表單’可能指HTML <form>
、或者生成它的Django 的Form
、或者提交時發(fā)送的結(jié)構(gòu)化數(shù)據(jù)、或者這些部分的總和。
表單系統(tǒng)的核心部分是Django 的Form
類。Django 的模型描述一個對象的邏輯結(jié)構(gòu)、行為以及展現(xiàn)給我們的方式,與此類似,Form
類描述一個表單并決定它如何工作和展現(xiàn)。
模型類的字典映射到數(shù)據(jù)庫的字典,與此類似,表單類的字段映射到HTML 的表單<input>
元素。(ModelForm
通過一個Form
映射模型類的字段到HTML 表單的<input>
元素;Django 的Admin 站點就是基于這個)。
表單的字段本身也是類;它們管理表單的數(shù)據(jù)并在表單提交時進(jìn)行驗證。DateField
和FileField
處理的數(shù)據(jù)類型差別很大,必須完成不同的事情。
表單字段在瀏覽器中呈現(xiàn)給用戶的是一個HTML 的“widget” —— 用戶界面的一個片段。每個字段類型都有一個合適的默認(rèn)Widget 類,需要時可以覆蓋。
在Django 中渲染一個對象時,我們通常:
在模板中渲染表單和渲染其它類型的對象幾乎一樣,除了幾個關(guān)鍵的差別。
在模型實例不包含數(shù)據(jù)的情況下,在模板中對它做處理很少有什么用處。但是渲染一個未填充的表單卻非常有意義 —— 我們希望用戶去填充它。
所以當(dāng)我們在視圖中處理模型實例時,我們一般從數(shù)據(jù)庫中獲取它。當(dāng)我們處理表單時,我們一般在視圖中實例化它。
當(dāng)我們實例化表單時,我們可以選擇讓它為空還是預(yù)先填充它,例如使用:
最后一種情況最令人關(guān)注,因為它使得用戶可以不只是閱讀一個網(wǎng)站,而且可以給網(wǎng)站返回信息。
假設(shè)你想在你的網(wǎng)站上創(chuàng)建一個簡單的表單,以獲得用戶的名字。你需要類似這樣的模板:
<form action="/your-name/" method="post">
<label for="your_name">Your name: </label>
<input id="your_name" type="text" name="your_name" value="{{ current_name }}">
<input type="submit" value="OK">
</form>
這告訴瀏覽器發(fā)送表單的數(shù)據(jù)到URL /your-name/
,并使用POST
方法。它將顯示一個標(biāo)簽為"Your name:"的文本字段,和一個"OK"按鈕。如果模板上下文包含一個current_name
變量,它將用于預(yù)填充your_name
字段。
你將需要一個視圖來渲染這個包含HTML 表單的模板,并提供合適的current_name
字段。
當(dāng)表單提交時,發(fā)往服務(wù)器的POST
請求將包含表單數(shù)據(jù)。
現(xiàn)在你還需要一個對應(yīng)/your-name/
URL 的視圖,它在請求中找到正確的鍵/值對,然后處理它們。
這是一個非常簡單的表單。實際應(yīng)用中,一個表單可能包含幾十上百個字段,其中大部分需要預(yù)填充,而且我們預(yù)料到用戶將來回編輯-提交幾次才能完成操作。
我們可能需要在表單提交之前,在瀏覽器端作一些驗證。我們可能想使用非常復(fù)雜的字段,以允許用戶做類似從日歷中挑選日期這樣的事情,等等。
這個時候,讓Django 來為我們完成大部分工作是很容易的。
我們已經(jīng)計劃好了我們的 HTML 表單應(yīng)該呈現(xiàn)的樣子。在Django 中,我們的起始點是這里:
#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>
中(在這個例子中,即使我們省略它,我們指定的label
還是會自動生成)。
字段允許的最大長度通過max_length
定義。它完成兩件事情。首先,它在HTML 的<input>
上放置一個maxlength="100"
(這樣瀏覽器將在第一時間阻止用戶輸入多于這個數(shù)目的字符)。它還意味著當(dāng)Django 收到瀏覽器發(fā)送過來的表單時,它將驗證數(shù)據(jù)的長度。
Form
的實例具有一個is_valid()
方法,它為所有的字段運行驗證的程序。當(dāng)調(diào)用這個方法時,如果所有的字段都包含合法的數(shù)據(jù),它將:
True
cleaned_data
屬性中。完整的表單,第一次渲染時,看上去將像:
<label for="your_name">Your name: </label>
<input id="your_name" type="text" name="your_name" maxlength="100">
注意它不包含 <form>
標(biāo)簽和提交按鈕。我們必須自己在模板中提供它們。
發(fā)送給Django 網(wǎng)站的表單數(shù)據(jù)通過一個視圖處理,一般和發(fā)布這個表單的是同一個視圖。這允許我們重用一些相同的邏輯。
當(dāng)處理表單時,我們需要在視圖中實例化它:
#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'll create a blank form
else:
form = NameForm()
return render(request, 'name.html', {'form': form})
如果訪問視圖的是一個GET
請求,它將創(chuàng)建一個空的表單實例并將它放置到要渲染的模板的上下文中。這是我們在第一個訪問該URL 時預(yù)期發(fā)生的情況。
如果表單的提交使用POST
請求,那么視圖將再次創(chuàng)建一個表單實例并使用請求中的數(shù)據(jù)填充它:form = NameForm(request.POST)
。這叫做”綁定數(shù)據(jù)至表單“(它現(xiàn)在是一個綁定的表單)。
我們調(diào)用表單的is_valid()
方法;如果它不為True
,我們將帶著這個表單返回到模板。這時表單不再為空(未綁定),所以HTML 表單將用之前提交的數(shù)據(jù)填充,然后可以根據(jù)要求編輯并改正它。
如果is_valid()
為True
,我們將能夠在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)記 。
表單和跨站請求偽造的防護(hù)
Django 原生支持一個簡單易用的跨站請求偽造的防護(hù)。當(dāng)提交一個啟用CSRF 防護(hù)的
POST
表單時,你必須使用上面例子中的csrf_token
模板標(biāo)簽。然而,因為CSRF 防護(hù)在模板中不是與表單直接捆綁在一起的,這個標(biāo)簽在這篇文檔的以下示例中將省略。HTML5 輸入類型和瀏覽器驗證
如果你的表單包含
URLField
、EmailField
和其它整數(shù)字段類似,Django 將使用url
、number
這樣的HTML5 輸入類型。默認(rèn)情況下,瀏覽器可能會對這些字段進(jìn)行它們自身的驗證,這些驗證可能比Django 的驗證更嚴(yán)格。如果你想禁用這個行為,請設(shè)置form
標(biāo)簽的novalidate
屬性,或者指定一個不同的字段,如TextInput
。
現(xiàn)在我們有了一個可以工作的網(wǎng)頁表單,它通過Django Form 描述、通過視圖處理并渲染成一個HTML <form>
。
這是你入門所需要知道的所有內(nèi)容,但是表單框架為了提供了更多的內(nèi)容。一旦你理解了上面描述的基本處理過程,你應(yīng)該可以理解表單系統(tǒng)的其它功能并準(zhǔn)備好學(xué)習(xí)更多的底層機制。
所有的表單類都作為django.forms.Form
的子類創(chuàng)建,包括你在Django 管理站點中遇到的ModelForm
。
模型和表單
實際上,如果你的表單打算直接用來添加和編輯Django 的模型,ModelForm 可以節(jié)省你的許多時間、精力和代碼,因為它將根據(jù)
Model
類構(gòu)建一個表單以及適當(dāng)?shù)淖侄魏蛯傩浴?/p>
綁定的和未綁定的表單 之間的區(qū)別非常重要:
表單的is_bound
屬性將告訴你一個表單是否具有綁定的數(shù)據(jù)。
考慮一個比上面的迷你示例更有用的一個表單,我們可以用它來在一個個人網(wǎng)站上實現(xiàn)“聯(lián)系我”功能:
#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ù),一旦通過調(diào)用is_valid()
成功驗證(is_valid()
返回True
),驗證后的表單數(shù)據(jù)將位于form.cleaned_data
字典中。這些數(shù)據(jù)已經(jīng)為你轉(zhuǎn)換好為Python 的類型。
注
此時,你依然可以從
request.POST
中直接訪問到未驗證的數(shù)據(jù),但是訪問驗證后的數(shù)據(jù)更好一些。
在上面的聯(lián)系表單示例中,cc_myself
將是一個布爾值。類似地,IntegerField
和FloatField
字段分別將值轉(zhuǎn)換為Python 的int
和float
。
下面是在視圖中如何處理表單數(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, recipients)
return HttpResponseRedirect('/thanks/')
提示
關(guān)于Django 中如何發(fā)送郵件的更多信息,請參見發(fā)送郵件。
有些字段類型需要一些額外的處理。例如,使用表單上傳的文件需要不同地處理(它們可以從request.FILES
獲取,而不是request.POST
)。如何使用表單處理文件上傳的更多細(xì)節(jié),請參見綁定上傳的文件到一個表單。
你需要做的就是將表單實例放進(jìn)模板的上下文。如果你的表單在Contex
t 中叫做form
,那么{{ form }}
將正確地渲染它的<label>
和 <input>
元素。
表單模板的額外標(biāo)簽
不要忘記,表單的輸出不 包含
<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
實例的輸出{{ form.as_p }}
:
<p><label for="id_subject">Subject:</label>
<input id="id_subject" type="text" name="subject" maxlength="100" /></p>
<p><label for="id_message">Message:</label>
<input type="text" name="message" id="id_message" /></p>
<p><label for="id_sender">Sender:</label>
<input type="email" name="sender" id="id_sender" /></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>
,它被一起的label
標(biāo)簽引用。它對于確保屏幕閱讀軟件這類的輔助計算非常重要。你還可以自定義label 和 id 生成的方式。
更多信息參見 輸出表單為HTML。
我們沒有必要非要讓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)心如何展示錯誤信息,因為Django 已經(jīng)幫我們處理好。在下面的例子中,我們將自己處理每個字段的錯誤和表單整體的各種錯誤。注意,表單和模板頂部的{{ form.non_field_errors }}
查找每個字段的錯誤。
使用{{ form.name_of_field.errors }}
顯示表單錯誤的一個清單,并渲染成一個ul
??瓷先タ赡芟瘢?/p>
<ul class="errorlist">
<li>Sender is required.</li>
</ul>
這個ul
有一個errorlist
CSS 類型,你可以用它來定義外觀。如果你希望進(jìn)一步自定義錯誤信息的顯示,你可以迭代它們來實現(xiàn):
{% if form.subject.errors %}
<ol>
{% for error in form.subject.errors %}
<li><strong>{{ error|escape }}</strong></li>
{% endfor %}
</ol>
{% endif %}
空字段錯誤(以及使用form.as_p()
時渲染的隱藏字段錯誤)將渲染成一個額外的CSS 類型nonfield
以幫助區(qū)分每個字段的錯誤信息。例如,{{ form.non_field_errors }}
看上去會像:
<ul class="errorlist nonfield">
<li>Generic validation error</li>
</ul>
Changed in Django 1.8:
添加上面示例中提到的nonfield CSS 類型。
參見Forms API 以獲得關(guān)于錯誤、樣式以及在模板中使用表單屬性的更多內(nèi)容。
如果你為你的表單使用相同的HTML,你可以使用{% for %}
循環(huán)迭代每個字段來減少重復(fù)的代碼:
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
{{ field }}
中有用的屬性包括:
{{ field.label }}
字段的label
,例如Email address
。
{{ field.label_tag }}
包含在HTML <label>
標(biāo)簽中的字段Label
。它包含表單的label_suffix
。例如,默認(rèn)的label_suffix
是一個冒號:
<label for="id_email">Email address:</label>
{{ field.id_for_label }}
用于這個字段的ID
(在上面的例子中是id_email
)。如果你正在手工構(gòu)造label
,你可能想使用它代替label_tag
。如果你有一些內(nèi)嵌的JavaScript 并且想避免硬編碼字段的ID
,這也是有用的。
{{ field.value }}
字段的值,例如someone@example.com
。
{{ field.html_name }}
輸入元素的name
屬性中將使用的名稱。它將考慮到表單的前綴。
{{ field.help_text }}
與該字段關(guān)聯(lián)的幫助文檔。
{{ field.errors }}
輸出一個<ul class="errorlist">
,包含這個字段的驗證錯誤信息。你可以使用{% for error in field.errors %}
自定義錯誤的顯示。 這種情況下,循環(huán)中的每個對象只是一個包含錯誤信息的簡單字符串。
{{ field.is_hidden }}
如果字段是隱藏字段,則為True
,否則為False
。作為模板變量,它不是很有用處,但是可以用于條件測試,例如:
{% if field.is_hidden %}
{% endif %}
{{ field.field }}
表單類中的Field
實例,通過BoundField
封裝。你可以使用它來訪問Field
屬性,例如{% char_field.field.max_length %}。
如果你正在手工布局模板中的一個表單,而不是依賴Django 默認(rèn)的表單布局,你可能希望將<input type="hidden">
字段與非隱藏的字段區(qū)別對待。例如,因為隱藏的字段不會顯示,在該字段旁邊放置錯誤信息可能讓你的用戶感到困惑 —— 所以這些字段的錯誤應(yīng)該有區(qū)別地來處理。
Django 提供兩個表單方法,它們允許你獨立地在隱藏的和可見的字段上迭代:hidden_fields()
和visible_fields()
。下面是使用這兩個方法對前面一個例子的修改:
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in form.visible_fields %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
這個示例沒有處理隱藏字段中的任何錯誤信息。通常,隱藏字段中的錯誤意味著表單被篡改,因為正常的表單填寫不會改變它們。然而,你也可以很容易地為這些表單錯誤插入一些錯誤信息顯示出來。
如果你的網(wǎng)站在多個地方對表單使用相同的渲染邏輯,你可以保存表單的循環(huán)到一個單獨的模板中來減少重復(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 %}
如果你發(fā)現(xiàn)自己經(jīng)常這樣做,你可能需要考慮一下創(chuàng)建一個自定義的inclusion
標(biāo)簽。
這里只是基礎(chǔ),表單還可以完成更多的工作:
另見
表單參考 覆蓋完整的API 參考,包括表單字段、表單Widget 以及表單和字段的驗證。
譯者:Django 文檔協(xié)作翻譯小組,原文:Overview。
本文以 CC BY-NC-SA 3.0 協(xié)議發(fā)布,轉(zhuǎn)載請保留作者署名和文章出處。
Django 文檔協(xié)作翻譯小組人手緊缺,有興趣的朋友可以加入我們,完全公益性質(zhì)。交流群:467338606。