鍍金池/ 教程/ Python/ 編寫你的第一個 Django 程序 第4部分
點擊劫持保護(hù)
安全問題歸檔
Model 類參考
將遺留數(shù)據(jù)庫整合到Django
關(guān)聯(lián)對象參考
內(nèi)建基于類的視圖的API
聚合
Django 中的用戶認(rèn)證
django.contrib.humanize
Django管理文檔生成器
分頁
使用Django輸出CSV
加密簽名
文件儲存API
安全
Django中的測試
國際化和本地化
為Django編寫首個補(bǔ)丁
條件表達(dá)式
日志
模型元選項
部署靜態(tài)文件
執(zhí)行查詢
使用Django認(rèn)證系統(tǒng)
基于類的視圖
中間件
編寫自定義的django-admin命令
Django 的設(shè)置
格式本地化
數(shù)據(jù)庫訪問優(yōu)化
錯誤報告
基于類的內(nèi)建通用視圖
編寫自定義存儲系統(tǒng)
編寫你的第一個 Django 程序 第3部分
編寫數(shù)據(jù)庫遷移
使用表單
編寫你的第一個 Django 程序 第2部分
編寫你的第一個 Django 程序 第1部分
如何使用會話
系統(tǒng)檢查框架
新手入門
信號
編寫視圖
如何使用WSGI 部署
編寫你的第一個Django應(yīng)用,第6部分
常見的網(wǎng)站應(yīng)用工具
Widgets
內(nèi)建的視圖
模型實例參考
視圖層
Django中的密碼管理
高級教程:如何編寫可重用的應(yīng)用
國際化和本地化
"本地特色"附加功能
TemplateResponse 和 SimpleTemplateResponse
模式編輯器
文件上傳
快速安裝指南
部署 Django
表單 API
表單素材 ( <code>Media</code> 類)
管理文件
其它核心功能
查找 API 參考
表單
Admin
數(shù)據(jù)庫函數(shù)
自定義查找
使用基于類的視圖處理表單
管理操作
開發(fā)過程
編寫你的第一個Django應(yīng)用,第5部分
進(jìn)行原始的sql查詢
模型層
多數(shù)據(jù)庫
編寫你的第一個 Django 程序 第4部分
Django安全
Django 初探
Django異常
重定向應(yīng)用
按需內(nèi)容處理
管理器
視圖裝飾器
驗證器
使用Django輸出PDF
File對象
Django 的快捷函數(shù)
基于類的通用視圖 —— 索引
為模型提供初始數(shù)據(jù)
模板層
URL調(diào)度器
中間件
模型

編寫你的第一個 Django 程序 第4部分

本教程上接 教程 第3部分 。我們將 繼續(xù)開發(fā) Web-poll 應(yīng)用并且關(guān)注在處理簡單的窗體和優(yōu)化我們的代碼。

編寫一個簡單的窗體

讓我們把在上一篇教程中編寫的 poll 的 detail 模板更新下,在模板中包含 HTML 的

組件:

<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>

簡單的總結(jié)下:

  • 上面的模板中為每個投票選項設(shè)置了一個單選按鈕。每個單選按鈕的 value 是投票選項對應(yīng)的 ID 。每個單選按鈕的 name 都是 “choice”。這意味著,當(dāng)有人選擇了一個單選按鈕并提交了表單,將會發(fā)送 的 POST 數(shù)據(jù)是 choice=3。這是 HTML 表單中的基本概念。
  • 我們將 form 的 action 設(shè)置為 {% url 'polls:vote' poll.id %},以及設(shè)置了method="post"` 。使用 method="post" ( 而不是 method="get") 是非常重要的,因為這種提交表單的方式會改變服務(wù)器端的數(shù)據(jù)。 當(dāng)你創(chuàng)建一個表單為了修改服務(wù)器端的數(shù)據(jù)時,請使用 method="post" 。這不是 Django 特定的技巧;這是優(yōu)秀的 Web 開發(fā)實踐。
  • forloop.counter 表示 for 標(biāo)簽在循環(huán)中已經(jīng)循環(huán)過的次數(shù)
  • 由于我們要創(chuàng)建一個POST form ( 具有修改數(shù)據(jù)的功能 ),我們需要擔(dān)心跨站點請求偽造 ( Cross Site Request Forgeries )。 值得慶幸的是,你不必太擔(dān)心這一點,因為 Django 自帶了一個非常容易使用的系統(tǒng)來防御它。 總之,所有的 POST form 針對內(nèi)部的 URLs 時都應(yīng)該使用 {% csrf_token %} 模板標(biāo)簽。

現(xiàn)在,讓我們來創(chuàng)建一個 Django 視圖來處理提交的數(shù)據(jù)。 記得嗎?在 教程 第3部分 中,我們?yōu)?polls 應(yīng)用創(chuàng)建了一個 URLconf 配置中包含有這一行代碼:

url(r'^(?P<poll_id>\d+)/vote/$', views.vote, name='vote'),

我們還創(chuàng)建了一個虛擬實現(xiàn)的 vote() 函數(shù)。讓我們創(chuàng)建一個真實版本吧。在 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,)))

在這代碼中有些內(nèi)容還未在本教程中提到過:

request.POST 是一個類似字典的對象,可以讓你 通過關(guān)鍵字名稱來獲取提交的數(shù)據(jù)。在本例中, request.POST['choice'] 返回了所選擇的投票項目的 ID ,以字符串的形式。 request.POST 的值永遠(yuǎn)是字符串形式的。

請注意 Django 也同樣的提供了通過 request.GET 獲取 GET 數(shù)據(jù)的方法 – 但是在代碼中我們明確的使用了 request.POST 方法,以確保數(shù)據(jù)是通過 POST 方法來修改的。

如果 choice 未在 POST 數(shù)據(jù)中提供 request.POST['choice'] 將拋出 KeyError 當(dāng)未給定 choice 對象時上面的代碼若檢測到拋出的是 KeyError 異常就會向 poll 顯示一條錯誤信息。

在增加了投票選項的統(tǒng)計數(shù)后,代碼返回一個 HttpResponseRedirect 對象而不是常見的 HttpResponse 對象。 HttpResponseRedirect 對象需要一個參數(shù):用戶將被重定向的 URL (請繼續(xù)看下去在這情況下我們是如何構(gòu)造 URL ) 。

就像上面用 Python 作的注釋那樣,當(dāng)成功的處理了 POST 數(shù)據(jù)后你應(yīng)該總是返回一個 HttpResponseRedirect 對象。 這個技巧不是特定于 Django 的;它是優(yōu)秀的 Web 開發(fā)實踐。

在本例中,我們在 HttpResponseRedirect 的構(gòu)造方法中使用了 reverse() 函數(shù)。 此函數(shù)有助于避免在視圖中硬編碼 URL 的功能。它指定了我們想要的跳轉(zhuǎn)的視圖函數(shù)名以及視圖函數(shù)中 URL 模式相應(yīng)的可變參數(shù)。在本例中,我們使用了教程 第3部分中的 URLconf 配置, reverse() 將會返回類似如下所示的字符串

'/polls/3/results/'

... 在此 3 就是 p.id 的值。該重定向 URL 會調(diào)用 'results' 視圖并顯示最終頁面。

正如在教程 第3部分提到的,request 是一個 HttpRequest 對象。想了解 HttpRequest 對象更多的內(nèi)容,請參閱 request 和 response 文檔 。

當(dāng)有人投票后,vote() 視圖會重定向到投票結(jié)果頁。讓我們來編寫這個視圖

def results(request, poll_id):
    poll = get_object_or_404(Poll, pk=poll_id)
    return render(request, 'polls/results.html', {'poll': poll})

這幾乎和 教程 第3部分 中的 detail() 視圖完全一樣。 唯一的區(qū)別就是模板名稱。 稍后我們會解決這個冗余問題。

現(xiàn)在,創(chuàng)建一個 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>

現(xiàn)在,在瀏覽器中訪問 /polls/1/ 并完成投票。每次投票后你將會看到結(jié)果頁數(shù)據(jù)都有更新。 如果你沒有選擇投票選項就提交了,將會看到錯誤的信息。

使用通用視圖:優(yōu)化代碼

detail() ( 在 教程 第3部分 中) 和 results() 視圖 都很簡單 – 并且還有上面所提到的冗余問題。index() 用于顯示 polls 列表的 index() 視圖 (也在教程 第3部分中),也是存在類似的問題。

這些視圖代表了基本的 Web 開發(fā)中一種常見的問題: 根據(jù) URL 中的參數(shù)從數(shù)據(jù)庫中獲取數(shù)據(jù),加載模板并返回渲染后的內(nèi)容。由于這類現(xiàn)象很 常見,因此 Django 提供了一種快捷方式,被稱之為“通用視圖”系統(tǒng)。

通用視圖抽象了常見的模式,以至于你不需要編寫 Python 代碼來編寫一個應(yīng)用。

讓我們把 poll 應(yīng)用修改成使用通用視圖系統(tǒng)的應(yīng)用,這樣我們就能刪除刪除一些我們自己的代碼了。 我們將采取以下步驟來進(jìn)行修改:

  • 修改 URLconf 。
  • 刪除一些舊的,不必要的視圖。
  • 修正 URL 處理到對應(yīng)的新視圖。

請繼續(xù)閱讀了解詳細(xì)的信息。

為什么要重構(gòu)代碼?

通常情況下,當(dāng)你編寫一個 Django 應(yīng)用時,你會評估下通用視圖是否適合解決你的問題, 如果適合你就應(yīng)該從一開始就使用它,而不是進(jìn)行到一半才重構(gòu)你的代碼。 但是本教程直到現(xiàn)在都故意集中介紹“硬編碼”視圖,是為了專注于核心概念上。

就像你在使用計算器前需要知道基本的數(shù)學(xué)知識一樣。

修改 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 。這兩個視圖分別用于顯示兩種抽象概念 “顯示一系列對象的列表” 和 “顯示一個特定類型的對象的詳細(xì)信息頁”。

  • 每個視圖都需要知道使用哪個模型數(shù)據(jù)。因此需要提供將要使用的 model 參數(shù)。
  • DetailView 通用視圖期望從 URL 中捕獲名為 "pk" 的主鍵值,因此我們將 poll_id 改為 pk 。

默認(rèn)情況下, DetailView 通用視圖使用名為 <應(yīng)用名>/<模型名>_detail.html 的模板。在我們的例子中,將使用名為 "polls/poll_detail.html" 的模板。 template_name 參數(shù)是告訴 Django 使用指定的模板名,而不是使用自動生成的默認(rèn)模板名。 我們也指定了 results 列表視圖的 template_name – 這確保了 results 視圖和 detail 視圖渲染時會有不同的外觀,雖然它們有一個 DetailView 隱藏在幕后。

同樣的,~django.views.generic.list.ListView 通用視圖使用的默認(rèn)模板名為 <應(yīng)用名>/<模型名>_list.html ;我們指定了 template_name 參數(shù)告訴 ListView 使用已經(jīng)存在的 "polls/index.html" 模板。

在之前的教程中,模板提供的上下文中包含了 poll 和 latest_poll_list 上下文變量。在 DetailView 中 poll 變量是自動提供的 – 因為我們使用了一個 Django 模型 (Poll) ,Django 能夠為上下文變量確定適合的名稱。 另外 ListView 自動生成的上下文變量名是 poll_list 。若要覆蓋此變量我們需要提供 context_object_name 選項, 我們想要使用 latest_poll_list 來替代它。作為一種替代方式,你可以改變你的模板來 匹配新的默認(rèn)的上下文變量 – 但它是一個非常容易地告訴 Django 使用你想要的變量的方式。

現(xiàn)在你可以在 polls/views.py 中刪除 index() , detail() 和 results() 視圖了。 我們不需要它們了 – 它們已替換為通用視圖了。你也可以刪除不再需要的 HttpResponse 導(dǎo)入包了。

運行服務(wù)器,并且使用下基于通用視圖的新投票應(yīng)用。

有關(guān)通用視圖的完整詳細(xì)信息,請參閱 通用視圖文檔.

當(dāng)你熟悉了窗體和通用視圖后,請閱讀 教程 第5部分 來學(xué)習(xí)測試我們的投票應(yīng)用。

譯者:Django 文檔協(xié)作翻譯小組,原文:Part 4: Forms and generic views

本文以 CC BY-NC-SA 3.0 協(xié)議發(fā)布,轉(zhuǎn)載請保留作者署名和文章出處。

Django 文檔協(xié)作翻譯小組人手緊缺,有興趣的朋友可以加入我們,完全公益性質(zhì)。交流群:467338606。