除非您打算建立只發(fā)布內(nèi)容的網(wǎng)站和應(yīng)用程序,并且不接受訪問者的輸入,否則您將需要理解和使用表格。
Django提供了一系列工具和庫,可幫助您構(gòu)建表單以接受來自站點(diǎn)訪問者的輸入,然后處理并響應(yīng)輸入。
在HTML中,表單是內(nèi)部元素的集合<form>...</form>,允許訪問者執(zhí)行諸如輸入文本,選擇選項(xiàng),操作對(duì)象或控件等操作,然后將該信息發(fā)送回服務(wù)器。
其中一些表單界面元素(文本輸入或復(fù)選框)內(nèi)置于HTML本身。其他則要復(fù)雜得多。彈出日期選擇器或允許您移動(dòng)滑塊或操縱控件的界面通常將使用JavaScript和CSS以及HTML表單<input>元素來實(shí)現(xiàn)這些效果。
<input>表單及其元素還必須指定兩件事:
Django表單處理流程
Django 的表單處理:視圖獲取請(qǐng)求,執(zhí)行所需的任何操作,包括從模型中讀取數(shù)據(jù),然后生成并返回HTML頁面(從模板中),我們傳遞一個(gè)包含要顯示的數(shù)據(jù)的上下文。使事情變得更復(fù)雜的是,服務(wù)器還需要能夠處理用戶提供的數(shù)據(jù),并在出現(xiàn)任何錯(cuò)誤時(shí),重新顯示頁面。
GET方法是通過鍵值對(duì)的方式顯示從用戶那邊獲取的數(shù)據(jù),然后通過“&”將其組合形成一個(gè)整體的字符串,最后加上“?”,將組合后的字符串拼接到URL內(nèi),生成一個(gè)url地址。它既不適用于大量數(shù)據(jù),也不適合于二進(jìn)制數(shù)據(jù)(例如圖像)。使用GET
管理表單請(qǐng)求的Web應(yīng)用程序存在安全風(fēng)險(xiǎn):攻擊者很容易模仿表單的請(qǐng)求來訪問系統(tǒng)的敏感部分。GET僅應(yīng)用于不影響系統(tǒng)狀態(tài)的請(qǐng)求。諸如Web搜索表單類,它可以輕松對(duì)請(qǐng)求到的URL進(jìn)行共享,提交等操作。
POST 方法
處理表格是一項(xiàng)復(fù)雜的業(yè)務(wù)??紤]一下Django的管理員,其中可能需要準(zhǔn)備好幾種不同類型的大量數(shù)據(jù),以表格形式顯示,呈現(xiàn)為HTML,使用便利的界面進(jìn)行編輯,返回到服務(wù)器,進(jìn)行驗(yàn)證和清理,然后保存或傳遞進(jìn)行進(jìn)一步處理。
Django的表單功能可以簡(jiǎn)化和自動(dòng)化大部分工作,并且比大多數(shù)程序員在編寫自己的代碼中所能做到的更加安全。
Django處理涉及表單的工作的三個(gè)不同部分:
這是有可能到手動(dòng)做這一切寫代碼,但Django的可以照顧這一切為您服務(wù)。
我們已經(jīng)簡(jiǎn)要描述了HTML表單,但是HTML <form>只是所需機(jī)制的一部分。
在Web應(yīng)用程序的上下文中,“表單”可能是指該HTML <form>或Form生成它的Django ,或者是提交時(shí)返回的結(jié)構(gòu)化數(shù)據(jù),或者是這些部分的端到端工作集合。
該組件系統(tǒng)的核心是Django的Form類。類與Django模型描述對(duì)象的邏輯結(jié)構(gòu),其行為以及向我們表示其部分的方式幾乎相同,一個(gè) Form類描述一種形式并確定其工作方式和外觀。
就像模型類的字段映射到數(shù)據(jù)庫字段一樣,表單類的字段映射到HTML表單<input>元素。(A 通過;ModelForm 將模型類的字段映射到HTML表單<input>元素 Form,這是Django管理員所基于的。)
表單的字段本身就是類。他們管理表單數(shù)據(jù)并在提交表單時(shí)執(zhí)行驗(yàn)證。一個(gè)DateField和 FileField手柄非常不同類型的數(shù)據(jù),并有做不同的事情吧。
表單字段在瀏覽器中以HTML“窗口小部件”的形式向用戶表示-一種用戶界面機(jī)制。每個(gè)字段類型都有一個(gè)適當(dāng)?shù)哪J(rèn) Widget類,但是可以根據(jù)需要覆蓋它們。
在Django中渲染對(duì)象時(shí),通常:
在模板中呈現(xiàn)表單與呈現(xiàn)任何其他類型的對(duì)象幾乎涉及相同的工作,但是存在一些關(guān)鍵區(qū)別。
對(duì)于不包含數(shù)據(jù)的模型實(shí)例,在模板中執(zhí)行任何操作幾乎是沒有用的。另一方面,呈現(xiàn)未填充的表單非常有意義-當(dāng)我們希望用戶填充它時(shí),這就是我們要做的。
因此,當(dāng)我們?cè)谝晥D中處理模型實(shí)例時(shí),通常會(huì)從數(shù)據(jù)庫中檢索它。當(dāng)我們處理表單時(shí),通常在視圖中實(shí)例化它。
實(shí)例化表單時(shí),我們可以選擇將其保留為空或預(yù)先填充,例如:
這些情況中的最后一個(gè)是最有趣的,因?yàn)樗褂脩舨粌H可以閱讀網(wǎng)站,而且還可以向其發(fā)送信息。
假設(shè)您想在您的網(wǎng)站上創(chuàng)建一個(gè)簡(jiǎn)單的表單,以獲得用戶名。您在模板中需要這樣的內(nèi)容:
<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>
這告訴瀏覽器/your-name/使用POST方法將表單數(shù)據(jù)返回到URL 。它將顯示一個(gè)文本字段,標(biāo)記為“您的姓名:”,以及一個(gè)標(biāo)記為“確定”的按鈕。如果模板上下文包含一個(gè)current_name 變量,它將用于預(yù)填充該your_name字段。
您將需要一個(gè)視圖來呈現(xiàn)包含HTML表單的模板,并且可以提供current_name適當(dāng)?shù)淖侄巍?/p>
提交表單后,POST發(fā)送到服務(wù)器的請(qǐng)求將包含表單數(shù)據(jù)。
現(xiàn)在,您還將需要一個(gè)與該/your-name/URL 對(duì)應(yīng)的視圖,該視圖將在請(qǐng)求中找到適當(dāng)?shù)逆I/值對(duì),然后對(duì)其進(jìn)行處理。
這是一個(gè)非常簡(jiǎn)單的形式。實(shí)際上,一個(gè)表單可能包含數(shù)十個(gè)或數(shù)百個(gè)字段,其中許多字段可能需要預(yù)先填充,并且我們可能希望用戶在結(jié)束操作之前先完成幾次編輯-提交循環(huán)。
甚至在提交表單之前,我們可能需要在瀏覽器中進(jìn)行一些驗(yàn)證;我們可能想使用更復(fù)雜的字段,使用戶可以執(zhí)行諸如從日歷中選擇日期之類的操作。
在這一點(diǎn)上,讓Django為我們完成大部分工作要容易得多。
我們已經(jīng)知道我們想要HTML表單的外觀了。我們?cè)贒jango中的起點(diǎn)是:
的forms.pyfrom django import forms
class NameForm(forms.Form):
your_name = forms.CharField(label='Your name', max_length=100)
這定義了一個(gè)Form具有單個(gè)字段(your_name)的類。我們?cè)谠撟侄紊蠎?yīng)用了一個(gè)人類友好的標(biāo)簽,該標(biāo)簽將在<label>呈現(xiàn)時(shí)顯示在標(biāo)簽上(盡管在這種情況下,label 我們指定的標(biāo)簽實(shí)際上與省略該標(biāo)簽時(shí)會(huì)自動(dòng)生成的標(biāo)簽 相同)。
字段的最大允許長度由定義 max_length。這有兩件事。它放在 maxlength="100"HTML上<input>(因此瀏覽器應(yīng)首先防止用戶輸入超過該數(shù)量的字符)。這也意味著,當(dāng)Django從瀏覽器接收回表單時(shí),它將驗(yàn)證數(shù)據(jù)的長度。
一個(gè)Form實(shí)例有一個(gè)is_valid()方法,它運(yùn)行于所有的字段驗(yàn)證程序。調(diào)用此方法時(shí),如果所有字段都包含有效數(shù)據(jù),它將:
首次渲染時(shí),整個(gè)表單將如下所示:
<label for="your_name">Your name: </label> <input id="your_name" type="text" name="your_name" maxlength="100" required>
請(qǐng)注意,它不包含<form>標(biāo)簽或提交按鈕。我們必須在模板中提供這些信息。
發(fā)送回Django網(wǎng)站的表單數(shù)據(jù)由視圖處理,通常與發(fā)布表單的視圖相同。這使我們可以重用某些相同的邏輯。
要處理表單,我們需要在視圖中將其實(shí)例化的URL實(shí)例化為:
的views.pyfrom 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請(qǐng)求到達(dá)此視圖,它將創(chuàng)建一個(gè)空表單實(shí)例并將其放置在要呈現(xiàn)的模板上下文中。這是我們第一次訪問URL時(shí)可以預(yù)期的情況。
如果表單是使用POST請(qǐng)求提交的,則視圖將再次創(chuàng)建表單實(shí)例,并使用請(qǐng)求中的數(shù)據(jù)填充該表單實(shí)例:這稱為“將數(shù)據(jù)綁定到表單”(現(xiàn)在是綁定表單)。form = NameForm(request.POST)
我們稱為表單的is_valid()方法;如果不是True,我們返回帶有表單的模板。這次,表單不再是空的(未綁定),因此將使用先前提交的數(shù)據(jù)填充HTML表單,并可以在其中根據(jù)需要對(duì)其進(jìn)行編輯和更正。
如果is_valid()為True,我們現(xiàn)在將能夠在其cleaned_data屬性中找到所有經(jīng)過驗(yàn)證的表單數(shù)據(jù)。我們可以使用此數(shù)據(jù)來更新數(shù)據(jù)庫或進(jìn)行其他處理,然后再將HTTP重定向發(fā)送到瀏覽器,告訴瀏覽器下一步該怎么做。
我們不需要在name.html模板中做很多事情:
<form action="/your-name/" method="post"> {% csrf_token %} {{ form }} <input type="submit" value="Submit"> </form>
表單的所有字段及其屬性將通過Django的模板語言從中解壓縮為HTML標(biāo)記。{{ form }}
表格和跨站點(diǎn)請(qǐng)求偽造保護(hù)
Django隨附了易于使用的跨站點(diǎn)請(qǐng)求偽造保護(hù)。在POST啟用CSRF保護(hù)的情況下提交表單時(shí),必須csrf_token像前面的示例一樣使用template標(biāo)記。但是,由于CSRF保護(hù)并不直接與模板中的表單相關(guān)聯(lián),因此在本文檔的以下示例中省略了此標(biāo)簽。
HTML5輸入類型和瀏覽器驗(yàn)證
如果表單包括URLField,一個(gè) EmailField或任何整數(shù)字段類型,Django會(huì)使用的url,email和numberHTML5輸入類型。默認(rèn)情況下,瀏覽器可以在這些字段上應(yīng)用自己的驗(yàn)證,這可能比Django的驗(yàn)證更嚴(yán)格。如果您想禁用此行為,請(qǐng)novalidate在form標(biāo)簽上設(shè)置屬性,或在字段上指定其他小部件,例如TextInput。
現(xiàn)在,我們有了一個(gè)工作的Web表單,該表單由Django描述Form,由視圖處理并呈現(xiàn)為HTML <form>。
這就是您入門所需的全部內(nèi)容,但是表單框架為您提供了更多便利。一旦了解了上述過程的基礎(chǔ),就應(yīng)該準(zhǔn)備了解表單系統(tǒng)的其他功能,并準(zhǔn)備進(jìn)一步了解基礎(chǔ)機(jī)械。
所有表單類均作為django.forms.Form 或的子類創(chuàng)建django.forms.ModelForm。您可以將其ModelForm視為的子類Form。Form并ModelForm實(shí)際上從(私有)BaseForm類繼承通用功能,但是這種實(shí)現(xiàn)細(xì)節(jié)很少很重要。
模型和形式
實(shí)際上,如果您的表單將用于直接添加或編輯Django模型,那么ModelForm可以節(jié)省大量的時(shí)間,精力和代碼,因?yàn)樗梢詷?gòu)建表單以及適當(dāng)?shù)淖侄渭捌鋵傩裕瑏碜砸籑odel堂課。
綁定形式和未綁定形式之間的區(qū)別很重要:
表單的is_bound屬性將告訴您表單是否綁定了數(shù)據(jù)。
考慮一個(gè)比上面的最小示例更有用的形式,我們可以使用該形式在個(gè)人網(wǎng)站上實(shí)現(xiàn)“與我聯(lián)系”功能:
的forms.pyfrom 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)
我們之前的形式使用的單場(chǎng),your_name,一CharField。在這種情況下,我們的表單有四個(gè)字段:subject,message,sender和 cc_myself。CharField,EmailField并且 BooleanField只有三個(gè)可用的字段類型; 完整列表可以在“ 表單”字段中找到。
每個(gè)表單字段都有一個(gè)對(duì)應(yīng)的Widget類,該類又對(duì)應(yīng)于HTML表單小部件,例如。<input type="text">
在大多數(shù)情況下,該字段將具有明智的默認(rèn)小部件。例如,默認(rèn)情況下,a CharField將具有在HTML TextInput中生成a的小部件。如果需要 ,可以在定義表單字段時(shí)指定適當(dāng)?shù)男〔考?,就像我們?duì)字段所做的那樣。<input type="text"><textarea>message
無論通過表單提交的數(shù)據(jù)是什么,一旦通過調(diào)用成功驗(yàn)證is_valid()(并is_valid()返回True),經(jīng)過驗(yàn)證的表單數(shù)據(jù)都將位于form.cleaned_data字典中。這些數(shù)據(jù)將為您很好地轉(zhuǎn)換為Python類型。
注意
此時(shí),您仍然可以直接訪問未經(jīng)驗(yàn)證的數(shù)據(jù)request.POST,但是經(jīng)過驗(yàn)證的數(shù)據(jù)更好。
在上面的聯(lián)系表單示例中,cc_myself將為布爾值。同樣,諸如IntegerField和FloatField將值分別轉(zhuǎn)換為Python int和的字段float。
以下是在處理此表單的視圖中如何處理表單數(shù)據(jù)的方法:
的views.pyfrom 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/')
小費(fèi)
有關(guān)從Django發(fā)送電子郵件的更多信息,請(qǐng)參見發(fā)送電子郵件。
一些字段類型需要一些額外的處理。例如,使用表單上傳的文件需要進(jìn)行不同的處理(可以從而request.FILES不是從中檢索它們 request.POST)。有關(guān)如何處理表單上載文件的詳細(xì)信息,請(qǐng)參閱將上載的文件綁定到表單。
將表單放入模板所需要做的就是將表單實(shí)例放入模板上下文中。因此,如果您的表單是form在上下文中調(diào)用的,則將適當(dāng)?shù)爻尸F(xiàn)其和元素。{{ form }}<label><input>
附加表格模板家具
不要忘了,一個(gè)形式的輸出并沒有包括周圍的 <form>標(biāo)簽,或窗體的submit控制。您必須自己提供這些。
<label>/ <input>對(duì)還有其他輸出選項(xiàng):
請(qǐng)注意,您必須自己提供周圍環(huán)境<table>或<ul> 元素。
這是我們的實(shí)例的輸出:{{ form.as_p }}ContactForm
<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>
請(qǐng)注意,每個(gè)表單字段的ID屬性設(shè)置為id_<field-name>,由隨附的標(biāo)簽標(biāo)記引用。這對(duì)于確保輔助技術(shù)(例如屏幕閱讀器軟件)可訪問表單很重要。您還可以自定義標(biāo)簽和ID的生成方式。
有關(guān)更多信息,請(qǐng)參見將表單輸出為HTML。
我們不必讓Django解壓縮表單字段;我們可以根據(jù)需要手動(dòng)進(jìn)行操作(例如,允許我們對(duì)字段進(jìn)行重新排序)。每個(gè)字段都可以使用用作表單的屬性,并且在Django模板中將適當(dāng)?shù)爻尸F(xiàn)。例如:{{ form.name_of_field }}
{{ 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)然,這種靈活性的代價(jià)是更多的工作。到目前為止,我們不必?fù)?dān)心如何顯示表單錯(cuò)誤,因?yàn)檫@已經(jīng)為我們解決了。在此示例中,我們必須確保處理每個(gè)字段的所有錯(cuò)誤以及整個(gè)表單的所有錯(cuò)誤。請(qǐng)注意在表單和模板查找的頂部,以查找每個(gè)字段上的錯(cuò)誤。{{ form.non_field_errors }}
使用顯示格式錯(cuò)誤列表,并顯示為無序列表。可能看起來像:{{ form.name_of_field.errors }}
<ul class="errorlist"> <li>Sender is required.</li> </ul>
該列表的CSS類errorlist允許您設(shè)置外觀樣式。如果您希望進(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 %}
非字段錯(cuò)誤(和/或使用諸如的輔助工具時(shí)在表單頂部顯示的隱藏字段錯(cuò)誤form.as_p())將通過附加的類別呈現(xiàn),nonfield以幫助將其與特定于字段的錯(cuò)誤區(qū)分開。例如,如下所示:{{ form.non_field_errors }}
<ul class="errorlist nonfield"> <li>Generic validation error</li> </ul>
有關(guān)錯(cuò)誤,樣式以及如何在模板中使用表單屬性的更多信息,請(qǐng)參見Forms API。
如果您對(duì)每個(gè)表單字段使用相同的HTML,則可以通過使用 循環(huán)依次遍歷每個(gè)字段來減少重復(fù)代碼:{% for %}
{% 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 %}
有用的屬性包括:{{ field }}
{{ field.label }}
Email address
{{ field.label_tag }}
字段的標(biāo)簽包裝在適當(dāng)?shù)腍TML <label>
標(biāo)記中。這包括表格的label_suffix
。例如,默認(rèn)label_suffix
值為冒號(hào):
<label for="id_email">Email address:</label>
{{ field.id_for_label }}
id_email
在上面的示例中)。如果您是手動(dòng)構(gòu)建標(biāo)簽,則可能要使用它代替label_tag
。例如,如果您有一些內(nèi)聯(lián)JavaScript并希望避免對(duì)字段ID進(jìn)行硬編碼,它也很有用。{{ field.value }}
someone@example.com
。{{ field.html_name }}
{{ field.help_text }}
{{ field.errors }}
<ul class="errorlist">
{% for error in field.errors %}
{{ field.is_hidden }}
True
表單字段是否為隱藏字段,False
否則為隱藏字段 。它作為模板變量不是特別有用,但在條件測(cè)試中可能有用,例如:{% if field.is_hidden %} {# Do something special #} {% endif %}
{{ field.field }}
Field
的表單類中的實(shí)例BoundField
。您可以使用它來訪問
Field
屬性,例如 。{{ char_field.field.max_length }}
也可以看看
有關(guān)屬性和方法的完整列表,請(qǐng)參見 BoundField。
如果您要手動(dòng)在模板中布置表單,而不是依賴Django的默認(rèn)表單布局,則可能需要將 字段與非隱藏字段區(qū)別對(duì)待。例如,由于隱藏字段不顯示任何內(nèi)容,因此將錯(cuò)誤消息放在該字段旁邊可能會(huì)給您的用戶造成混亂-因此,應(yīng)對(duì)這些字段的錯(cuò)誤進(jìn)行不同的處理。<input type="hidden">
Django在表單上提供了兩種方法,可讓您獨(dú)立遍歷隱藏字段和可見字段:hidden_fields()和 visible_fields()。這是對(duì)使用這兩種方法的先前示例的修改:
{# 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 %}
本示例不處理隱藏字段中的任何錯(cuò)誤。通常,隱藏字段中的錯(cuò)誤是表單被篡改的標(biāo)志,因?yàn)檎5谋韱谓换ゲ粫?huì)改變它們。但是,您也可以輕松地為這些表單錯(cuò)誤插入一些錯(cuò)誤顯示。
如果您的站點(diǎn)在多個(gè)位置對(duì)表單使用相同的呈現(xiàn)邏輯,則可以通過將表單的循環(huán)保存在獨(dú)立模板中并使用include標(biāo)簽在其他模板中重用它來減少重復(fù):
# 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ì)象在上下文中具有不同的名稱,則可以使用 標(biāo)記的with參數(shù)對(duì)其進(jìn)行別名include:
{% include "form_snippet.html" with form=comment_form %}
如果您發(fā)現(xiàn)自己經(jīng)常這樣做,則可以考慮創(chuàng)建一個(gè)自定義 包含標(biāo)簽。
詳情參考: https://docs.djangoproject.com/en/3.0/topics/forms/#working-with-form-templates
更多建議: