九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
practical django project 第九章 代碼共享應(yīng)用中的表單處理

第九章 代碼共享應(yīng)用中的表單處理

目前為止你所有的Django應(yīng)用——除了weblog的評論系統(tǒng)——全部集中在站點(diǎn)的可信任用戶通過Django的管理界面輸入內(nèi)容,而沒有允許普通用戶提交內(nèi)容的機(jī)制。因此,在這個應(yīng)用中你將需要一個方式允許用戶提交他們自己的代碼片段。你還需要確保他們提交的內(nèi)容是符合你在模型中設(shè)定的數(shù)據(jù)格式的。

幸運(yùn)的是,Django通過使用一種簡單而強(qiáng)大的顯示和處理基于web表單的系統(tǒng)使這項(xiàng)任務(wù)變得異常簡單。在本章中,你將徹底的了解Django表單處理系統(tǒng)并使用它來創(chuàng)建一個可使用戶提交和編輯它們代碼的樣例。

9.1一個簡要的Django表單系統(tǒng)的介紹

Django表單處理代碼,位于django.forms模塊中,提供了三個關(guān)鍵的組件,涵蓋了構(gòu)建,顯示,處理表單的各個方面:

  • 一個字段類集合,類似于Django數(shù)據(jù)模型中的字段類型,表示了特定的數(shù)據(jù)類型而且知道該如何驗(yàn)證它們。
  • 一個組件(widget)類集合,知道該如何生成不同類型的HTML控制表單(文本輸入框,選擇框等等),并從一個HTTP表單提交請求中讀取相應(yīng)的數(shù)據(jù)。
  • 一個Form類將這些概念整合到一起并提供了統(tǒng)一接口來定義數(shù)據(jù)及校驗(yàn)數(shù)據(jù)的高級規(guī)則

9.1.1 一個簡單的例子

為了來感受下它是如何工作的,我們來看一個簡單但是常用的需求:用戶注冊。

#Admonition#這些代碼到哪去了?

這段特定的代碼邏輯上是不屬于你正在開發(fā)的cab應(yīng)用的,如果你已經(jīng)開發(fā)了處理用戶注冊的代碼,那么最好是將這些代碼放置在獨(dú)立的應(yīng)用中。不過目前不用擔(dān)心如何在python文件中保存這些代碼的問題。這只是一個用于盡可能多的展示django表單處理系統(tǒng)的有用的例子。如果你確實(shí)有需要實(shí)現(xiàn)一個用戶注冊系統(tǒng),當(dāng)然,你可以自由的引用這段代碼并調(diào)整到合適于你需要的樣子。

基本的注冊表單由三部分?jǐn)?shù)據(jù)組成:

  • 一個用戶名
  • 一個與新賬戶關(guān)聯(lián)的e-mail地址
  • 一個用于登陸的密碼

另外,你還要做一點(diǎn)自定義的校驗(yàn)工作:

  • 需要保證不存在兩個相同的用戶名
  • 顯示兩個密碼字段讓用戶輸入同一個密碼兩次一直是個好主意。這會檢查出輸入時的錯誤并提供一點(diǎn)額外的安全措施,確保用戶輸入的的確是它們想要的密碼。

從邏輯上說,這會產(chǎn)生一個有四個表單域的<element>元素:用戶名和e-mail各一個,另外兩個處理重復(fù)密碼。下面就是構(gòu)建這個表單開始時的樣子:

1 from django import forms2 3 class SignupForm(forms.Form):4     username = forms.CharField(max_length = 30)5     email = forms.EmailField()6     password1 =forms.CharField(max_length = 30)7     password2 = forms.CharField(max_length = 30)

除了使用django.froms代替django.db.models,以上代碼看起來和定義數(shù)據(jù)模型時十分類似:簡單的子類化適當(dāng)?shù)幕惒⒓尤牒线m的字段。

但是這些代碼并不完善。HTML提供了一個特殊的表單輸入類型來處理密碼——<input type="password">——這是生成密碼域的適當(dāng)?shù)姆绞健D憧梢暂p微改寫這兩個字段來實(shí)現(xiàn)這個輸入類型:

 password1 = forms.CharField(max_length=30,widget=forms.PasswordInput()) password2 = forms.CharField(max_lengt=30,widget = forms.PasswordInput())

PasswordInput組件將會產(chǎn)生<input type="password">。這同樣也顯示出Django表單系統(tǒng)有分離的數(shù)據(jù)校驗(yàn)機(jī)制,一個是在字段中處理的,一個是在組件中處理的。最常見的情況是你有一個單獨(dú)的基本校驗(yàn)規(guī)則需要使用與多個不同的字段,并且是來源于不同類型的HTML輸入。這種分離校驗(yàn)機(jī)制就使事情變簡單了:你可以重用一個字段類型而只需要修改包含的組件。
現(xiàn)在,再做一些修改:

password1 = forms.CharField(max_length=30,widget =forms.PasswordInput(render_value=False))password2 = forms.CharField(max_lengt=30,widget = forms.PasswordInput(render_value=False))

參數(shù)render_value告訴PasswordInput盡管有一些數(shù)據(jù),但是不會顯示出來。當(dāng)輸入密碼時產(chǎn)生了錯誤原先的數(shù)據(jù)會被完全清除使下次用戶能正確輸入。

9.1.2 校驗(yàn)用戶名

到目前為止你所設(shè)置的字段都包含有隱式的校驗(yàn)規(guī)則。username字段及兩個密碼字段都有最大長度設(shè)置,Email字段會確認(rèn)輸入的是正常的e-mail格式的地址(通過正則表達(dá)式)。但還是需要保證用戶名不是已經(jīng)存在的,因此需要自定義校驗(yàn)規(guī)則。

你可以在form中定義一個名為你call_username()的方法。在校驗(yàn)過程中,Django表單系統(tǒng)會自動查找任何以call_開始,以字段名結(jié)束的方法,然后在字段內(nèi)建的校驗(yàn)規(guī)則完成后調(diào)用。

下面是clean_username()方法看起來的樣子(假設(shè)Django User模型已經(jīng)被導(dǎo)入了):

User.objects.get(username=self.cleaned_date['username'])except User.DoesNotExist:return self.cleaned_data['username']rasise forms.ValidationError("Thisusernameis already in use.Please choose another.")

這段代碼短小精悍。首先,這個方法只會在username字段少于內(nèi)建規(guī)則的30個字符時才會被調(diào)用。在這種情況下,username字段提交的值在self.cleaned_data['username']中。這個cleaned_data屬性是一個所有被提交數(shù)據(jù)中那些已經(jīng)通過校驗(yàn)后的數(shù)據(jù)值組成的字典。

查詢是否存在一個用戶與username字段提交的值精確匹配。如果沒有這樣一個用戶則Django會拋出一個UserNotExist異常。這個異常告訴你這個用戶不存在,因此這個username字段的值即通過了校驗(yàn)。在這種情況下,只簡單返回這個值。

9.1.3 校驗(yàn)密碼

校驗(yàn)密碼是一個需要點(diǎn)技巧的,因?yàn)樾枰瑫r查看兩個字段并確保兩項(xiàng)匹配。你可以為一個字段定義一個方法,如下:

def clean_password2(self):    if self.cleaned_data['password1'] != self.cleaned_data['password2']:                raise forms.ValidationError("You must type the same password each time")    return self.cleaned_data['password2']

但是還有一個更好的方式來實(shí)現(xiàn)這個校驗(yàn)。Django允許你定義一個校驗(yàn)方法——簡單的名為clean()——可以應(yīng)用于整個表單。下面是寫法:

def clean(self):    if 'password1' in self.clean_data and 'password2' in self.cleaned_data:        if self.clean_data['password1'] != self.cleaned_data['password2']:        raise forms.ValidationError("You must type the same password each time")    return self.cleaned_data

注意在這種情況下,你手動的檢查了兩個password字段的值是否存在于clean_data中。如果有任何錯誤在單獨(dú)的字段校驗(yàn)中被拋出,cleand_data就會變成空的。因此你需要在引用任何你需要的值之前檢查它。

#Admonition# 默認(rèn)的表單字段都是必須的

所有內(nèi)建于Django表單系統(tǒng)的字段類型都是必須的,因此不能被留空。如果密碼字段中的一個被留空,Django會在調(diào)用clean()方法前產(chǎn)生一個ValidationError的異常,因此你不需要為必需的值生成額外的錯誤。為了將一個字段標(biāo)記為可選,可以向其傳遞一個關(guān)鍵字參數(shù)required=False。

9.1.4 創(chuàng)建新用戶

現(xiàn)在,停下寫form代碼的手轉(zhuǎn)到處理表單的視圖上來。你可以創(chuàng)建視圖使得可以創(chuàng)建和保存新的用戶對象。但是如果一旦你需要在別的視圖中重用這個form,就需要一遍遍的重寫這樣的代碼。所以一個更好的辦法是在form中編寫一個方法如何處理校驗(yàn)后的數(shù)據(jù)。因?yàn)檫@個方法是保存一個新的User對象到數(shù)據(jù)庫中,所以我們稱其為save().

#Admonition# save()方法不只是針對數(shù)據(jù)庫的

大多數(shù)時候,表單用于創(chuàng)建和更新模型對象,在這些情況下save()方法是最自然的選擇。但是表單可能被用于其他目的(例如,一個聯(lián)系表單可能會發(fā)送一封電子郵件而不是保存一個對象)。在Django社區(qū)中一般的約定是:任何時候表單類都有一個“知道”該如何處理處理校驗(yàn)后數(shù)據(jù)的方法,這個方法應(yīng)該被叫做save(),即便它有時候可能不保存任何數(shù)據(jù)到你的數(shù)據(jù)庫中去。給這類方法一個固定和易識別的名字的好處遠(yuǎn)大于可能導(dǎo)致的初始化混淆。

在save()中,你需要從form提交的用戶名,e-mail和密碼中生成一個User對象。假設(shè)你已經(jīng)導(dǎo)入了User模型,下面可以這樣編寫:

def save(self):    new_user = User.objects.create_user(username=slef.cleaned_data['username'], email=self.cleaned_data['email'],password=self.cleaned_data['password1'])    return new_user

#Admoniton# 用戶名及密碼

儲存在數(shù)據(jù)庫中的用戶名和密碼有一個很大的問題,即任何能訪問數(shù)據(jù)庫的人都可能看到全部密碼。由于很多人都喜歡在不同的站點(diǎn)上使用相同的密碼,這就會導(dǎo)致恨嚴(yán)重的安全風(fēng)險。為了幫助保護(hù)你的用戶,Django阻止在數(shù)據(jù)庫中儲存用戶實(shí)際用于登錄的純文本的密碼。Django使用一套稱為哈希函數(shù)(hash function)的數(shù)學(xué)技巧,將密碼轉(zhuǎn)換為一組隨機(jī)的(實(shí)際上不是正真隨機(jī)的)字符串和數(shù)字的組合。儲存在數(shù)據(jù)庫中的其實(shí)是這組隨機(jī)結(jié)果。這樣做的好處是,哈希函數(shù)只能單向工作:如果知道了密碼可以使用哈希函數(shù)并總是得到同樣的結(jié)果,但是如果只是知道結(jié)果,就不能反向的得到實(shí)際密碼。這就提供了一種合理的儲存密碼的安全方法。當(dāng)你嘗試登錄的時候,Django的認(rèn)證系統(tǒng)對輸入的密碼運(yùn)用哈希函數(shù)并比較其結(jié)果與數(shù)據(jù)庫中儲存的結(jié)果。這就表示純文本的密碼不必永久的保存在任何地方。但是由于這個系統(tǒng)的工作需要一定的技巧,Django的User模塊有一個自定的管理器定義了你在這里使用的create_user()方法。這個方法負(fù)責(zé)處理對密碼使用哈希函數(shù)并儲存正確的結(jié)果。

下面是最終完成后的form:

 1 from django.contrib.auth.models import User 2         from django import forms 3  4         class SignupForm(forms.Form): 5             username = forms.CharField(max_length=30) 6             email = forms.EmailField() 7             password1 = forms.CharField(max_length=30,widget = forms.PasswordInput(render_value=False)) 8             password2 = forms.CharField(max_length=30,widget = forms.PasswordInput(render_value=False)) 9             10             def clean_username(self):11                 try:12                     User.objects.get(username=self.cleaned_data['username'])13                 except User.DoesNotExist:14                     return self.cleaned__data['username']15                 raise forms.ValidationError("This username is already in use. Please choose another.")16             17             def clean(self):18             if 'password1' in self.clean_data and 'password2' in self.cleaned_data:19                 if self.clean_data['password1'] != self.cleaned_data['password2']:20                     raise forms.ValidationError("You must type the same password each time")21             return self.cleaned_data22             23             def save(self):24             new_user = User.objects.create_user(username =slef.cleaned_data['username'],25                                                                    email=self.cleaned_data['email'],26                                                                    password=self.cleaned_data['password1'])27             return new_user

9.1.5 表單驗(yàn)證的工作方式

在視圖中用來確定是否有數(shù)據(jù)通過校驗(yàn)的方法名為is_valid(),被定義與Form基類,所有Django表單都源于該基類。在Form類中,is_valid( ) touches off表單的驗(yàn)證路徑,以一個特定順序,通過一個叫做full_clean( )的方法(django.form中的Form基類中定義的其他方法見圖9-1)。

校驗(yàn) 的順序如下:

  • 首先,full_clean()在每個表單域上循環(huán)。每一個字段類都有一個clean()方法,實(shí)現(xiàn)了字段的內(nèi)建校驗(yàn)規(guī)則,每一個這樣的方法會拋出ValidationError異?;蚍祷匾粋€值。如果一個ValidationErro方法被拋出,之后對這個字段的校驗(yàn)就不會繼續(xù)(因?yàn)橐呀?jīng)知道這個字段中的數(shù)據(jù)不可用)。如果返回的是一個值,將會被加入到表單的cleaned_data字典。
  • 如果一個字段 的內(nèi)建clean( )方法沒有拋出ValidationError異常,那么任何自定義的校驗(yàn)方法就會被執(zhí)行——這些自定義的校驗(yàn)方法以clean_開始以字段名結(jié)束。這些方法同樣不是拋出異常就是返回一個值,如果返回了一個值,則被加入clean_data。
  • 最后,表單的clean()方法被調(diào)用,它同樣也會產(chǎn)生ValidationError異常,雖然它不與任何具體的字段相關(guān)聯(lián)。如果clean()方法沒有產(chǎn)生新的錯誤,它將返回一個完整的包含表單數(shù)據(jù)的字典,通常是這樣:return self.cleaned_data。
  • 如果沒有校驗(yàn)錯誤產(chǎn)生,表單的cleaned_data字典將會被校驗(yàn)后的數(shù)據(jù)完全填充。如果存在校驗(yàn)錯誤,cleaned_data就不會存在,而一個含有錯誤的字典(self.errors)將會被校驗(yàn)錯誤填充。每個字段都知道該如何從字典中提取它自己的錯誤,這就是為什么你可以在模板中使用{{ form.username.errors }}的原因。
  • 最后,is_valid()當(dāng)存在校驗(yàn)錯誤的時候返回False,沒有錯誤的時候返回True。

理解這個過程是充分發(fā)揮Django表單處理系統(tǒng)的作用的關(guān)鍵。可能初看起來有些復(fù)雜,但是在不同地方為表單附加校驗(yàn)規(guī)則的能力產(chǎn)生了巨大的靈活性而且能更容易的寫出可重用的代碼。舉個例子,如果你需要不斷的使用一個特定類型的校驗(yàn)多次,你會注意到給每一個表單都寫自定義方法十分繁瑣。最好是寫一個自定義字段類,定義一個clean()方法,然后重用這個字段。

類似的,區(qū)分開特定字段校驗(yàn)方法和“表單級”clean( )方法顯示出大量驗(yàn)證多個字段組合的技巧。當(dāng)使用單獨(dú)一個字段時不一定需要這些技巧。

9.1.6 處理表單

現(xiàn)在,來看看用于顯示和處理表單的視圖:

from django.http import HttpResponseRedirect            from django.shortcuts import render_to_response            def signup(request):                if request.method =='POST':                    form = SignupForm(data = request.POST)                    if form.is_valid():                        new_user = form.save()                        return HttpResponseRedirect("/account/login/")                else:                    form = SignupForm()                return render_to_response('signup.html',{'form':form})

我們一步步的分解來看:

  • 首先看看這個方法的HTTP請求。通常是GET或者POST.(還有一些不常用的HTTP方法,典型的瀏覽器只支持GET和POST用于表單提交。)
  • 當(dāng)且僅當(dāng),request請求是POST 的時候,你實(shí)例化一個SignupForm并傳遞一個request.POST作為form數(shù)據(jù)。回顧第三章中,當(dāng)寫一個簡單的search函數(shù)的時候,已經(jīng)見到過隨GET請求發(fā)送的request.GET是一個字典;類似的,request.POST也是一個數(shù)據(jù)字典(在本例中,是提交的表單數(shù)據(jù))通過POST請求發(fā)送。
  • 調(diào)用表單的is_valid()檢查是否提交的數(shù)據(jù)通過了校驗(yàn)。在這個結(jié)果之下是匹配表單中字段和提交的數(shù)據(jù)并檢查每個字段的校驗(yàn)規(guī)則。如果數(shù)據(jù)通過校驗(yàn),is_valid()將會返回True并且表單的cleaned_data字典將被正確的值填充。否則,is_valid()將會返回False,并且cleaned_字典不會存在。
  • 如果數(shù)據(jù)通過校驗(yàn),則調(diào)用之前定意的save()方法。然后返回一個HTTP重定向——使用django.http.HttpResponseRedirect——到一個新頁面,應(yīng)該是一個新用戶登錄的視圖。任何時候你通過HTTP POST接收了數(shù)據(jù),在處理成功后應(yīng)該總是重定向。通過將用戶導(dǎo)入一個新頁面,避免了一個常見的陷阱,即刷新頁面或點(diǎn)擊返回按鈕時意外的重新提交表單。
  • 如果request方法不是POST,那么實(shí)例化一個SignupForm不帶有任何數(shù)據(jù)。技術(shù)上講,這叫做非綁定的表單(沒有任何可以使用的數(shù)據(jù)),和綁定表單相反,沒有任何可提交的數(shù)據(jù)。
  • 渲染一個模板,將表單作為一個變量傳遞給模板,然后返回一個響應(yīng)。注意因?yàn)橐晥D編寫的方式,如果用戶提交了驗(yàn)證后的數(shù)據(jù)你永遠(yuǎn)不會執(zhí)行到這一步。在這種情況下,if語句會保證返回一個重定向。同樣,注意這一步也會忽略掉是否存在未通過校驗(yàn)的數(shù)據(jù)或者根本沒有數(shù)據(jù)——SignupForm對象不會根據(jù)情況的不同有不同的處理方式。

最后,來看看使用這個視圖如何在signup.html模板中顯示這個表單:

 1 <html> 2               <head> 3                 <title>Sign up for an account</title> 4               </head> 5               <body> 6                 <h1>Sign up for an account</h1> 7                 <p>Use the form below to register for your new account; all 8                    fields are required.</p> 9                 <form method="post" action="">10                   {% if form.non_field_errors %}11                   <p><span class="error">12                   {{ form.non_field_errors|join:", " }}13                   </span></p>14                   {% endif %}15                   <p>{% if form.username.errors %}16                   <span class="error">{{ form.username.errors|join:", " }}</span>17                   {% endif %}</p>18                   <p><label for="id_username">Username:</label>19                      {{ form.username }}</p>20                   <p>{% if form.email.errors %}21                   <span class="error">22                   {{ form.email.errors|join:", " }}23                   </span>24                   {% endif %}</p>25                   <p><label for="id_name">Your e-mail address:</label>26                      {{ form.email }}</p>27                   <p>{% if form.password1.errors %}28                   <span class="error">29                   {{ form.passsword1.errors|join:", " }}30                   </span>31                   {% endif %}</p>32                   <p><label for="id_password1">Password:</label>33                      {{ form.password1 }}</p>34                   <p>{% if form.password2.errors %}35                   <span class="error">36                   {{ form.passsword2.errors|join:", " }}37                   </span>38                   {% endif %}</p>39                   <p><label for="id_password2">Password (again, to catch40                       typos): </label>41                      {{ form.password2 }}</p>42                   <p><input type="submit" value="Submit"></p>43                 </form>44               </body>45  </html>

這里的HTML語句大多都很簡單:一個標(biāo)準(zhǔn)的<form>標(biāo)簽,每個字段帶有一個<label>標(biāo)簽以及一個提交按鈕。但是注意最終是如何實(shí)際顯示字段的。每一個都作為{{ form }}變量的屬性被訪問。你可以檢查每一個是否有錯誤,并且顯示錯誤信息(將會是一種列表形式,即使只有一條信息,因此使用join過濾器,用一個特定字符將列表中的項(xiàng)組合起來。) 

注意,雖然在表單的開始使用了{(lán){ form.non_field_errors }}。這是因?yàn)閏lean()方法拋出的異常不屬于任何一個字段(因?yàn)槭莵碜杂诒容^兩個字段)。無論什么時候有一個潛在的校驗(yàn)錯誤來自clean( )方法,你需要檢查non_field_errors并在存在的時候顯示它。

9.2 編寫表單來添加代碼片段

現(xiàn)在,用戶注冊的例子已經(jīng)給你關(guān)于如何編寫一個接受提交數(shù)據(jù)信息的表單的很好的啟發(fā),你可以編寫一個來添加Snippte模型實(shí)例。你可以簡單的設(shè)置想要用戶填寫的字段信息,然后給它一個save()方法,用于創(chuàng)建和保存新的代碼段。

但是現(xiàn)在這里有一個新的需要處理的問題。Snippet模型中的author字段必須填寫,并且是被正確的填寫,但是你不希望將其顯示給用戶并讓他們來選擇這個值。如果你這樣做了,那么任何人都可以通過填寫一個其他人的名字來假裝成是別的用戶。所以你需要某種方法來填寫這個字段而又不使它成為表單中公開的部分。

幸運(yùn)的是,這很容易辦到:一個表單就是一個Python類。因此你可以添加你自己的__init__()方法并信任處理這個表單的視圖函數(shù)將會傳入確定的正確的、經(jīng)過認(rèn)證的用戶,你可以儲存并在需要保存snippet的時候引用。那么,我們來開始編寫AddSnippetForm。

在cab目錄下創(chuàng)建一個forms.py文件。在其中開始編寫如下代碼:

from django import formsfrom cab.models import Snippet                class AddSnippetForm(forms.Form):                    def __init__(self,author,*args,**kwargs):                        super(AddSnippteForm,self).__init__(*args,**kwargs):                        self.author = author

 除了接受一個額外的參數(shù)——author以便之后使用——在這里還做了兩件重要的事:

  • 對額外的author參數(shù),你設(shè)定了一個接收*args和**kwargs的__init__()方法。這是Python當(dāng)中用來接收任意組合的可選參數(shù)和關(guān)鍵字參數(shù)的一種快捷方法。
  • 使用supre()調(diào)用父類的__init__()方法,傳遞了自定義__init__()方法接受到的其它參數(shù)。這保證了Form基類中的__init__()方法被調(diào)用,并將你表單中的其它內(nèi)容設(shè)置妥當(dāng)。

使用這一技術(shù)——接受*args和**kwargs并傳遞給父類方法——是使復(fù)寫的方法接收大量參數(shù),特別是大量的可選參數(shù)時的一種有用的快捷方法。Form基類的__init__()方法實(shí)際上接收了七個全部是可選的參數(shù),這是一個方便的小技巧。
現(xiàn)在可以添加所需要的字段:

title = forms.CharField(max_length=255)                description = forms.CharField(widget=forms.Textarea)                code = forms.CharField(widget=forms.Textarea)                tags = forms.CharField(max_length=255)

再次提醒,你可以借助修改字段所使用的組建來改變它的表現(xiàn)形式。Django的模型系統(tǒng)使用了兩個不同的字段——CharField和TextField——來表現(xiàn)不同大小的基于文本的字段(必須如此,因?yàn)樗鼈冊诘讓訑?shù)據(jù)庫列上是工作于不同的數(shù)據(jù)類型的),表單系統(tǒng)則只有一個CharField。為了在最終的HTML文件中將其轉(zhuǎn)換為<textarea>,你需要簡單的將其組建修改為Textarea,以同樣的方式,你在用戶注冊表單的例子中使用了PasswordInput組建。

這就解決了除了語言之外的其他字段,這個需要突然看起來需要那么一點(diǎn)點(diǎn)技巧。你像要做的是顯示一個下拉列表(一個HTML元素<select>)其中含有可用語言及校驗(yàn)規(guī)則,使用戶可以從中選擇。但是到目前為止你見過的字段類型沒有適合處理這個問題的,所以需要求助于一些新的東西。

一個可以處理這個問題的字段類型叫做ChoiceField。它需要一個選項(xiàng)列表(和模型字段中接受的選項(xiàng)是同樣的格式——例如,你在weblog的Entry模型中的status字段已經(jīng)見過)并確保提交的值是其中之一。但是將其設(shè)置妥當(dāng)以使每次使用時表單查詢語言集合(以防管理員像系統(tǒng)中添加了新的語言)這就需要在__init__()方法中做些hacking。像這樣表現(xiàn)一個模型中的關(guān)聯(lián)是一項(xiàng)極為常見的情況,因此你會希望有一種簡單的方法來處理它。

 

最終證明,Django還是提供了一個簡單的解決:一個叫做ModelChoiceField的特殊字段。一個普通的ChoiceField字段只需要一個簡單的選項(xiàng)列表,一個ModelChoiceField字段需要一個Django QuerySet并從查詢的結(jié)果(每次都執(zhí)行最新的)中動態(tài)的生成它的選項(xiàng)。為了使用它,需要在文件頭部導(dǎo)入模型的語句中加上Language 模型:

from cab.models import Snippet,Laabguage

然后簡單的編寫:

language = ModelChoiceField(queryset = Language.objetcs.all())

對這個表單來說,你不需要在字段本身提供的校驗(yàn)之外的任何特殊校驗(yàn),所以你可以直接編寫save()方法:

def save(self):    snippet = Snippet(title = self.cleaned_data['title'],                                  description = self.cleaned_data['description'],                                  code = self.cleand_data['code'],                                  tags = self.cleand_data['tags'],                                  author = self.author,                                  language = self.cleand_data['language'])    snippet.save()    return snippet

因?yàn)樵贒jango中在一個步驟中創(chuàng)建并保存一個對象是一種常規(guī)做法,實(shí)際上你可以用這種更為簡便的做法。Django提供的默認(rèn)管理器類包含了一個叫做create()的方法,可以創(chuàng)建,保存并返回一個新的對象。使用它,你的save()方法就簡化到了只需兩行:

1 def save(self):2     return Snippets.objects.create(title=self.cleaned_data['title'],description=self.cleaned_data['description'],code=self.cleaned_data['code'],tags=self.cleaned_data['tags'],author=self.author,language=self.cleaned_data['language']

現(xiàn)在,你的AddSnippetForm就完成了:

 1 from django import forms 2 from cab.models import Snippet, Language 3 class AddSnippetForm(forms.Form): 4     def __init__(self, author, *args, **kwargs): 5         super(AddSnippetForm, self).__init__(*args, **kwargs): 6         self.author = author 7         title = forms.CharField(max_length=255) 8         description = forms.CharField(widget=forms.Textarea()) 9         code = forms.CharField(widget=forms.Textarea())10         tags = forms.CharField(max_length=255)11         language = forms.ModelChoiceField(queryset=Language.objects.all())12         def save(self):13             return Snippet.objects.create(title=self.cleaned_data['title'],14                                       description=self.cleaned_data['description'],15                                       code=self.cleaned_data['code'],16                                       tags=self.cleaned_data['tags'],17                                                        author=self.author,18                                       language=self.cleaned_data['language'])

 9.2.1 編寫視圖來處理表單

現(xiàn)在你可以編寫一個簡短的名為add_snippet的視圖來處理后續(xù)任務(wù)。在cab/views目錄下,創(chuàng)建一個snippets.py文件,然后加入如下代碼:

from django.http import HttpResponseRedirectfrom django.shortcuts import render_to_responsefrom cab.forms import AddSnippetFormdef add_snippet(request):    if request.method== 'POST'        form = AddSnippetForm(author=request.user,data=request.POST)        if  form.is_valid():            new_snippet = form.save()        return HttpResponseRedirect(new_snippet.get_absolute_url())    else:        form = AddSnippetForm(author = request.user)        return render_to_response('cab/add_snippet.html',{'form':form})

這段代碼將實(shí)例化表單,校驗(yàn)數(shù)據(jù),保存新的Snippet,然后返回一個重定向到該段snippet的詳細(xì)視圖。(再次強(qiáng)調(diào),總是在成功的POST之后重定向。)

開始這個視圖函數(shù)看起來還不錯,但是這里有一個潛在的問題。你引用了request.user,即當(dāng)前登錄的用戶(Django在當(dāng)認(rèn)證系統(tǒng)被正確的激活后會自動的設(shè)置這個值)。但是如果填寫這個表單的人沒有登錄會發(fā)生什么呢?

答案是,你的數(shù)據(jù)不會被真正的校驗(yàn)。當(dāng)當(dāng)前用戶沒有登錄,request.user是一個表示匿名用戶的“假”對象,它無法被用做一個snippet的author字段的值。因此你需要以某種方式來確認(rèn)只有登錄的用戶才能填寫這個表單。

幸運(yùn)的是,Django提供了一個簡單的方式處理這個問題,使用一個認(rèn)證系統(tǒng)中的稱為login_required的裝飾器。

你可以簡單的導(dǎo)入并應(yīng)用于你的視圖函數(shù),沒有登錄的用戶將會被重定向到登錄頁面:

from django.http import HttpResponseRedirectfrom django.shortcuts import render_to_responsefrom django.contrib.auth.decorators import login_requiredfrom cab.forms import AddSnippetFromdef add_snippet(request):    if request.method == 'POST':        form = AddSnippetForm(author=request.user,data=request.POST)        if form.is_valid():            new_snippet = form.save()            return HttpResponseRedirect(new_snippet.get_absolute_url())    else:        form = AddSnippetForm(author = request.user)    return render_to_response('cab/add_snippet.html',                                            {'form':form})add_snippet = login_required(add_snippet)

#admonition:設(shè)置登錄/登出視圖

Django的認(rèn)證系統(tǒng),內(nèi)置于django.contrib.auth,包括了認(rèn)證并登入用戶的視圖函數(shù)及表單。只要是在你自己的機(jī)器上對一個應(yīng)用進(jìn)行測試,那么你可以通過django管理界面登錄,然后訪問任何你以login_required標(biāo)記的視圖。但是在公開部署時,你會需要為一般用戶設(shè)置公開界面的登入/登出視圖。

了解如何使用內(nèi)置的認(rèn)證視圖,參考Django認(rèn)證系統(tǒng)在線文檔http://docs.djangoproject.com/en/dev/topics/auth/.

9.2.2 編寫模版處理add_snippet視圖

現(xiàn)在,你可以編寫cab/add_snippet.html模版如下:

<html>    <head>    <title>Add a snippet</title>    </head>    <body>    <h1>Add a snippet</h1>    <p> Use the form below to submit your snippet;all fields are required.</p>    <form method ="post"  action="">        <p>{% if form.title.errors %}        <span class="error">        {{form.title.errors|join:", "}}        </span>      {% endif %}</p>
    <p><label for="id_title">Title:</label>
    {{ form.title }}</p>
    <p>{% if form.language.errors %}
    <span class="error">
    {{ form.language.errors|join:", " }}
    </span>
    {% endif %}</p>

9.3 從模型定義自動產(chǎn)生表單

盡管Django的表單系統(tǒng)使你能較為簡潔的方式編寫和使用這個表單,但是這還不是最理想的解決方案。設(shè)置一個表單來添加和編輯一個模型的實(shí)例是極為常見的事情,一遍又一遍的編寫這些程式化的表單會是件及其惱火的事情(特別是你已經(jīng)在定義模型類的時候?qū)⒍鄶?shù)甚至全部相關(guān)信息都設(shè)置了一遍的情況下)。

幸運(yùn)的是,有一個方法可以戲劇性的減少你編寫的代碼的數(shù)量。為提供不需要太多自定義行文的表單,Django提供了一個稱為ModelForm的類,可以從模型定義自動的產(chǎn)生一個適度自定義的表單,包含了所有相關(guān)字段及必要的save()方法。它的最基礎(chǔ)的工作方式如下:

from django.forms import ModelFormfrom cab.models import Snippetclass SnippetForm(ModelForm):    class Meta:        model = Snippet

子類化ModelForm并提供一個內(nèi)部Meta類指定一個模型將自動的從制定模型中提取字段來設(shè)置這個新的SnippetForm類。并且,ModelForm會自動的忽略在模型中定義為editable=False的字段,因此類似HTML版本的描述字段將不會在表單中顯示。這里唯一欠缺的東西其實(shí)是author這個字段將會顯示出來。幸運(yùn)的是,ModelForm支持一些自定義,包括一個要排除在表單之外的字段列表,因此你可以簡單的修改SnippetFrom的定義如下:

class SnippetForm(ModelForm):    class Meta:        model = Snippet        exclude = ['author']

這會將author字段排除在外?,F(xiàn)在,你可以簡單的刪除cab/forms.py并像這樣重寫cab/views/snippets.py:

 

from django.http import HttpResponseRedirectfrom django.forms import ModelFormfrom django.shortcuts import render_to_responsefrom django.contrib.auth.decoartors import  login_requiredfrom cab.models import Snippetclass SnippetForm(ModelForm):    class Meta:        model = Snippet        exclude = ['author']def add_snippet(request):    if request.method == 'POST':        form = SnippetForm(data=request.POST)        if form.is_valid():            new_snippet  = form.save()            return HttpResponseRedirect(new_snippet.get_absolute_url())    else:        form = SnippetForm()    return render_to_response('cab/add_snippet.html',                                            {'form':form})add_snippet = login_required(add_snippet)

當(dāng)然,這不是十分正確。Snippets需要填充一個作者(author),但是你已經(jīng)將這個字段排除出表單之外了。你可以回過頭去定義一個自定義的__init__()方法,并傳遞一個request.user,但是ModelForm有一個更富技巧的方式。你可以用ModelForm來創(chuàng)建一個Snippet對象并返回它,但并不保存;你通過向save()方法傳遞一個額外的參數(shù)——commit=False——來做到這一點(diǎn)。當(dāng)這樣做的時候,save()還是會返回一個新的Snippet對象,但是它不會被保存到數(shù)據(jù)庫中去。這就給了你自由的添加user,并手動的將這個新的Snippet對象插入到數(shù)據(jù)庫的機(jī)會:

from django.http import HttpResponseRedirectfrom django.forms import ModelFormfrom django.shortcuts import render_to_responsefrom django.contrib.auth.decorators import login_requiredfrom cab.models import  Snippetclass SnippetForm(ModelForm):    class Meta:        model = Snippet        exclude = ['author']def add_snippet(request):    if request.method == 'POST':        form = SnippetForm(data = request.POST)        if form.is_valid():            new_snippet = form.save(commit=False)            new_snippet.author = request.user            new_snippet.save()            return HttpResponseRedirect(new_snippet.get_absolute_url())    else:        form = SnippetForm()    return render_to_response('cab/add_snippet.html',{'form':form})add_snippet = login_required(add_snippet)

#admonition:commit=False 和多對多關(guān)系(many to many relationships)

如果使用的模型中有多對多字段(在表單中會以一個名為ModelMultipleChoiceField的字段來表示),你需要在在ModelForm中的save()方法里使用commit=False的時候再多一個步驟。多對多關(guān)系在主對象被保存之前是不能被設(shè)置的(因?yàn)樾枰浪跀?shù)據(jù)庫中的id)。因此在有多對多關(guān)系的表單中使用commit=False的時候,表單將會有一個名為save_m2m()的方法,為最終的多對多關(guān)系保存數(shù)據(jù)。你需要在保存了主對象之后手動的調(diào)用這個方法(不帶參數(shù))。

現(xiàn)在,你可以打開cab/urls/snippets.py,然后添加一個新的導(dǎo)入語句:

from cab.views.snippets import add_snippet

以及一個新的URL模式:

url(r'^add/$',add_snippet,name='cab_snippet_add'),

9.4 簡化顯示表單的模版

之前列出的模版還是會繼續(xù)正常的工作,因?yàn)楸韱蔚淖侄螞]有改變。但是,如果django提供一種在模版中顯示表單的簡單方式可以使你免除編寫全部重復(fù)的HTML和字段錯誤檢查的話就太好了。你已經(jīng)避免了自行定義表單類的冗繁工作,為什么不排除在模版上的冗余工作呢?

為此,每個Django表單都有一些附加的方法來獲知如何將表單渲染為不同類型的HTML:

as_url:將表單渲染為一系列HTML列表項(xiàng)(<li>標(biāo)簽),一個字段一項(xiàng)。

as_p:將表單渲染為一系列的段落(<p>標(biāo)簽),一個段落一項(xiàng)。

as_table:將表單渲染為一個HTML表格,一個字段一個<tr>標(biāo)簽。

那么,舉例來說,你可以將目前你所做的模版(以HTML段落元素分隔)替換為如下一句:

{{ form.as_p }}

但是在使用這些方法的時候需要注意:

  • 它們都不輸出閉合的<form>和</form>標(biāo)簽,因?yàn)楸韱巍安恢馈蹦阌媱澰谀抢锾峤换蛘呷绾翁峤槐韱?。你需要自行添加這些標(biāo)簽,并附帶合適的action 及 method 屬性。
  • 它們都不輸出用以提交表單的按鈕。再說一次,表單不知道你像要如何提交,因此你需要自行提供一個或者多個<input type="submit">標(biāo)簽。
  • as_url()方法不輸出環(huán)繞它的<ul>和</ul>標(biāo)簽,as_table()方法也不輸出環(huán)繞它的<table>和</table>標(biāo)簽。這是為了以防你想要添加更多自己的HTML內(nèi)容(在表單顯示中是十分普遍的需要),所以要記住自行填充這些標(biāo)簽。
  • 最后,這些方法不能被簡單的自定義。當(dāng)你只需要基本的表單顯示(特別是為了測試一個應(yīng)用需要的快速原型)的時候,它們是十分簡便的,但是如果你需要自定義表單表現(xiàn)形式的時候,可能需要返回頭去手動的為表單建立模版。

9.5 編輯代碼片段

現(xiàn)在你有了一個可以讓用戶提交他們的代碼段的系統(tǒng),但是如果有人想要返回去編輯他們提交的代碼段該怎么做呢?不可避免的總是會有某人意外的提交了含有錯誤的代碼段,或者找到了解決實(shí)際問題的更好方法。最好是允許用戶在這些情況下能夠編輯他們自己的代碼段,因此我們通過一個名為edite_snippet的視圖來設(shè)置代碼段的編輯。

幸運(yùn)的是,這十分之簡單。ModelForm同樣知道改如何編輯一個已經(jīng)存在的對象,這就解決了大部分的苦活累活。你所要做的,就是處理兩件事:

  • 計算哪個Snippet對象要被編輯
  • 保證要編輯這個Snippet的用戶是代碼段最初的作者

處理第一個問題十分簡單:你可以在URL中設(shè)置你的edite_snippet視圖函數(shù)接收Snippet的id,然后在數(shù)據(jù)庫中查詢它。然后你可以比較Snippet的author字段同當(dāng)前登錄用戶的一致性來保證它們的匹配。所以,我么開始再添加兩個導(dǎo)入語句在cab/views/snippets.py中:

from django.shortcuts import get_object_or_404from django.http import HttpResponseForbidden

HttpResponseForbidden 類表示一個狀態(tài)碼為403的HTTP響應(yīng),它表示用戶沒有他們想要做的事情的權(quán)限。你需要在用戶嘗試編輯不是由他們提交的代碼段的時候使用它。

下面是edite_snippet視圖:

def edit_snippet(request,snippet_id):    snippet = get_object_or_404(Snippet,pk=snippet_id)    if request.user.id != snippet.author.id:        return HttpResponseForbidden()    if request.method == 'POST':        form = SnippetForm(instance=snippet,data=request.POST)        if form.is_valid():            snippet  = form.save()            return HttpResponseRedirect(snippet.get_absolute_url())    else:        form = SnippetForm(instance=snippet)    return render_to_response('cab/edit_snippet.html'.{'form':form})edit_snippet = login_required(edit_snippet)

為了告訴ModelForm子類你要編輯一個已經(jīng)存在的對象,你簡單的將這個對象作為instance參數(shù)傳遞;表單會處理剩下的事情。注意因?yàn)镾nippet已經(jīng)有了一個作者,且這個值是不要改變的,你不需要使用commit=False然后手動的保存Snippet。表單不會修改這個值,所以你可以簡單的讓它來保存即可。

現(xiàn)在你可以為視圖函數(shù)添加一個URL模式。首先修改cab/urls/snippets.py中的導(dǎo)入語句來導(dǎo)入這個視圖函數(shù):

from cab.views.snippet import add_snippet,edit_snippet

然后添加URL模式:

url(r'^edit/(?P<snippet_id>\d+)/$',edit_snippet,name='cab_snippet_edit'),

因?yàn)閑dit_snippet視圖和add_snippet視圖的表單具有同樣的字段,你可以將模版化簡成一個,然后通過傳遞一個指示添加還是編輯的的變量(這樣諸如頁面標(biāo)題之類的元素可以根據(jù)它來改變)。因此我們來修改add_snippet視圖的最后一行來傳遞一個叫做add的額外變量,將其值設(shè)置為True,然后將模版名改為cab/snippet_form.html:

return render_to_response('cab/snippet_form.html',{'form':form,'add':True})

然后修改edit_snippet視圖中同樣的一行來使用cab/snippet_form.html,并將add變量設(shè)置為False:

return render_to_response('cab/snippet_form.html'.{'form':form,'add':False})

現(xiàn)在可以簡化為一個模版——cab/snippet_form.html——內(nèi)容如下:

<html>    <head>        <title>{%if add%}Add a {% else %}Edit your{% endif %} snippet</title>    </head>    <body>        <h1>{% if add  %}Add a {% else %}Edit your{% endif %} snippet</h1>        <p>Use the form below to {% if add %}add{% else %}edit {% endif %}            your snippet;all fields are required</p>         <form action=""  method="post">        {{ form.as_p }}             <p><input type="submit" value="Send" ></p>        </form>    </body></html>

現(xiàn)在,使用戶能夠添加和編輯他們的代碼段的表單、視圖、模版都一應(yīng)俱全了。下面就是完成后的cab/views/snippets.py 文件:

from django.http import HttpResponseForbidden,HttpResponseRedirectfrom django.forms import ModelFormfrom django.shortcuts import get_object_or_404,render_to_responsefrom django.contrib.auth.decorators import login_requiredfrom cab.models import Snippetclass SnippetForm(ModelForm):    class Meta:        model = Snippet        exclude = ['author']def add_snippet(request):    if request.method == 'POST':        form = SnippetForm(data=request.POST)        if form.is_valid():            new_snippet = form.save(commit=False)            new_snipet.author = request.user            new_snippet.save()            return HttpResponseRedirect(new_snippet.get_absolute_url())        else:        form=SnippetForm()    return render_to_response('cab/snippet_form.html',{'form':form,'add':True})add_snippet=login_required(add_snippet)def edit_snippet(request,snippet_id):    snippet = get_object_or_404(Snipet,pk=snippet_id)    if request.user.id != snippet.author.id:        return HttpResponseForbidden()    if request.method == 'POST':        form = SnippetForm(instance=snippet,data=request.POST)        if form.is_valid():            snippet = form.save()            return HttpResponseReidrect(snippet.get_absolute_url())    else:        form = SnippetFrom(instance=snippet)    return render_to_response('cab/snippet_form.html',{'form':form,'add':False})edit_snippet = login_required(edit_snippet)

 

9.2 展望

在繼續(xù)之前,我建議花點(diǎn)時間來熟悉一下Django的表單系統(tǒng)。盡管目前為止你對基本內(nèi)容已經(jīng)有了很好掌握,你可能還想多花點(diǎn)時間來看看django.forms包的完整文檔(在線地址 http://docs/djangoproject.com/en/dev/topics/forms/)對全部特性有一個全面了解(包括全部字段類型和組件,及自定義表單外觀的高級技巧)。

當(dāng)你準(zhǔn)備好回來之后,下一章將會對這個應(yīng)用進(jìn)行最后的掃尾工作,包括添加書簽和評分系統(tǒng),包括最受歡迎的代碼段列表,以及為確定用戶是否標(biāo)記或評分而做的必要的模版擴(kuò)展。

 

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
django form表單
Django限制表單中ForeignKey對應(yīng)下拉菜單選項(xiàng)數(shù)量的兩種經(jīng)典方法
Django 教程 10: 測試 Django 網(wǎng)頁應(yīng)用
Python基礎(chǔ)教程:一次性搞定 Django Form
Django 2.0 發(fā)布,都有哪些新特性要注意?
Django中關(guān)于“CSRF verification failed. Request aborted”的問題
更多類似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服