{% raw %}
# 編寫你的第一個 Django 程序 第3部分 #
本教程上接 教程 第2部分 。我們將繼續 開發 Web-poll 應用并且專注在創建公共界面 – “視圖 (views )”。
## 哲理 ##
在 Django 應用程序中,視圖是一“類”具有特定功能和模板的網頁。 例如,在一個博客應用程序中,你可能會有以下視圖:
+ 博客首頁 – 顯示最新發表的博客。
+ 博客詳細頁面 – 一篇博客的獨立頁面。
+ 基于年份的歸檔頁 – 顯示給定年份中發表博客的所有月份。
+ 基于月份的歸檔頁 – 顯示給定月份中發表博客的所有日期。
+ 基于日期的歸檔頁 – 顯示給定日期中發表的所有的博客。
+ 評論功能 – 為一篇給定博客發表評論。
在我們的 poll 應用程序中,將有以下四個視圖:
+ Poll “index” 頁 – 顯示最新發布的民意調查。
+ Poll “detail” 頁 – 顯示一項民意調查的具體問題,不顯示該項的投票結果但可以進行投票的 form 。
+ Poll “results” 頁 – 顯示一項給定的民意調查的投票結果。
+ 投票功能 – 為一項給定的民意調查處理投票選項。
在 Django 中,網頁及其他內容是由視圖來展現的。而每個視圖就是一個簡單的 Python 函數(或方法, 對于基于類的視圖情況下)。Django 會通過檢查所請求的 URL (確切地說是域名之后的那部分 URL)來匹配一個視圖。
平時你上網的時候可能會遇到像 “ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B” 這種如此美麗的 URL。 但是你會很高興知道 Django 允許我們使用比那優雅的 URL 模式 來展現 URL。
URL 模式就是一個簡單的一般形式的 URL - 比如: `/newsarchive/<year>/<month>/`.
Django 是通過 ‘URLconfs’ 從 URL 獲取到視圖的。而 URLconf 是將 URL 模式 ( 由正則表達式來描述的 ) 映射到視圖的一種配置。
本教程中介紹了使用 URLconfs 的基本指令,你可以查閱 django.core.urlresolvers 來獲取更多信息。
## 編寫你的第一個視圖 ##
讓我們編寫第一個視圖。打開文件 polls/views.py 并在其中輸入以下 Python 代碼
```
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the poll index.")
```
在 Django 中這可能是最簡單的視圖了。為了調用這個視圖,我們需要將它映射到一個 URL – 為此我們需要配置一個URLconf 。
在 polls 目錄下創建一個名為 urls.py 的 URLconf 文檔。 你的應用目錄現在看起來像這樣
```
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 中插入一個 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)),
)
```
現在你在 URLconf 中配置了 index 視圖。通過瀏覽器訪問 http://localhost:8000/polls/ ,如同你在 index 視圖中定義的一樣,你將看到 “Hello, world. You’re at the poll index.” 文字。
url() 函數有四個參數,兩個必須的: regex 和 ``view``, 兩個可選的: ``kwargs``, 以及 ``name``。 接下來,來探討下這些參數的意義。
## url() 參數: regex #
regex 是 regular expression 的簡寫,這是字符串中的模式匹配的一種語法, 在 Django 中就是是 url 匹配模式。 Django 將請求的 URL 從上至下依次匹配列表中的正則表達式,直到匹配到一個為止。
需要注意的是,這些正則表達式不會匹配 GET 和 POST 參數,以及域名。 例如:針對 http://www.example.com/myapp/ 這一請求,URLconf 將只查找 myapp/``。而在 ``http://www.example.com/myapp/?page=3 中 URLconf 也僅查找 myapp/ 。
如果你需要正則表達式方面的幫助,請參閱 Wikipedia’s entry 和本文檔中的 re 模塊。 此外,O’Reilly 出版的由 Jeffrey Friedl 著的 “Mastering Regular Expressions” 也是不錯的。 但是,實際上,你并不需要成為一個正則表達式的專家,僅僅需要知道如何捕獲簡單的模式。 事實上,復雜的正則表達式會降低查找性能,因此你不能完全依賴正則表達式的功能。
最后有個性能上的提示:這些正則表達式在 URLconf 模塊第一次加載時會被編譯。 因此它們速度超快 ( 像上面提到的那樣只要查找的不是太復雜 )。
## url() 參數: view ##
當 Django 匹配了一個正則表達式就會調用指定的視圖功能,包含一個 HttpRequest 實例作為第一個參數和正則表達式 “捕獲” 的一些值的作為其他參數。 如果使用簡單的正則捕獲,將按順序位置傳參數;如果按命名的正則捕獲,將按關鍵字傳參數值。 有關這一點我們會給出一個例子。
## url() 參數: kwargs ##
任意關鍵字參數可傳一個字典至目標視圖。在本教程中,我們并不打算使用 Django 這一特性。
## url() 參數: name ##
命名你的 URL ,讓你在 Django 的其他地方明確地引用它,特別是在模板中。 這一強大的功能可允許你通過一個文件就可全局修改項目中的 URL 模式。
## 編寫更多視圖 ##
現在讓我們添加一些視圖到 polls/views.py 中去。這些視圖與之前的略有不同,因為 它們有一個參數::
```
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/ 。將運行 detail() 方法并且顯示你在 URL 中提供的任意 ID 。試著訪問 http://localhost:8000/polls/34/results/ 和 http://localhost:8000/polls/34/vote/ – 將會顯示對應的結果頁及投票頁。
當有人訪問你的網站頁面如 “ /polls/34/ ” 時,Django 會加載 mysite.urls 模塊,這是因為 ROOT_URLCONF 設置指向它。接著在該模塊中尋找名為``urlpatterns`` 的變量并依次匹配其中的正則表達式。 include() 可讓我們便利地引用其他 URLconfs 。請注意 include() 中的正則表達式沒有 $ (字符串結尾的匹配符 match character) 而尾部是一個反斜杠。當 Django 解析 include() 時,它截取匹配的 URL 那部分而把剩余的字符串交由 加載進來的 URLconf 作進一步處理。
include() 背后隱藏的想法是使 URLs 即插即用。 由于 polls 在自己的 URLconf(polls/urls.py) 中,因此它們可以被放置在 “/polls/” 路徑下,或 “/fun_polls/” 路徑下,或 “/content/polls/” 路徑下,或者其他根路徑,而應用仍可以運行。
以下是當用戶訪問 “/polls/34/” 路徑時系統中將發生的事:
+ Django 將尋找 '^polls/' 的匹配
+ 接著,Django 截取匹配文本 ("polls/") 后剩余的文本 – "34/" – 傳遞到 ‘polls.urls’ URLconf 中作進一步處理, 再將匹配 r'^(?P<poll_id>\d+)/$' 的結果作為參數傳給 detail() 視圖
```
detail(request=<HttpRequest object>, poll_id='34')
```
poll_id='34' 這部分就是來自 (?P<poll_id>\d+) 匹配的結果。 使用括號包圍一個 正則表達式所“捕獲”的文本可作為一個參數傳給視圖函數;``?P<poll_id>`` 將會定義名稱用于標識匹配的內容; 而 \d+ 是一個用于匹配數字序列(即一個數字)的正則表達式。
因為 URL 模式是正則表達式,所以你可以毫無限制地使用它們。但是不要加上 URL 多余的部分如 .html – 除非你想,那你可以像下面這樣::
```
(r'^polls/latest\.html$', 'polls.views.index'),
```
真的,不要這樣做。這很傻。
## 在視圖中添加些實際的功能 ##
每個視圖只負責以下兩件事中的一件:返回一個 HttpResponse 對象,其中包含了所請求頁面的內容, 或者拋出一個異常,例如 Http404 。剩下的就由你來實現了。
你的視圖可以讀取數據庫記錄,或者不用。它可以使用一個模板系統,例如 Django 的 – 或者第三方的 Python 模板系統 – 或不用。它可以生成一個 PDF 文件,輸出 XML , 即時創建 ZIP 文件, 你可以使用你想用的任何 Python 庫來做你想做的任何事。
而 Django 只要求是一個 HttpResponse 或一個異常。
因為它很方便,那讓我們來使用 Django 自己的數據庫 API 吧, 在 教程 第1部分 中提過。修改下 index() 視圖, 讓它顯示系統中最新發布的 5 個調查問題,以逗號分割并按發布日期排序::
```
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)
```
在這就有了個問題,頁面的設計是硬編碼在視圖中的。如果你想改變頁面的外觀,就必須修改這里的 Python 代碼。因此,讓我們使用 Django 的模板系統創建一個模板給視圖用,就使頁面設計從 Python 代碼中 分離出來了。
首先,在 polls 目錄下創建一個 templates 目錄。 Django 將會在那尋找模板。
Django 的 TEMPLATE_LOADERS 配置中包含一個知道如何從各種來源導入模板的可調用的方法列表。 其中有一個默認值是 django.template.loaders.app_directories.Loader ,Django 就會在每個 INSTALLED_APPS 的 “templates” 子目錄下查找模板 - 這就是 Django 知道怎么找到 polls 模板的原因,即使我們 沒有修改 TEMPLATE_DIRS, 還是如同在 教程 第2部分 那樣。
> 組織模板
>
> 我們 能夠 在一個大的模板目錄下一起共用我們所有的模板,而且它們會運行得很好。 但是,此模板屬于 polls 應用,因此與我們在上一個教程中創建的管理模板不同, 我們要把這個模板放在應用的模板目錄 (polls/templates) 下而不是項目的模板目錄 (templates) 。 我們將在 可重用的應用教程 中詳細討論我們 為什么 要這樣做。
在你剛才創建的``templates`` 目錄下,另外創建個名為 polls 的目錄,并在其中創建一個 index.html 文件。換句話說,你的模板應該是 polls/templates/polls/index.html 。由于知道如上所述的 app_directories 模板加載器是 如何運行的,你可以參考 Django 內的模板簡單的作為 polls/index.html 模板。
> 模板命名空間
>
> 現在我們 也許 能夠直接把我們的模板放在 polls/templates 目錄下 ( 而不是另外創建 polls 子目錄 ) , 但它實際上是一個壞注意。 Django 將會選擇第一個找到的按名稱匹配的模板, 如果你在 不同 應用中有相同的名稱的模板,Django 將無法區分它們。 我們想要讓 Django 指向正確的模板,最簡單的方法是通過 命名空間 來確保是 他們的模板。也就是說,將模板放在 另一個 目錄下并命名為應用本身的名稱。
將以下代碼添加到該模板中:
```
{% 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 %}
```
現在讓我們在 index 視圖中使用這個模板:
```
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 模板并傳遞一個 context 變量。 The context is a dictionary mapping template variable names to Python 該 context 變量是一個映射了 Python 對象到模板變量的字典。
在你的瀏覽器中加載 “/polls/” 頁,你應該看到一個列表,包含了在教程 第1部分 中創建的 “What’s up” 調查。而鏈接指向 poll 的詳細頁面。
## 快捷方式: render() ##
這是一個非常常見的習慣用語,用于加載模板,填充上下文并返回一個含有模板渲染結果的 HttpResponse 對象。 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)
```
請注意,一旦我們在所有視圖中都這樣做了,我們就不再需要導入 loader , Context 和 HttpResponse ( 如果你仍然保留了 detail,``resutls``, 和``vote`` 方法,你還是需要保留 HttpResponse ) 。
render() 函數中第一個參數是 request 對象,第二個參數是一個模板名稱,第三個是一個字典類型的可選參數。 它將返回一個包含有給定模板根據給定的上下文渲染結果的 HttpResponse 對象。
## 拋出 404 異常 ##
現在讓我們解決 poll 的詳細視圖 – 該頁顯示一個給定 poll 的詳細問題。 視圖代碼如下所示::
```
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})
```
在這有個新概念:如果請求的 poll 的 ID 不存在,該視圖將拋出 Http404 異常。
我們稍后討論如何設置 polls/detail.html 模板,若是你想快速運行上面的例子, 在模板文件中添加如下代碼:
```
{{ poll }}
```
現在你可以運行了。
## 快捷方式: get_object_or_404() ##
這很常見,當你使用 get() 獲取對象時 對象卻不存在時就會拋出 Http404 異常。對此 Django 提供了一個快捷操作。如下所示重寫 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() 函數需要一個 Django 模型類作為第一個參數以及 一些關鍵字參數,它將這些參數傳遞給模型管理器中的 get() 函數。 若對象不存在時就拋出 Http404 異常。
> 哲理
>
> 為什么我們要使用一個 get_object_or_404() 輔助函數 而不是在更高級別自動捕獲 ObjectDoesNotExist 異常, 或者由模型 API 拋出 Http404 異常而不是 ObjectDoesNotExist 異常?
>
> 因為那樣會使模型層與視圖層耦合在一起。Django 最重要的設計目標之一 就是保持松耦合。一些控制耦合在 django.shortcuts 模塊中介紹。
還有個 get_list_or_404() 函數,與 get_object_or_404() 一樣 – 不過執行的是 filter() 而不是 get() 。若返回的是空列表將拋出 Http404 異常。
## 編寫一個 404 ( 頁面未找到 ) 視圖 ##
當你在視圖中拋出 Http404 時,Django 將載入一個特定的視圖來處理 404 錯誤。Django 會根據你的 root URLconf ( 僅在你的 root URLconf 中;在其他任何地方設置 handler404 都無效 )中設置的 handler404 變量來查找該視圖,這個變量是個 Python 包格式字符串 – 和標準 URLconf 中的回調函數格式是一樣的。 404 視圖本身沒有什么特殊性:它就是一個普通的視圖。
通常你不必費心去編寫 404 視圖。若你沒有設置 handler404 變量,默認情況下會使用內置的 django.views.defaults.page_not_found() 視圖。或者你可以在你的模板目錄下的根目錄中 創建一個 404.html 模板。當 DEBUG 值是 False ( 在你的 settings 模塊中 ) 時, 默認的 404 視圖將使用此模板來顯示所有的 404 錯誤。 如果你創建了這個模板,至少添加些如“頁面未找到” 的內容。
一些有關 404 視圖需要注意的事項 :
+ 如果 DEBUG 設為 True ( 在你的 settings 模塊里 ) 那么你的 404 視圖將永遠不會被使用 ( 因此 404.html 模板也將永遠不會被渲染 ) 因為將要顯示的是跟蹤信息。
+ 當 Django 在 URLconf 中不能找到能匹配的正則表達式時 404 視圖也將被調用。
編寫一個 500 ( 服務器錯誤 ) 視圖
類似的,你可以在 root URLconf 中定義 handler500 變量,在服務器發生錯誤時 調用它指向的視圖。服務器錯誤是指視圖代碼產生的運行時錯誤。
同樣,你在模板根目錄下創建一個 500.html 模板并且添加些像“出錯了”的內容。
## 使用模板系統 ##
回到我們 poll 應用的 detail() 視圖中,指定 poll 變量后,``polls/detail.html`` 模板可能看起來這樣 :
```
<h1>{{ poll.question }}</h1>
<ul>
{% for choice in poll.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
```
模板系統使用了“變量.屬性”的語法訪問變量的屬性值。 例如 `{{ poll.question }}` , 首先 Django 對 poll 對象做字典查詢。 否則 Django 會嘗試屬性查詢 – 在本例中屬性查詢成功了。 如果屬性查詢還是失敗了,Django 將嘗試 list-index 查詢。
在 `{% for %}` 循環中有方法調用: poll.choice_set.all 就是 Python 代碼 poll.choice_set.all(),它將返回一組可迭代的 Choice 對象,可以用在 `{% for %}` 標簽中。
請參閱 模板指南 來了解模板的更多內容。
## 移除模板中硬編碼的 URLS ##
記得嗎? 在 polls/index.html 模板中,我們鏈接到 poll 的鏈接是硬編碼成這樣子的:
```
<li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li>
```
問題出在硬編碼,緊耦合使得在大量的模板中修改 URLs 成為富有挑戰性的項目。 不過,既然你在 polls.urls 模塊中的 url() 函數中定義了 命名參數,那么就可以在 url 配置中使用 `{% url %}` 模板標記來移除特定的 URL 路徑依賴:
```
<li><a href="{% url 'detail' poll.id %}">{{ poll.question }}</a></li>
```
> Note
>
> 如果 `{% url 'detail' poll.id %}` (含引號) 不能運行,但是 `{% url detail poll.id %}` (不含引號) 卻能運行,那么意味著你使用的 Djang 低于 < 1.5 版。這樣的話,你需要在模板文件的頂部添加如下的聲明::
>
```
{% load url from future %}
```
>
其原理就是在 polls.urls 模塊中尋找指定的 URL 定義。 你知道命名為 ‘detail’ 的 URL 就如下所示那樣定義的一樣::
```
...
# 'name' 的值由 {% url %} 模板標記來引用
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'),
...
```
## URL 名稱的命名空間 ##
本教程中的項目只有一個應用:``polls`` 。在實際的 Django 項目中,可能有 5、10、20 或者 更多的應用。Django 是如何區分它們的 URL 名稱的呢?比如說,``polls`` 應用有一個 detail 視圖,而可能會在同一個項目中是一個博客應用的視圖。Django 是如何知道 使用 `{% url %}` 模板標記創建應用的 url 時選擇正確呢?
答案是在你的 root URLconf 配置中添加命名空間。在 mysite/urls.py 文件 (項目的 ``urls.py``,不是應用的) 中,修改為包含命名空間的定義:
```
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)),
)
```
現在將你的 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>
```
當你編寫視圖熟練后,請閱讀 教程 第4部分 來學習如何處理簡單的表單和通用視圖。
> 譯者:[Django 文檔協作翻譯小組](http://python.usyiyi.cn/django/index.html),原文:[Part 3: Views and templates](https://docs.djangoproject.com/en/1.8/intro/tutorial03/)。
>
> 本文以 [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格式化輸入
- “本地特色”
- 常見的網站應用工具
- 認證
- 概覽
- 使用認證系統
- 密碼管理
- 日志
- 分頁
- 會話
- 數據驗證
- 其它核心功能
- 按需內容處理
- 重定向
- 信號
- 系統檢查框架