本教程上接 教程 第3部分 。咱們將 繼續開發 Web-poll 應用而且關注在處理簡單的窗體和優化咱們的代碼。html
讓咱們把在上一篇教程中編寫的 poll 的 detail 模板更新下,在模板中包含 HTML 的 <form> 組件:python
<h1>{{ poll.question }}</h1> {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %} <form action="{% url 'polls:vote' poll.id %}" method="post"> {% csrf_token %} {% for choice in poll.choice_set.all %} <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" /> <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br /> {% endfor %} <input type="submit" value="Vote" /> </form>
簡單的總結下:數據庫
上面的模板中爲每一個投票選項設置了一個單選按鈕。每一個單選按鈕的 value 是投票選項對應的 ID 。每一個單選按鈕的 name 都是 「choice」
。這意味着,當有人選擇了一個單選按鈕並提交了表單,將會發送 的 POST 數據是 choice=3
。這是 HTML 表單中的基本概念。django
咱們將 form 的 action 設置爲 {% url 'polls:vote' poll.id %},以及設置了
method="post" 。使用 method="post" ( 而不是 method="get") 是很是重要的,由於這種提交表單的方式會改變服務器端的數據。 當你建立一個表單爲了修改服務器端的數據時,請使用 method="post" 。這不是 Django 特定的技巧;這是優秀的 Web 開發實踐。瀏覽器
forloop.counter 表示 for 標籤在循環中已經循環過的次數服務器
因爲咱們要建立一個POST form ( 具備修改數據的功能 ),咱們須要擔憂跨站點請求僞造 ( Cross Site Request Forgeries )。 值得慶幸的是,你沒必要太擔憂這一點,由於 Django 自帶了一個很是容易使用的系統來防護它。 總之,全部的 POST form 針對內部的 URLs 時都應該使用 {% csrf_token %} 模板標籤。函數
如今,讓咱們來建立一個 Django 視圖來處理提交的數據。 記得嗎?在 教程 第3部分 中,咱們爲 polls 應用建立了一個 URLconf 配置中包含有這一行代碼:oop
url(r'^(?P<poll_id>\d+)/vote/$', views.vote, name='vote'),
咱們還建立了一個虛擬實現的 vote() 函數。讓咱們建立一個真實版本吧。在 polls/views.py 中添加以下代碼:post
from django.shortcuts import get_object_or_404, render from django.http import HttpResponseRedirect, HttpResponse from django.core.urlresolvers import reverse from polls.models import Choice, Poll # ... def vote(request, poll_id): p = get_object_or_404(Poll, pk=poll_id) try: selected_choice = p.choice_set.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): # Redisplay the poll voting form. return render(request, 'polls/detail.html', { 'poll': p, 'error_message': "You didn't select a choice.", }) else: selected_choice.votes += 1 selected_choice.save() # Always return an HttpResponseRedirect after successfully dealing # with POST data. This prevents data from being posted twice if a # user hits the Back button. return HttpResponseRedirect(reverse('polls:results', args=(p.id,)))
在這代碼中有些內容還未在本教程中提到過:學習
request.POST 是一個相似字典的對象,可讓你 經過關鍵字名稱來獲取提交的數據。在本例中, request.POST['choice'] 返回了所選擇的投票項目的 ID ,以字符串的形式。 request.POST 的值永遠是字符串形式的。
請注意 Django 也一樣的提供了經過 request.GET 獲取 GET 數據的方法 – 可是在代碼中咱們明確的使用了 request.POST 方法,以確保數據是經過 POST 方法來修改的。
若是 choice 未在 POST 數據中提供 request.POST['choice'] 將拋出 KeyError 當未給定 choice 對象時上面的代碼若檢測到拋出的是 KeyError 異常就會向 poll 顯示一條錯誤信息。
在增長了投票選項的統計數後,代碼返回一個 HttpResponseRedirect 對象而不是常見的 HttpResponse 對象。 HttpResponseRedirect 對象須要一個參數:用戶將被重定向的 URL (請繼續看下去在這狀況下咱們是如何構造 URL ) 。
就像上面用 Python 做的註釋那樣,當成功的處理了 POST 數據後你應該老是返回一個 HttpResponseRedirect 對象。 這個技巧不是特定於 Django 的;它是優秀的 Web 開發實踐。
在本例中,咱們在 HttpResponseRedirect 的構造方法中使用了 reverse() 函數。 此函數有助於避免在視圖中硬編碼 URL 的功能。它指定了咱們想要的跳轉的視圖函數名以及視圖函數中 URL 模式相應的可變參數。在本例中,咱們使用了教程 第3部分中的 URLconf 配置, reverse() 將會返回相似以下所示的字符串
'/polls/3/results/'
... 在此 3 就是 p.id 的值。該重定向 URL 會調用 'results' 視圖並顯示最終頁面。
正如在教程 第3部分提到的,request
是一個 HttpRequest 對象。想了解 HttpRequest 對象更多的內容,請參閱 request 和 response 文檔 。
當有人投票後,vote()
視圖會重定向到投票結果頁。讓咱們來編寫這個視圖
def results(request, poll_id): poll = get_object_or_404(Poll, pk=poll_id) return render(request, 'polls/results.html', {'poll': poll})
這幾乎和 教程 第3部分 中的 detail() 視圖徹底同樣。 惟一的區別就是模板名稱。 稍後咱們會解決這個冗餘問題。
如今,建立一個 polls/results.html 模板:
<h1>{{ poll.question }}</h1> <ul> {% for choice in poll.choice_set.all %} <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li> {% endfor %} </ul> <a href="{% url 'polls:detail' poll.id %}">Vote again?</a>
如今,在瀏覽器中訪問 /polls/1/ 並完成投票。每次投票後你將會看到結果頁數據都有更新。 若是你沒有選擇投票選項就提交了,將會看到錯誤的信息。
detail() ( 在 教程 第3部分 中) 和 results() 視圖 都很簡單 – 而且還有上面所提到的冗餘問題。index()
用於顯示 polls 列表的 index() 視圖 (也在教程 第3部分中),也是存在相似的問題。
這些視圖表明瞭基本的 Web 開發中一種常見的問題: 根據 URL 中的參數從數據庫中獲取數據,加載模板並返回渲染後的內容。因爲這類現象很 常見,所以 Django 提供了一種快捷方式,被稱之爲「通用視圖」系統。
通用視圖抽象了常見的模式,以致於你不須要編寫 Python 代碼來編寫一個應用。
讓咱們把 poll 應用修改爲使用通用視圖系統的應用,這樣咱們就能刪除刪除一些咱們本身的代碼了。 咱們將採起如下步驟來進行修改:
修改 URLconf 。
刪除一些舊的,沒必要要的視圖。
修正 URL 處理到對應的新視圖。
請繼續閱讀了解詳細的信息。
爲何要重構代碼?
一般狀況下,當你編寫一個 Django 應用時,你會評估下通用視圖是否適合解決你的問題, 若是適合你就應該從一開始就使用它,而不是進行到一半才重構你的代碼。 可是本教程直到如今都故意集中介紹「硬編碼」視圖,是爲了專一於核心概念上。
就像你在使用計算器前須要知道基本的數學知識同樣。
首先,打開 polls/urls.py 的 URLconf 配置文件並修改爲以下所示樣子
from django.conf.urls import patterns, url from django.views.generic import DetailView, ListView from polls.models import Poll urlpatterns = patterns('', url(r'^$', ListView.as_view( queryset=Poll.objects.order_by('-pub_date')[:5], context_object_name='latest_poll_list', template_name='polls/index.html'), name='index'), url(r'^(?P<pk>\d+)/$', DetailView.as_view( model=Poll, template_name='polls/detail.html'), name='detail'), url(r'^(?P<pk>\d+)/results/$', DetailView.as_view( model=Poll, template_name='polls/results.html'), name='results'), url(r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote', name='vote'), )
在這咱們將使用兩個通用視圖: ListView 和 DetailView 。這兩個視圖分別用於顯示兩種抽象概念 「顯示一系列對象的列表」 和 「顯示一個特定類型的對象的詳細信息頁」。
每一個視圖都須要知道使用哪一個模型數據。所以須要提供將要使用的 model 參數。
DetailView 通用視圖指望從 URL 中捕獲名爲 "pk" 的主鍵值,所以咱們將 poll_id 改成 pk 。
默認狀況下, DetailView 通用視圖使用名爲 <應用名>/<模型名>detail.html 的模板。在咱們的例子中,將使用名爲 "polls/polldetail.html" 的模板。 templatename 參數是告訴 Django 使用指定的模板名,而不是使用自動生成的默認模板名。 咱們也指定了 results 列表視圖的 templatename – 這確保了 results 視圖和 detail 視圖渲染時會有不一樣的外觀,雖然它們有一個 DetailView 隱藏在幕後。
一樣的,~django.views.generic.list.ListView 通用視圖使用的默認模板名爲 <應用名>/<模型名>list.html ;咱們指定了 templatename 參數告訴 ListView 使用已經存在的 "polls/index.html" 模板。
在以前的教程中,模板提供的上下文中包含了 poll 和 latestpolllist 上下文變量。在 DetailView 中 poll 變量是自動提供的 – 由於咱們使用了一個 Django 模型 (Poll) ,Django 可以爲上下文變量肯定適合的名稱。 另外 ListView 自動生成的上下文變量名是 polllist 。若要覆蓋此變量咱們須要提供 contextobjectname 選項, 咱們想要使用 latestpoll_list 來替代它。做爲一種替代方式,你能夠改變你的模板來 匹配新的默認的上下文變量 – 但它是一個很是容易地告訴 Django 使用你想要的變量的方式。
如今你能夠在 polls/views.py 中刪除 index() , detail() 和 results() 視圖了。 咱們不須要它們了 – 它們已替換爲通用視圖了。你也能夠刪除再也不須要的 HttpResponse 導入包了。
運行服務器,而且使用下基於通用視圖的新投票應用。
有關通用視圖的完整詳細信息,請參閱 通用視圖文檔.
當你熟悉了窗體和通用視圖後,請閱讀 教程 第5部分 來學習測試咱們的投票應用。
譯者:Django 文檔協做翻譯小組,原文:Part 4: Forms and generic views。
本文以 CC BY-NC-SA 3.0 協議發佈,轉載請保留做者署名和文章出處。
Django 文檔協做翻譯小組人手緊缺,有興趣的朋友能夠加入咱們,徹底公益性質。交流羣:467338606。