使用表單¶
除非您計劃建置只發布內容,不接受訪客輸入的網站和應用程式,否則您將需要了解和使用表單。
Django 提供一系列工具和函式庫,可協助您建置表單以接受網站訪客的輸入,然後處理和回應這些輸入。
HTML 表單¶
在 HTML 中,表單是 <form>...</form>
內的一組元素,允許訪客執行諸如輸入文字、選擇選項、操作物件或控制項等操作,然後將該資訊傳送回伺服器。
其中一些表單介面元素(例如文字輸入或核取方塊)是內建在 HTML 本身的。其他元素則複雜得多;彈出日期選取器或允許您移動滑桿或操作控制項的介面通常會使用 JavaScript 和 CSS 以及 HTML 表單 <input>
元素來實現這些效果。
除了其 <input>
元素之外,表單還必須指定兩件事
哪裡:應將對應於使用者輸入的資料傳回的 URL
如何:應傳回資料的 HTTP 方法
例如,Django 管理介面的登入表單包含幾個 <input>
元素:一個 type="text"
用於使用者名稱,一個 type="password"
用於密碼,以及一個 type="submit"
用於「登入」按鈕。它還包含一些使用者看不到的隱藏文字欄位,Django 使用這些欄位來判斷下一步該怎麼做。
它還告訴瀏覽器,表單資料應傳送到 <form>
的 action
屬性中指定的 URL(即 /admin/
),並且應使用 method
屬性指定的 HTTP 機制(即 post
)傳送。
當觸發 <input type="submit" value="登入">
元素時,資料會傳回 /admin/
。
GET
和 POST
¶
GET
和 POST
是處理表單時唯一要使用的 HTTP 方法。
Django 的登入表單是使用 POST
方法傳回的,其中瀏覽器會將表單資料捆綁起來,進行傳輸編碼,傳送到伺服器,然後接收回其回應。
相較之下,GET
會將提交的資料捆綁到字串中,並以此來組成 URL。URL 包含必須傳送資料的地址,以及資料的索引鍵和值。如果您在 Django 文件中執行搜尋,您可以看到實際運作情況,這將產生格式為 https://django-docs.dev.org.tw/search/?q=forms&release=1
的 URL。
GET
和 POST
通常用於不同的目的。
任何可用於變更系統狀態的請求(例如,在資料庫中進行變更的請求)都應使用 POST
。GET
僅應用於不影響系統狀態的請求。
GET
也不適用於密碼表單,因為密碼會出現在 URL 中,因此也會以純文字形式出現在瀏覽器歷史記錄和伺服器記錄中。它也不適用於大量資料或二進位資料(例如圖像)。將 GET
請求用於管理表單的 Web 應用程式是一種安全風險:攻擊者可以很容易地模仿表單的請求,以取得對系統敏感部分的存取權。POST
與其他保護機制(例如 Django 的CSRF 保護)搭配使用,可提供對存取權的更多控制。
另一方面,GET
適用於 Web 搜尋表單等項目,因為代表 GET
請求的 URL 可以很容易地加入書籤、共用或重新提交。
Django 在表單中的角色¶
處理表單是一項複雜的工作。想想 Django 的管理介面,其中可能需要準備多個不同類型的資料項目,以便在表單中顯示、以 HTML 呈現、使用方便的介面編輯、傳回伺服器、驗證和清除,然後儲存或傳遞以進行進一步處理。
Django 的表單功能可以簡化和自動化這項工作的大部分,而且可以比大多數程式設計師自己編寫程式碼做得更安全。
Django 處理表單中涉及的三個不同部分
準備和重組資料,使其準備好進行呈現
為資料建立 HTML 表單
接收和處理來自用戶端的提交表單和資料
可以編寫程式碼來手動執行所有這些操作,但是 Django 可以為您處理所有這些操作。
Django 中的表單¶
我們簡要描述了 HTML 表單,但是 HTML <form>
只是所需機制的其中一部分。
在 Web 應用程式的環境中,「表單」可能指的是 HTML <form>
,或是產生它的 Django Form
,或是提交時傳回的結構化資料,或是這些部分端對端的完整工作集合。
Django Form
類別¶
此元件系統的核心是 Django 的 Form
類別。就像 Django 模型描述物件的邏輯結構、其行為以及其各部分向我們呈現的方式一樣,Form
類別描述表單,並決定其運作方式和外觀。
與模型類別的欄位對應於資料庫欄位的方式類似,表單類別的欄位對應於 HTML 表單 <input>
元素。(ModelForm
透過 Form
將模型類別的欄位對應至 HTML 表單 <input>
元素;這就是 Django 管理介面所基於的。)
表單的欄位本身就是類別;它們管理表單資料並在提交表單時執行驗證。DateField
和 FileField
處理非常不同類型的資料,並且必須對其執行不同的操作。
表單欄位在瀏覽器中以 HTML「小工具」(使用者介面機制)的形式呈現給使用者。每個欄位類型都有一個適當的預設Widget 類別,但是可以根據需要覆寫這些類別。
表單的實例化、處理和呈現¶
在 Django 中呈現一個物件時,我們通常會:
在視圖中取得它(例如,從資料庫獲取)。
將它傳遞給模板上下文。
使用模板變數將其展開為 HTML 標記。
在模板中呈現表單所涉及的工作與呈現任何其他類型的物件幾乎相同,但有一些關鍵差異。
如果模型實例不包含任何資料,在模板中使用它幾乎沒有任何用處。另一方面,呈現一個未填寫的表單是很有意義的,這就是我們希望使用者填寫表單時的做法。
因此,當我們在視圖中處理模型實例時,通常會從資料庫中檢索它。當我們處理表單時,通常會在視圖中實例化它。
當我們實例化表單時,我們可以選擇將其留空或預先填寫,例如:
來自已儲存模型實例的資料(如用於編輯的管理表單)。
我們從其他來源收集的資料。
從先前的 HTML 表單提交接收的資料。
最後一種情況最有趣,因為它讓使用者不僅可以閱讀網站,還可以將資訊傳送回網站。
建立表單¶
需要完成的工作¶
假設您想在您的網站上建立一個簡單的表單,以獲取使用者的姓名。您需要在模板中包含類似以下的內容:
<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>
這會告知瀏覽器使用 POST
方法將表單資料傳回至 URL /your-name/
。它將顯示一個標示為「你的姓名:」的文字欄位和一個標示為「確定」的按鈕。如果模板上下文包含 current_name
變數,則會使用該變數預先填寫 your_name
欄位。
您需要一個視圖來呈現包含 HTML 表單的模板,並且可以視情況提供 current_name
欄位。
當表單提交時,傳送到伺服器的 POST
請求將包含表單資料。
現在您還需要一個對應於 /your-name/
URL 的視圖,該視圖會在請求中找到適當的鍵/值對,然後處理它們。
這是一個非常簡單的表單。在實務上,表單可能包含數十個或數百個欄位,其中許多欄位可能需要預先填寫,並且我們可能期望使用者在完成操作之前多次完成編輯-提交週期。
我們可能需要一些驗證在瀏覽器中進行,甚至在提交表單之前;我們可能希望使用更複雜的欄位,讓使用者可以執行諸如從日曆中選擇日期等操作。
在這一點上,讓 Django 為我們完成大部分工作會容易得多。
在 Django 中建立表單¶
Form
類別¶
我們已經知道我們想要 HTML 表單的外觀。在 Django 中,我們的起點是這樣:
forms.py
¶from django import forms
class NameForm(forms.Form):
your_name = forms.CharField(label="Your name", max_length=100)
這定義了一個帶有單一欄位(your_name
)的 Form
類別。我們已將一個人性化的標籤套用到該欄位,當它呈現時將會出現在 <label>
中(雖然在這種情況下,我們指定的 label
實際上與我們省略它時會自動生成的標籤相同)。
欄位的最大允許長度由 max_length
定義。這會執行兩件事。它會在 HTML <input>
上放置 maxlength="100"
(因此瀏覽器應首先阻止使用者輸入超過該數量的字元)。這也表示當 Django 從瀏覽器接收回表單時,它會驗證資料的長度。
Form
實例有一個 is_valid()
方法,該方法會對其所有欄位執行驗證常式。當呼叫此方法時,如果所有欄位都包含有效資料,它將:
傳回
True
。將表單的資料放置在其
cleaned_data
屬性中。
當第一次呈現時,整個表單將如下所示:
<label for="your_name">Your name: </label>
<input id="your_name" type="text" name="your_name" maxlength="100" required>
請注意,它不包含 <form>
標籤或提交按鈕。我們必須自己在模板中提供這些。
視圖¶
傳送回 Django 網站的表單資料由視圖處理,通常是發佈表單的同一個視圖。這讓我們可以重複使用一些相同的邏輯。
若要處理表單,我們需要在我們想要發佈它的 URL 的視圖中實例化它。
views.py
¶from django.http import HttpResponseRedirect
from django.shortcuts import render
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
請求抵達此視圖,它會建立一個空的表單實例並將其放置在模板上下文中進行呈現。這是我們第一次訪問 URL 時可以預期的情況。
如果表單是使用 POST
請求提交的,則視圖會再次建立表單實例,並使用來自請求的資料填寫它:form = NameForm(request.POST)
。這稱為「將資料繫結到表單」(它現在是一個*繫結的*表單)。
我們呼叫表單的 is_valid()
方法;如果它不是 True
,我們會回到帶有表單的模板。這次表單不再為空(*未繫結*),因此 HTML 表單將會填入先前提交的資料,您可以在其中根據需要編輯和更正。
如果 is_valid()
為 True
,我們現在可以在其 cleaned_data
屬性中找到所有經過驗證的表單資料。我們可以使用此資料來更新資料庫或執行其他處理,然後傳送 HTTP 重新導向到瀏覽器,告訴它接下來要去哪裡。
模板¶
我們不需要在我們的 name.html
模板中執行太多操作。
<form action="/your-name/" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>
Django 的模板語言會從 {{ form }}
將所有表單的欄位及其屬性解壓縮到 HTML 標記中。
表單和跨站請求偽造保護
Django 隨附易於使用的跨站請求偽造保護。當透過 POST
提交表單且啟用 CSRF 保護時,您必須使用 csrf_token
模板標籤,如前面的範例所示。但是,由於 CSRF 保護並未直接與模板中的表單連結,因此本文檔的以下範例中省略了此標籤。
HTML5 輸入類型和瀏覽器驗證
如果您的表單包含 URLField
、EmailField
或任何整數欄位類型,Django 將使用 url
、email
和 number
HTML5 輸入類型。預設情況下,瀏覽器可能會在這些欄位上套用自己的驗證,這可能比 Django 的驗證更嚴格。如果您想要停用此行為,請在 form
標籤上設定 novalidate
屬性,或在欄位上指定不同的 widget,例如 TextInput
。
我們現在有一個可運作的網頁表單,它由 Django 的 Form
類別描述,並由視圖處理,最後渲染為 HTML 的 <form>
。
這就是您入門所需的一切,但表單框架的功能遠不止於此。一旦您了解上述過程的基本原理,您應該就能掌握表單系統的其他功能,並準備好進一步了解其底層機制。
更多關於 Django Form
類別的資訊¶
所有表單類別都是作為 django.forms.Form
或 django.forms.ModelForm
的子類別建立的。您可以將 ModelForm
視為 Form
的子類別。Form
和 ModelForm
實際上繼承自一個(私有的)BaseForm
類別的通用功能,但這個實作細節通常並不重要。
模型與表單
事實上,如果您的表單將直接用於新增或編輯 Django 模型,ModelForm 可以為您節省大量的時間、精力和程式碼,因為它會從 Model
類別建立表單,以及適當的欄位及其屬性。
綁定和未綁定的表單實例¶
綁定和未綁定的表單之間的區別非常重要。
未綁定的表單沒有關聯的資料。當渲染給使用者時,它將是空的或包含預設值。
綁定的表單有已提交的資料,因此可以用於判斷該資料是否有效。如果渲染了無效的綁定表單,它可以包含內嵌錯誤訊息,告知使用者要更正哪些資料。
表單的 is_bound
屬性會告訴您表單是否已綁定資料。
更多關於欄位的資訊¶
考慮一個比我們上面的最小範例更有用的表單,我們可以利用它在個人網站上實作「聯絡我」的功能。
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
只是可用的欄位類型中的三個;完整的列表可以在 表單欄位 中找到。
小部件¶
每個表單欄位都有一個對應的 小部件類別,它又對應於 HTML 表單小部件,例如 <input type="text">
。
在大多數情況下,欄位將具有合理的預設小部件。例如,預設情況下,CharField
將具有 TextInput
小部件,它會在 HTML 中產生 <input type="text">
。如果您需要 <textarea>
而不是這樣,您需要在定義表單欄位時指定適當的小部件,就像我們為 message
欄位所做的那樣。
欄位資料¶
無論表單提交的資料是什麼,一旦透過呼叫 is_valid()
成功驗證(且 is_valid()
返回 True
),驗證後的表單資料將位於 form.cleaned_data
字典中。這些資料會被妥善地轉換為 Python 類型供您使用。
注意
您仍然可以從此時的 request.POST
直接存取未經驗證的資料,但驗證後的資料更好。
在上面的聯絡表單範例中,cc_myself
將是一個布林值。同樣,諸如 IntegerField
和 FloatField
之類的欄位會將值分別轉換為 Python 的 int
和 float
。
以下是如何在處理此表單的視圖中處理表單資料的方式
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/")
提示
有關從 Django 發送電子郵件的更多資訊,請參閱 發送電子郵件。
某些欄位類型需要一些額外的處理。例如,使用表單上傳的檔案需要以不同的方式處理(它們可以從 request.FILES
而不是 request.POST
擷取)。有關如何使用表單處理檔案上傳的詳細資訊,請參閱 將上傳的檔案綁定到表單。
使用表單模板¶
將表單放入模板中,您只需將表單實例放置到模板內容中即可。因此,如果您的表單在內容中稱為 form
,則 {{ form }}
將適當地渲染其 <label>
和 <input>
元素。
其他表單模板裝飾
請不要忘記,表單的輸出不包含周圍的 <form>
標籤或表單的 submit
控制項。您必須自己提供這些。
可重複使用的表單模板¶
渲染表單時的 HTML 輸出本身是透過模板產生的。您可以透過建立適當的模板檔案並設定自訂的 FORM_RENDERER
來控制它,以在整個網站範圍內使用該 form_template_name
。您也可以透過覆蓋表單的 template_name
屬性來為每個表單自訂,以使用自訂模板渲染表單,或將模板名稱直接傳遞給 Form.render()
。
下面的範例將導致 {{ form }}
被渲染為 form_snippet.html
模板的輸出。
在您的模板中
# In your template:
{{ form }}
# In form_snippet.html:
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
然後您可以設定 FORM_RENDERER
設定
settings.py
¶from django.forms.renderers import TemplatesSetting
class CustomFormRenderer(TemplatesSetting):
form_template_name = "form_snippet.html"
FORM_RENDERER = "project.settings.CustomFormRenderer"
… 或對於單一表單
class MyForm(forms.Form):
template_name = "form_snippet.html"
...
… 或對於表單實例的單一渲染,將模板名稱傳遞給 Form.render()
。以下是此方法在視圖中使用的範例
def index(request):
form = MyForm()
rendered_form = form.render("form_snippet.html")
context = {"form": rendered_form}
return render(request, "index.html", context)
請參閱 將表單輸出為 HTML 以取得更多詳細資訊。
可重複使用的欄位組模板¶
每個欄位都可作為表單的屬性使用,在範本中使用 {{ form.欄位名稱 }}
。欄位有一個 as_field_group()
方法,它會將欄位的相關元素(如標籤、小工具、錯誤和輔助文字)渲染為一個群組。
這允許編寫通用的範本,以所需的佈局排列欄位元素。例如:
{{ form.non_field_errors }}
<div class="fieldWrapper">
{{ form.subject.as_field_group }}
</div>
<div class="fieldWrapper">
{{ form.message.as_field_group }}
</div>
<div class="fieldWrapper">
{{ form.sender.as_field_group }}
</div>
<div class="fieldWrapper">
{{ form.cc_myself.as_field_group }}
</div>
預設情況下,Django 使用 "django/forms/field.html"
範本,該範本設計用於預設的 "django/forms/div.html"
表單樣式。
可以透過在專案層級的 FORM_RENDERER
中設定 field_template_name
來客製化預設範本。
from django.forms.renderers import TemplatesSetting
class CustomFormRenderer(TemplatesSetting):
field_template_name = "field_snippet.html"
... 或在單個欄位上進行設定:
class MyForm(forms.Form):
subject = forms.CharField(template_name="my_custom_template.html")
...
... 或者透過呼叫 BoundField.render()
並提供範本名稱,在每個請求的基礎上進行設定:
def index(request):
form = ContactForm()
subject = form["subject"]
context = {"subject": subject.render("my_custom_template.html")}
return render(request, "index.html", context)
手動渲染欄位¶
也可以更精細地控制欄位的渲染。這通常會在自訂欄位範本中完成,以便範本可以編寫一次並重複用於每個欄位。但是,也可以直接從表單上的欄位屬性進行存取。例如:
{{ 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>
渲染表單錯誤訊息¶
這種彈性的代價是需要多做一些工作。到目前為止,我們還不必擔心如何顯示表單錯誤,因為這已為我們處理好。在這個例子中,我們必須確保處理每個欄位的任何錯誤以及整個表單的任何錯誤。請注意表單頂部的 {{ form.non_field_errors }}
和每個欄位錯誤的範本查找。
使用 {{ form.欄位名稱.errors }}
會顯示一個表單錯誤列表,並渲染為一個無序列表。這可能看起來像這樣:
<ul class="errorlist">
<li>Sender is required.</li>
</ul>
該列表有一個 CSS 類別 errorlist
,允許您設定其外觀樣式。如果您想進一步客製化錯誤的顯示方式,可以透過迴圈遍歷它們來實現:
{% if form.subject.errors %}
<ol>
{% for error in form.subject.errors %}
<li><strong>{{ error|escape }}</strong></li>
{% endfor %}
</ol>
{% endif %}
當使用像 form.as_p()
這樣的輔助程式時,非欄位錯誤(和/或在表單頂部渲染的隱藏欄位錯誤)將會以額外的類別 nonfield
渲染,以幫助將它們與特定於欄位的錯誤區分開來。例如,{{ form.non_field_errors }}
看起來會像這樣:
<ul class="errorlist nonfield">
<li>Generic validation error</li>
</ul>
有關錯誤、樣式設定以及如何在範本中使用表單屬性的更多資訊,請參閱 表單 API。
迴圈遍歷表單的欄位¶
如果您為每個表單欄位使用相同的 HTML,可以使用 {% for %}
迴圈來依序迴圈遍歷每個欄位,以減少重複的程式碼:
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
<p class="help" id="{{ field.auto_id }}_helptext">
{{ field.help_text|safe }}
</p>
{% endif %}
</div>
{% endfor %}
{{ field }}
上有用的屬性包括:
{{ field.errors }}
輸出一個
<ul class="errorlist">
,其中包含與此欄位對應的任何驗證錯誤。您可以使用{% for error in field.errors %}
迴圈來自訂錯誤的呈現方式。在這種情況下,迴圈中的每個物件都是一個包含錯誤訊息的字串。{{ field.field }}
此
BoundField
包裝的表單類別中的Field
實例。您可以使用它來存取Field
屬性,例如{{ char_field.field.max_length }}
。{{ field.help_text }}
已與欄位關聯的任何輔助文字。
{{ field.html_name }}
將在輸入元素的 name 欄位中使用的欄位名稱。如果已設定,這會將表單前綴納入考量。
{{ field.id_for_label }}
將用於此欄位的 ID(在上面的範例中為
id_email
)。如果您要手動建構標籤,您可能想使用它來代替label_tag
。例如,如果您有一些內嵌 JavaScript 並且想避免硬式編碼欄位的 ID,這也很有用。{{ field.is_hidden }}
如果表單欄位是隱藏欄位,則此屬性為
True
,否則為False
。作為範本變數,它並不是特別有用,但在條件測試中可能很有用,例如:
{% if field.is_hidden %}
{# Do something special #}
{% endif %}
{{ field.label }}
欄位的標籤,例如
Email address
。{{ field.label_tag }}
用適當的 HTML
<label>
標籤包裝的欄位標籤。這包含表單的label_suffix
。例如,預設的label_suffix
是冒號:<label for="id_email">Email address:</label>
{{ field.legend_tag }}
與
field.label_tag
類似,但使用<legend>
標籤代替<label>
,適用於以<fieldset>
包裝的多個輸入的小工具。{{ field.use_fieldset }}
如果表單欄位的小工具包含多個應在語義上分組到
<fieldset>
中並帶有<legend>
以提高輔助功能,則此屬性為True
。在範本中的範例用法:
{% if field.use_fieldset %}
<fieldset>
{% if field.label %}{{ field.legend_tag }}{% endif %}
{% else %}
{% if field.label %}{{ field.label_tag }}{% endif %}
{% endif %}
{{ field }}
{% if field.use_fieldset %}</fieldset>{% endif %}
{{ field.value }}
欄位的值。例如
someone@example.com
。
另請參閱
如需屬性和方法的完整列表,請參閱 BoundField
。
進一步的主題¶
這涵蓋了基本知識,但表單的功能遠不止於此:
另請參閱
- 表單參考
涵蓋完整的 API 參考,包括表單欄位、表單小工具,以及表單和欄位驗證。