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

編寫(xiě)你的第一個(gè) Django 程序 第4部分

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

編寫(xiě)一個(gè)簡(jiǎn)單的窗體

讓我們把在上一篇教程中編寫(xiě)的 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ǎn)單的總結(jié)下:

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

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

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

我們還創(chuàng)建了一個(gè)虛擬實(shí)現(xiàn)的 vote() 函數(shù)。讓我們創(chuàng)建一個(gè)真實(shí)版本吧。在 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)容還未在本教程中提到過(guò):

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

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

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

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

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

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

'/polls/3/results/'

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

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

當(dāng)有人投票后,vote() 視圖會(huì)重定向到投票結(jié)果頁(yè)。讓我們來(lái)編寫(xiě)這個(gè)視圖

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ū)別就是模板名稱(chēng)。 稍后我們會(huì)解決這個(gè)冗余問(wèn)題。

現(xiàn)在,創(chuàng)建一個(gè) 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)在,在瀏覽器中訪(fǎng)問(wèn) /polls/1/ 并完成投票。每次投票后你將會(huì)看到結(jié)果頁(yè)數(shù)據(jù)都有更新。 如果你沒(méi)有選擇投票選項(xiàng)就提交了,將會(huì)看到錯(cuò)誤的信息。

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

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

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

通用視圖抽象了常見(jiàn)的模式,以至于你不需要編寫(xiě) Python 代碼來(lái)編寫(xiě)一個(gè)應(yīng)用。

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

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

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

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

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

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

修改 URLconf

首先,打開(kāi) 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

在這我們將使用兩個(gè)通用視圖: ListView 和 DetailView 。這兩個(gè)視圖分別用于顯示兩種抽象概念 “顯示一系列對(duì)象的列表” 和 “顯示一個(gè)特定類(lèi)型的對(duì)象的詳細(xì)信息頁(yè)”。

  • 每個(gè)視圖都需要知道使用哪個(gè)模型數(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 使用指定的模板名,而不是使用自動(dòng)生成的默認(rèn)模板名。 我們也指定了 results 列表視圖的 template_name – 這確保了 results 視圖和 detail 視圖渲染時(shí)會(huì)有不同的外觀,雖然它們有一個(gè) 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 變量是自動(dòng)提供的 – 因?yàn)槲覀兪褂昧艘粋€(gè) Django 模型 (Poll) ,Django 能夠?yàn)樯舷挛淖兞看_定適合的名稱(chēng)。 另外 ListView 自動(dòng)生成的上下文變量名是 poll_list 。若要覆蓋此變量我們需要提供 context_object_name 選項(xiàng), 我們想要使用 latest_poll_list 來(lái)替代它。作為一種替代方式,你可以改變你的模板來(lái) 匹配新的默認(rèn)的上下文變量 – 但它是一個(gè)非常容易地告訴 Django 使用你想要的變量的方式。

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

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

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

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

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

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

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