{% raw %}
# 編寫你的第一個 Django 程序 第4部分 #
本教程上接 教程 第3部分 。我們將 繼續開發 Web-poll 應用并且關注在處理簡單的窗體和優化我們的代碼。
## 編寫一個簡單的窗體 ##
讓我們把在上一篇教程中編寫的 poll 的 detail 模板更新下,在模板中包含 HTML 的 <form> 組件:
```
<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 表單中的基本概念。
+ 我們將 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 配置中包含有這一行代碼:
```
url(r'^(?P<poll_id>\d+)/vote/$', views.vote, name='vote'),
```
我們還創建了一個虛擬實現的 vote() 函數。讓我們創建一個真實版本吧。在 polls/views.py 中添加如下代碼:
```
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 應用時,你會評估下通用視圖是否適合解決你的問題, 如果適合你就應該從一開始就使用它,而不是進行到一半才重構你的代碼。 但是本教程直到現在都故意集中介紹“硬編碼”視圖,是為了專注于核心概念上。
>
> 就像你在使用計算器前需要知道基本的數學知識一樣。
## 修改 URLconf ##
首先,打開 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'),
)
```
## 修改 views ##
在這我們將使用兩個通用視圖: ListView 和 DetailView 。這兩個視圖分別用于顯示兩種抽象概念 “顯示一系列對象的列表” 和 “顯示一個特定類型的對象的詳細信息頁”。
+ 每個視圖都需要知道使用哪個模型數據。因此需要提供將要使用的 model 參數。
+ DetailView 通用視圖期望從 URL 中捕獲名為 "pk" 的主鍵值,因此我們將 poll_id 改為 pk 。
默認情況下, DetailView 通用視圖使用名為 <應用名>/<模型名>_detail.html 的模板。在我們的例子中,將使用名為 "polls/poll_detail.html" 的模板。 template_name 參數是告訴 Django 使用指定的模板名,而不是使用自動生成的默認模板名。 我們也指定了 results 列表視圖的 template_name – 這確保了 results 視圖和 detail 視圖渲染時會有不同的外觀,雖然它們有一個 DetailView 隱藏在幕后。
同樣的,~django.views.generic.list.ListView 通用視圖使用的默認模板名為 <應用名>/<模型名>_list.html ;我們指定了 template_name 參數告訴 ListView 使用已經存在的 "polls/index.html" 模板。
在之前的教程中,模板提供的上下文中包含了 poll 和 latest_poll_list 上下文變量。在 DetailView 中 poll 變量是自動提供的 – 因為我們使用了一個 Django 模型 (Poll) ,Django 能夠為上下文變量確定適合的名稱。 另外 ListView 自動生成的上下文變量名是 poll_list 。若要覆蓋此變量我們需要提供 context_object_name 選項, 我們想要使用 latest_poll_list 來替代它。作為一種替代方式,你可以改變你的模板來 匹配新的默認的上下文變量 – 但它是一個非常容易地告訴 Django 使用你想要的變量的方式。
現在你可以在 polls/views.py 中刪除 index() , detail() 和 results() 視圖了。 我們不需要它們了 – 它們已替換為通用視圖了。你也可以刪除不再需要的 HttpResponse 導入包了。
運行服務器,并且使用下基于通用視圖的新投票應用。
有關通用視圖的完整詳細信息,請參閱 通用視圖文檔.
當你熟悉了窗體和通用視圖后,請閱讀 教程 第5部分 來學習測試我們的投票應用。
> 譯者:[Django 文檔協作翻譯小組](http://python.usyiyi.cn/django/index.html),原文:[Part 4: Forms and generic views](https://docs.djangoproject.com/en/1.8/intro/tutorial04/)。
>
> 本文以 [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/cn/) 協議發布,轉載請保留作者署名和文章出處。
>
> [Django 文檔協作翻譯小組](http://python.usyiyi.cn/django/index.html)人手緊缺,有興趣的朋友可以加入我們,完全公益性質。交流群:467338606。
{% endraw %}
- 新手入門
- 從零開始
- 概覽
- 安裝
- 教程
- 第1部分:模型
- 第2部分:管理站點
- 第3部分:視圖和模板
- 第4部分:表單和通用視圖
- 第5部分:測試
- 第6部分:靜態文件
- 高級教程
- 如何編寫可重用的應用
- 為Django編寫首個補丁
- 模型層
- 模型
- 模型語法
- 元選項
- 模型類
- 查詢集
- 執行查詢
- 查找表達式
- 模型的實例
- 實例方法
- 訪問關聯對象
- 遷移
- 模式編輯器
- 編寫遷移
- 高級
- 管理器
- 原始的SQL查詢
- 聚合
- 多數據庫
- 自定義查找
- 條件表達式
- 數據庫函數
- 其它
- 遺留的數據庫
- 提供初始數據
- 優化數據庫訪問
- 視圖層
- 基礎
- URL配置
- 視圖函數
- 快捷函數
- 裝飾器
- 參考
- 內建的視圖
- TemplateResponse 對象
- 文件上傳
- 概覽
- File 對象
- 儲存API
- 管理文件
- 自定義存儲
- 基于類的視圖
- 概覽
- 內建顯示視圖
- 內建編輯視圖
- API參考
- 分類索引
- 高級
- 生成 CSV
- 生成 PDF
- 中間件
- 概覽
- 內建的中間件類
- 模板層
- 基礎
- 面向設計師
- 語言概覽
- 人性化
- 面向程序員
- 表單
- 基礎
- 概覽
- 表單API
- 內建的Widget
- 高級
- 整合媒體
- 開發過程
- 設置
- 概覽
- 應用程序
- 異常
- 概覽
- django-admin 和 manage.py
- 添加自定義的命令
- 測試
- 介紹
- 部署
- 概述
- WSGI服務器
- 部署靜態文件
- 通過email追蹤代碼錯誤
- Admin
- 管理操作
- 管理文檔生成器
- 安全
- 安全概述
- 說明Django中的安全問題
- 點擊劫持保護
- 加密簽名
- 國際化和本地化
- 概述
- 本地化WEB UI格式化輸入
- “本地特色”
- 常見的網站應用工具
- 認證
- 概覽
- 使用認證系統
- 密碼管理
- 日志
- 分頁
- 會話
- 數據驗證
- 其它核心功能
- 按需內容處理
- 重定向
- 信號
- 系統檢查框架