本教程上接 教程 第2部分 。我們將繼續(xù) 開發(fā) Web-poll 應(yīng)用并且專注在創(chuàng)建公共界面 – “視圖 (views )”。
在 Django 應(yīng)用程序中,視圖是一“類”具有特定功能和模板的網(wǎng)頁。 例如,在一個(gè)博客應(yīng)用程序中,你可能會(huì)有以下視圖:
在我們的 poll 應(yīng)用程序中,將有以下四個(gè)視圖:
在 Django 中,網(wǎng)頁及其他內(nèi)容是由視圖來展現(xiàn)的。而每個(gè)視圖就是一個(gè)簡(jiǎn)單的 Python 函數(shù)(或方法, 對(duì)于基于類的視圖情況下)。Django 會(huì)通過檢查所請(qǐng)求的 URL (確切地說是域名之后的那部分 URL)來匹配一個(gè)視圖。
平時(shí)你上網(wǎng)的時(shí)候可能會(huì)遇到像 “ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core-Pages&gid=A6CD4967199A42D9B65B1B” 這種如此美麗的 URL。 但是你會(huì)很高興知道 Django 允許我們使用比那優(yōu)雅的 URL 模式 來展現(xiàn) URL。
URL 模式就是一個(gè)簡(jiǎn)單的一般形式的 URL - 比如: /newsarchive/<year>/<month>/
.
Django 是通過 ‘URLconfs’ 從 URL 獲取到視圖的。而 URLconf 是將 URL 模式 ( 由正則表達(dá)式來描述的 ) 映射到視圖的一種配置。
本教程中介紹了使用 URLconfs 的基本指令,你可以查閱 django.core.urlresolvers 來獲取更多信息。
讓我們編寫第一個(gè)視圖。打開文件 polls/views.py 并在其中輸入以下 Python 代碼
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the poll index.")
在 Django 中這可能是最簡(jiǎn)單的視圖了。為了調(diào)用這個(gè)視圖,我們需要將它映射到一個(gè) URL – 為此我們需要配置一個(gè)URLconf 。
在 polls 目錄下創(chuàng)建一個(gè)名為 urls.py 的 URLconf 文檔。 你的應(yīng)用目錄現(xiàn)在看起來像這樣
polls/
__init__.py
admin.py
models.py
tests.py
urls.py
views.py
在 polls/urls.py 文件中輸入以下代碼:
from django.conf.urls import patterns, url
from polls import views
urlpatterns = patterns('',
url(r'^$', views.index, name='index')
)
下一步是將 polls.urls 模塊指向 root URLconf 。在 mysite/urls.py 中插入一個(gè) include() 方法,最后的樣子如下所示
from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
url(r'^polls/', include('polls.urls')),
url(r'^admin/', include(admin.site.urls)),
)
現(xiàn)在你在 URLconf 中配置了 index 視圖。通過瀏覽器訪問 http://localhost:8000/polls/ ,如同你在 index 視圖中定義的一樣,你將看到 “Hello, world. You’re at the poll index.” 文字。
url() 函數(shù)有四個(gè)參數(shù),兩個(gè)必須的: regex 和 view
, 兩個(gè)可選的: kwargs
, 以及 name
。 接下來,來探討下這些參數(shù)的意義。
regex 是 regular expression 的簡(jiǎn)寫,這是字符串中的模式匹配的一種語法, 在 Django 中就是是 url 匹配模式。 Django 將請(qǐng)求的 URL 從上至下依次匹配列表中的正則表達(dá)式,直到匹配到一個(gè)為止。
需要注意的是,這些正則表達(dá)式不會(huì)匹配 GET 和 POST 參數(shù),以及域名。 例如:針對(duì) http://www.example.com/myapp/ 這一請(qǐng)求,URLconf 將只查找 myapp/。而在
http://www.example.com/myapp/?page=3 中 URLconf 也僅查找 myapp/ 。
如果你需要正則表達(dá)式方面的幫助,請(qǐng)參閱 Wikipedia’s entry 和本文檔中的 re 模塊。 此外,O’Reilly 出版的由 Jeffrey Friedl 著的 “Mastering Regular Expressions” 也是不錯(cuò)的。 但是,實(shí)際上,你并不需要成為一個(gè)正則表達(dá)式的專家,僅僅需要知道如何捕獲簡(jiǎn)單的模式。 事實(shí)上,復(fù)雜的正則表達(dá)式會(huì)降低查找性能,因此你不能完全依賴正則表達(dá)式的功能。
最后有個(gè)性能上的提示:這些正則表達(dá)式在 URLconf 模塊第一次加載時(shí)會(huì)被編譯。 因此它們速度超快 ( 像上面提到的那樣只要查找的不是太復(fù)雜 )。
當(dāng) Django 匹配了一個(gè)正則表達(dá)式就會(huì)調(diào)用指定的視圖功能,包含一個(gè) HttpRequest 實(shí)例作為第一個(gè)參數(shù)和正則表達(dá)式 “捕獲” 的一些值的作為其他參數(shù)。 如果使用簡(jiǎn)單的正則捕獲,將按順序位置傳參數(shù);如果按命名的正則捕獲,將按關(guān)鍵字傳參數(shù)值。 有關(guān)這一點(diǎn)我們會(huì)給出一個(gè)例子。
任意關(guān)鍵字參數(shù)可傳一個(gè)字典至目標(biāo)視圖。在本教程中,我們并不打算使用 Django 這一特性。
命名你的 URL ,讓你在 Django 的其他地方明確地引用它,特別是在模板中。 這一強(qiáng)大的功能可允許你通過一個(gè)文件就可全局修改項(xiàng)目中的 URL 模式。
現(xiàn)在讓我們添加一些視圖到 polls/views.py 中去。這些視圖與之前的略有不同,因?yàn)?它們有一個(gè)參數(shù)::
def detail(request, poll_id):
return HttpResponse("You're looking at poll %s." % poll_id)
def results(request, poll_id):
return HttpResponse("You're looking at the results of poll %s." % poll_id)
def vote(request, poll_id):
return HttpResponse("You're voting on poll %s." % poll_id)
將新視圖按如下所示的 url() 方法添加到 polls.urls 模塊中去::
from django.conf.urls import patterns, url
from polls import views
urlpatterns = patterns('',
# ex: /polls/
url(r'^$', views.index, name='index'),
# ex: /polls/5/
url(r'^(?P<poll_id>\d-)/$', views.detail, name='detail'),
# ex: /polls/5/results/
url(r'^(?P<poll_id>\d-)/results/$', views.results, name='results'),
# ex: /polls/5/vote/
url(r'^(?P<poll_id>\d-)/vote/$', views.vote, name='vote'),
)
在你的瀏覽器中訪問 http://localhost:8000/polls/34/ 。將運(yùn)行 detail() 方法并且顯示你在 URL 中提供的任意 ID 。試著訪問 http://localhost:8000/polls/34/results/ 和 http://localhost:8000/polls/34/vote/ – 將會(huì)顯示對(duì)應(yīng)的結(jié)果頁及投票頁。
當(dāng)有人訪問你的網(wǎng)站頁面如 “ /polls/34/ ” 時(shí),Django 會(huì)加載 mysite.urls 模塊,這是因?yàn)?ROOT_URLCONF 設(shè)置指向它。接著在該模塊中尋找名為urlpatterns
的變量并依次匹配其中的正則表達(dá)式。 include() 可讓我們便利地引用其他 URLconfs 。請(qǐng)注意 include() 中的正則表達(dá)式?jīng)]有 $ (字符串結(jié)尾的匹配符 match character) 而尾部是一個(gè)反斜杠。當(dāng) Django 解析 include() 時(shí),它截取匹配的 URL 那部分而把剩余的字符串交由 加載進(jìn)來的 URLconf 作進(jìn)一步處理。
include() 背后隱藏的想法是使 URLs 即插即用。 由于 polls 在自己的 URLconf(polls/urls.py) 中,因此它們可以被放置在 “/polls/” 路徑下,或 “/fun_polls/” 路徑下,或 “/content/polls/” 路徑下,或者其他根路徑,而應(yīng)用仍可以運(yùn)行。
以下是當(dāng)用戶訪問 “/polls/34/” 路徑時(shí)系統(tǒng)中將發(fā)生的事:
detail(request=<HttpRequest object>, poll_id='34')
poll_id='34' 這部分就是來自 (?P?P<poll_id>
將會(huì)定義名稱用于標(biāo)識(shí)匹配的內(nèi)容; 而 \d- 是一個(gè)用于匹配數(shù)字序列(即一個(gè)數(shù)字)的正則表達(dá)式。
因?yàn)?URL 模式是正則表達(dá)式,所以你可以毫無限制地使用它們。但是不要加上 URL 多余的部分如 .html – 除非你想,那你可以像下面這樣::
(r'^polls/latest\.html$', 'polls.views.index'),
真的,不要這樣做。這很傻。
每個(gè)視圖只負(fù)責(zé)以下兩件事中的一件:返回一個(gè) HttpResponse 對(duì)象,其中包含了所請(qǐng)求頁面的內(nèi)容, 或者拋出一個(gè)異常,例如 Http404 。剩下的就由你來實(shí)現(xiàn)了。
你的視圖可以讀取數(shù)據(jù)庫記錄,或者不用。它可以使用一個(gè)模板系統(tǒng),例如 Django 的 – 或者第三方的 Python 模板系統(tǒng) – 或不用。它可以生成一個(gè) PDF 文件,輸出 XML , 即時(shí)創(chuàng)建 ZIP 文件, 你可以使用你想用的任何 Python 庫來做你想做的任何事。
而 Django 只要求是一個(gè) HttpResponse 或一個(gè)異常。
因?yàn)樗芊奖?,那讓我們來使?Django 自己的數(shù)據(jù)庫 API 吧, 在 教程 第1部分 中提過。修改下 index() 視圖, 讓它顯示系統(tǒng)中最新發(fā)布的 5 個(gè)調(diào)查問題,以逗號(hào)分割并按發(fā)布日期排序::
from django.http import HttpResponse
from polls.models import Poll
def index(request):
latest_poll_list = Poll.objects.order_by('-pub_date')[:5]
output = ', '.join([p.question for p in latest_poll_list])
return HttpResponse(output)
在這就有了個(gè)問題,頁面的設(shè)計(jì)是硬編碼在視圖中的。如果你想改變頁面的外觀,就必須修改這里的 Python 代碼。因此,讓我們使用 Django 的模板系統(tǒng)創(chuàng)建一個(gè)模板給視圖用,就使頁面設(shè)計(jì)從 Python 代碼中 分離出來了。
首先,在 polls 目錄下創(chuàng)建一個(gè) templates 目錄。 Django 將會(huì)在那尋找模板。
Django 的 TEMPLATE_LOADERS 配置中包含一個(gè)知道如何從各種來源導(dǎo)入模板的可調(diào)用的方法列表。 其中有一個(gè)默認(rèn)值是 django.template.loaders.app_directories.Loader ,Django 就會(huì)在每個(gè) INSTALLED_APPS 的 “templates” 子目錄下查找模板 - 這就是 Django 知道怎么找到 polls 模板的原因,即使我們 沒有修改 TEMPLATE_DIRS, 還是如同在 教程 第2部分 那樣。
組織模板
我們 能夠 在一個(gè)大的模板目錄下一起共用我們所有的模板,而且它們會(huì)運(yùn)行得很好。 但是,此模板屬于 polls 應(yīng)用,因此與我們?cè)谏弦粋€(gè)教程中創(chuàng)建的管理模板不同, 我們要把這個(gè)模板放在應(yīng)用的模板目錄 (polls/templates) 下而不是項(xiàng)目的模板目錄 (templates) 。 我們將在 可重用的應(yīng)用教程 中詳細(xì)討論我們 為什么 要這樣做。
在你剛才創(chuàng)建的templates
目錄下,另外創(chuàng)建個(gè)名為 polls 的目錄,并在其中創(chuàng)建一個(gè) index.html 文件。換句話說,你的模板應(yīng)該是 polls/templates/polls/index.html 。由于知道如上所述的 app_directories 模板加載器是 如何運(yùn)行的,你可以參考 Django 內(nèi)的模板簡(jiǎn)單的作為 polls/index.html 模板。
模板命名空間
現(xiàn)在我們 也許 能夠直接把我們的模板放在 polls/templates 目錄下 ( 而不是另外創(chuàng)建 polls 子目錄 ) , 但它實(shí)際上是一個(gè)壞注意。 Django 將會(huì)選擇第一個(gè)找到的按名稱匹配的模板, 如果你在 不同 應(yīng)用中有相同的名稱的模板,Django 將無法區(qū)分它們。 我們想要讓 Django 指向正確的模板,最簡(jiǎn)單的方法是通過 命名空間 來確保是 他們的模板。也就是說,將模板放在 另一個(gè) 目錄下并命名為應(yīng)用本身的名稱。
將以下代碼添加到該模板中:
{% if latest_poll_list %}
<ul>
{% for poll in latest_poll_list %}
<li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
現(xiàn)在讓我們?cè)?index 視圖中使用這個(gè)模板:
from django.http import HttpResponse
from django.template import Context, loader
from polls.models import Poll
def index(request):
latest_poll_list = Poll.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
context = Context({
'latest_poll_list': latest_poll_list,
})
return HttpResponse(template.render(context))
代碼將加載 polls/index.html 模板并傳遞一個(gè) context 變量。 The context is a dictionary mapping template variable names to Python 該 context 變量是一個(gè)映射了 Python 對(duì)象到模板變量的字典。
在你的瀏覽器中加載 “/polls/” 頁,你應(yīng)該看到一個(gè)列表,包含了在教程 第1部分 中創(chuàng)建的 “What’s up” 調(diào)查。而鏈接指向 poll 的詳細(xì)頁面。
這是一個(gè)非常常見的習(xí)慣用語,用于加載模板,填充上下文并返回一個(gè)含有模板渲染結(jié)果的 HttpResponse 對(duì)象。 Django 提供了一種快捷方式。這里重寫完整的 index() 視圖
from django.shortcuts import render
from polls.models import Poll
def index(request):
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
context = {'latest_poll_list': latest_poll_list}
return render(request, 'polls/index.html', context)
請(qǐng)注意,一旦我們?cè)谒幸晥D中都這樣做了,我們就不再需要導(dǎo)入 loader , Context 和 HttpResponse ( 如果你仍然保留了 detail,resutls
, 和vote
方法,你還是需要保留 HttpResponse ) 。
render() 函數(shù)中第一個(gè)參數(shù)是 request 對(duì)象,第二個(gè)參數(shù)是一個(gè)模板名稱,第三個(gè)是一個(gè)字典類型的可選參數(shù)。 它將返回一個(gè)包含有給定模板根據(jù)給定的上下文渲染結(jié)果的 HttpResponse 對(duì)象。
現(xiàn)在讓我們解決 poll 的詳細(xì)視圖 – 該頁顯示一個(gè)給定 poll 的詳細(xì)問題。 視圖代碼如下所示::
from django.http import Http404
# ...
def detail(request, poll_id):
try:
poll = Poll.objects.get(pk=poll_id)
except Poll.DoesNotExist:
raise Http404
return render(request, 'polls/detail.html', {'poll': poll})
在這有個(gè)新概念:如果請(qǐng)求的 poll 的 ID 不存在,該視圖將拋出 Http404 異常。
我們稍后討論如何設(shè)置 polls/detail.html 模板,若是你想快速運(yùn)行上面的例子, 在模板文件中添加如下代碼:
{{ poll }}
現(xiàn)在你可以運(yùn)行了。
這很常見,當(dāng)你使用 get() 獲取對(duì)象時(shí) 對(duì)象卻不存在時(shí)就會(huì)拋出 Http404 異常。對(duì)此 Django 提供了一個(gè)快捷操作。如下所示重寫 detail() 視圖:
from django.shortcuts import render, get_object_or_404
# ...
def detail(request, poll_id):
poll = get_object_or_404(Poll, pk=poll_id)
return render(request, 'polls/detail.html', {'poll': poll})
get_object_or_404() 函數(shù)需要一個(gè) Django 模型類作為第一個(gè)參數(shù)以及 一些關(guān)鍵字參數(shù),它將這些參數(shù)傳遞給模型管理器中的 get() 函數(shù)。 若對(duì)象不存在時(shí)就拋出 Http404 異常。
哲理
為什么我們要使用一個(gè) get_object_or_404() 輔助函數(shù) 而不是在更高級(jí)別自動(dòng)捕獲 ObjectDoesNotExist 異常, 或者由模型 API 拋出 Http404 異常而不是 ObjectDoesNotExist 異常?
因?yàn)槟菢訒?huì)使模型層與視圖層耦合在一起。Django 最重要的設(shè)計(jì)目標(biāo)之一 就是保持松耦合。一些控制耦合在 django.shortcuts 模塊中介紹。
還有個(gè) get_list_or_404() 函數(shù),與 get_object_or_404() 一樣 – 不過執(zhí)行的是 filter() 而不是 get() 。若返回的是空列表將拋出 Http404 異常。
當(dāng)你在視圖中拋出 Http404 時(shí),Django 將載入一個(gè)特定的視圖來處理 404 錯(cuò)誤。Django 會(huì)根據(jù)你的 root URLconf ( 僅在你的 root URLconf 中;在其他任何地方設(shè)置 handler404 都無效 )中設(shè)置的 handler404 變量來查找該視圖,這個(gè)變量是個(gè) Python 包格式字符串 – 和標(biāo)準(zhǔn) URLconf 中的回調(diào)函數(shù)格式是一樣的。 404 視圖本身沒有什么特殊性:它就是一個(gè)普通的視圖。
通常你不必費(fèi)心去編寫 404 視圖。若你沒有設(shè)置 handler404 變量,默認(rèn)情況下會(huì)使用內(nèi)置的 django.views.defaults.page_not_found() 視圖?;蛘吣憧梢栽谀愕哪0迥夸浵碌母夸浿?創(chuàng)建一個(gè) 404.html 模板。當(dāng) DEBUG 值是 False ( 在你的 settings 模塊中 ) 時(shí), 默認(rèn)的 404 視圖將使用此模板來顯示所有的 404 錯(cuò)誤。 如果你創(chuàng)建了這個(gè)模板,至少添加些如“頁面未找到” 的內(nèi)容。
一些有關(guān) 404 視圖需要注意的事項(xiàng) :
類似的,你可以在 root URLconf 中定義 handler500 變量,在服務(wù)器發(fā)生錯(cuò)誤時(shí) 調(diào)用它指向的視圖。服務(wù)器錯(cuò)誤是指視圖代碼產(chǎn)生的運(yùn)行時(shí)錯(cuò)誤。
同樣,你在模板根目錄下創(chuàng)建一個(gè) 500.html 模板并且添加些像“出錯(cuò)了”的內(nèi)容。
回到我們 poll 應(yīng)用的 detail() 視圖中,指定 poll 變量后,polls/detail.html
模板可能看起來這樣 :
<h1>{{ poll.question }}</h1>
<ul>
{% for choice in poll.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
模板系統(tǒng)使用了“變量.屬性”的語法訪問變量的屬性值。 例如 {{ poll.question }}
, 首先 Django 對(duì) poll 對(duì)象做字典查詢。 否則 Django 會(huì)嘗試屬性查詢 – 在本例中屬性查詢成功了。 如果屬性查詢還是失敗了,Django 將嘗試 list-index 查詢。
在 {% for %}
循環(huán)中有方法調(diào)用: poll.choice_set.all 就是 Python 代碼 poll.choice_set.all(),它將返回一組可迭代的 Choice 對(duì)象,可以用在 {% for %}
標(biāo)簽中。
請(qǐng)參閱 模板指南 來了解模板的更多內(nèi)容。
記得嗎? 在 polls/index.html 模板中,我們鏈接到 poll 的鏈接是硬編碼成這樣子的:
<li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li>
問題出在硬編碼,緊耦合使得在大量的模板中修改 URLs 成為富有挑戰(zhàn)性的項(xiàng)目。 不過,既然你在 polls.urls 模塊中的 url() 函數(shù)中定義了 命名參數(shù),那么就可以在 url 配置中使用 {% url %}
模板標(biāo)記來移除特定的 URL 路徑依賴:
<li><a href="{% url 'detail' poll.id %}">{{ poll.question }}</a></li>
Note
如果
{% url 'detail' poll.id %}
(含引號(hào)) 不能運(yùn)行,但是{% url detail poll.id %}
(不含引號(hào)) 卻能運(yùn)行,那么意味著你使用的 Djang 低于 < 1.5 版。這樣的話,你需要在模板文件的頂部添加如下的聲明::{% load url from future %}
其原理就是在 polls.urls 模塊中尋找指定的 URL 定義。 你知道命名為 ‘detail’ 的 URL 就如下所示那樣定義的一樣::
...
# 'name' 的值由 {% url %} 模板標(biāo)記來引用
url(r'^(?P<poll_id>\d-)/$', views.detail, name='detail'),
...
如果你想將 polls 的 detail 視圖的 URL 改成其他樣子,或許像 polls/specifics/12/ 這樣子,那就不需要在模板(或者模板集)中修改而只要在 polls/urls.py 修改就行了:
...
# 新增 'specifics'
url(r'^specifics/(?P<poll_id>\d-)/$', views.detail, name='detail'),
...
本教程中的項(xiàng)目只有一個(gè)應(yīng)用:polls
。在實(shí)際的 Django 項(xiàng)目中,可能有 5、10、20 或者 更多的應(yīng)用。Django 是如何區(qū)分它們的 URL 名稱的呢?比如說,polls
應(yīng)用有一個(gè) detail 視圖,而可能會(huì)在同一個(gè)項(xiàng)目中是一個(gè)博客應(yīng)用的視圖。Django 是如何知道 使用 {% url %}
模板標(biāo)記創(chuàng)建應(yīng)用的 url 時(shí)選擇正確呢?
答案是在你的 root URLconf 配置中添加命名空間。在 mysite/urls.py 文件 (項(xiàng)目的 urls.py
,不是應(yīng)用的) 中,修改為包含命名空間的定義:
from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
url(r'^polls/', include('polls.urls', namespace="polls")),
url(r'^admin/', include(admin.site.urls)),
)
現(xiàn)在將你的 polls/index.html 模板中原來的 detail 視圖:
<li><a href="{% url 'detail' poll.id %}">{{ poll.question }}</a></li>
修改為包含命名空間的 detail 視圖:
<li><a href="{% url 'polls:detail' poll.id %}">{{ poll.question }}</a></li>
當(dāng)你編寫視圖熟練后,請(qǐng)閱讀 教程 第4部分 來學(xué)習(xí)如何處理簡(jiǎn)單的表單和通用視圖。
譯者:Django 文檔協(xié)作翻譯小組,原文:Part 3: Views and templates。
本文以 CC BY-NC-SA 3.0 協(xié)議發(fā)布,轉(zhuǎn)載請(qǐng)保留作者署名和文章出處。
Django 文檔協(xié)作翻譯小組人手緊缺,有興趣的朋友可以加入我們,完全公益性質(zhì)。交流群:467338606。