編寫(xiě)你的第一個(gè) Django 應(yīng)用,,第 3 部分?
這一篇從 教程第 2 部分 結(jié)尾的地方繼續(xù)講起。我們將繼續(xù)編寫(xiě)投票應(yīng)用,并且專(zhuān)注于如何創(chuàng)建公用界面——也被稱(chēng)為“視圖”,。
Django 中的視圖的概念是「一類(lèi)具有相同功能和模板的網(wǎng)頁(yè)的集合」,。比如,在一個(gè)博客應(yīng)用中,,你可能會(huì)創(chuàng)建如下幾個(gè)視圖:
- 博客首頁(yè)——展示最近的幾項(xiàng)內(nèi)容,。
- 內(nèi)容“詳情”頁(yè)——詳細(xì)展示某項(xiàng)內(nèi)容。
- 以年為單位的歸檔頁(yè)——展示選中的年份里各個(gè)月份創(chuàng)建的內(nèi)容,。
- 以月為單位的歸檔頁(yè)——展示選中的月份里各天創(chuàng)建的內(nèi)容,。
- 以天為單位的歸檔頁(yè)——展示選中天里創(chuàng)建的所有內(nèi)容。
- 評(píng)論處理器——用于響應(yīng)為一項(xiàng)內(nèi)容添加評(píng)論的操作,。
而在我們的投票應(yīng)用中,,我們需要下列幾個(gè)視圖:
- 問(wèn)題索引頁(yè)——展示最近的幾個(gè)投票問(wèn)題。
- 問(wèn)題詳情頁(yè)——展示某個(gè)投票的問(wèn)題和不帶結(jié)果的選項(xiàng)列表,。
- 問(wèn)題結(jié)果頁(yè)——展示某個(gè)投票的結(jié)果,。
- 投票處理器——用于響應(yīng)用戶(hù)為某個(gè)問(wèn)題的特定選項(xiàng)投票的操作,。
在 Django 中,,網(wǎng)頁(yè)和其他內(nèi)容都是從視圖派生而來(lái)。每一個(gè)視圖表現(xiàn)為一個(gè)簡(jiǎn)單的 Python 函數(shù)(或者說(shuō)方法,,如果是在基于類(lèi)的視圖里的話(huà)),。Django 將會(huì)根據(jù)用戶(hù)請(qǐng)求的 URL 來(lái)選擇使用哪個(gè)視圖(更準(zhǔn)確的說(shuō),是根據(jù) URL 中域名之后的部分),。
在你上網(wǎng)的過(guò)程中,,很可能看見(jiàn)過(guò)像這樣美麗的 URL: "ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B" 。別擔(dān)心,,Django 里的 URL 規(guī)則 要比這優(yōu)雅的多,!
一個(gè) URL 模式定義了某種 URL 的基本格式——舉個(gè)例子:/newsarchive/<year>/<month>/ 。
為了將 URL 和視圖關(guān)聯(lián)起來(lái),,Django 使用了 'URLconfs' 來(lái)配置,。URLconf 將 URL 模式映射到視圖。
本教程只會(huì)介紹 URLconf 的基礎(chǔ)內(nèi)容,,你可以看看 URL調(diào)度器 以獲取更多內(nèi)容,。
編寫(xiě)更多視圖?
現(xiàn)在讓我們向 polls/views.py 里添加更多視圖。這些視圖有一些不同,,因?yàn)樗麄兘邮諈?shù):
def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
把這些新視圖添加進(jìn) polls.urls 模塊里,,只要添加幾個(gè) url() 函數(shù)調(diào)用就行:
from django.urls import path
from . import views
urlpatterns = [
# ex: /polls/
path('', views.index, name='index'),
# ex: /polls/5/
path('<int:question_id>/', views.detail, name='detail'),
# ex: /polls/5/results/
path('<int:question_id>/results/', views.results, name='results'),
# ex: /polls/5/vote/
path('<int:question_id>/vote/', views.vote, name='vote'),
]
然后看看你的瀏覽器,如果你轉(zhuǎn)到 "/polls/34/" ,,Django 將會(huì)運(yùn)行 detail() 方法并且展示你在 URL 里提供的問(wèn)題 ID,。再試試 "/polls/34/vote/" 和 "/polls/34/vote/" ——你將會(huì)看到暫時(shí)用于占位的結(jié)果和投票頁(yè)。
當(dāng)某人請(qǐng)求你網(wǎng)站的某一頁(yè)面時(shí)——比如說(shuō), "/polls/34/" ,,Django 將會(huì)載入 mysite.urls 模塊,,因?yàn)檫@在配置項(xiàng) ROOT_URLCONF 中設(shè)置了。然后 Django 尋找名為 urlpatterns 變量并且按序匹配正則表達(dá)式,。在找到匹配項(xiàng) 'polls/' ,,它切掉了匹配的文本("polls/" ),將剩余文本——"34/" ,,發(fā)送至 'polls.urls' URLconf 做進(jìn)一步處理,。在這里剩余文本匹配了 '<int:question_id>/' ,使得我們 Django 以如下形式調(diào)用 detail() :
detail(request=<HttpRequest object>, question_id=34)
question_id=34 由 <int:question_id> 匹配生成,。使用尖括號(hào)“捕獲”這部分 URL,,且以關(guān)鍵字參數(shù)的形式發(fā)送給視圖函數(shù)。上述字符串的 :question_id> 部分定義了將被用于區(qū)分匹配模式的變量名,,而 int: 則是一個(gè)轉(zhuǎn)換器決定了應(yīng)該以什么變量類(lèi)型匹配這部分的 URL 路徑,。
為每個(gè) URL 加上不必要的東西,例如 .html ,,是沒(méi)有必要的,。不過(guò)如果你非要加的話(huà),也是可以的:
path('polls/latest.html', views.index),
但是,,別這樣做,,這太傻了。
寫(xiě)一個(gè)真正有用的視圖?
每個(gè)視圖必須要做的只有兩件事:返回一個(gè)包含被請(qǐng)求頁(yè)面內(nèi)容的 HttpResponse 對(duì)象,,或者拋出一個(gè)異常,,比如 Http404 。至于你還想干些什么,,隨便你,。
你的視圖可以從數(shù)據(jù)庫(kù)里讀取記錄,可以使用一個(gè)模板引擎(比如 Django 自帶的,,或者其他第三方的),,可以生成一個(gè) PDF 文件,可以輸出一個(gè) XML,,創(chuàng)建一個(gè) ZIP 文件,,你可以做任何你想做的事,使用任何你想用的 Python 庫(kù),。
Django 只要求返回的是一個(gè) HttpResponse ,,或者拋出一個(gè)異常。
因?yàn)?Django 自帶的數(shù)據(jù)庫(kù) API 很方便,,我們?cè)?教程第 2 部分 中學(xué)過(guò),,所以我們?cè)囋囋谝晥D里使用它,。我們?cè)?index() 函數(shù)里插入了一些新內(nèi)容,讓它能展示數(shù)據(jù)庫(kù)里以發(fā)布日期排序的最近 5 個(gè)投票問(wèn)題,,以空格分割:
from django.http import HttpResponse
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
output = ', '.join([q.question_text for q in latest_question_list])
return HttpResponse(output)
# Leave the rest of the views (detail, results, vote) unchanged
這里有個(gè)問(wèn)題:頁(yè)面的設(shè)計(jì)寫(xiě)死在視圖函數(shù)的代碼里的,。如果你想改變頁(yè)面的樣子,你需要編輯 Python 代碼,。所以讓我們使用 Django 的模板系統(tǒng),,只要?jiǎng)?chuàng)建一個(gè)視圖,就可以將頁(yè)面的設(shè)計(jì)從代碼中分離出來(lái),。
首先,,在你的 polls 目錄里創(chuàng)建一個(gè) templates 目錄。Django 將會(huì)在這個(gè)目錄里查找模板文件,。
你項(xiàng)目的 TEMPLATES 配置項(xiàng)描述了 Django 如何載入和渲染模板,。默認(rèn)的設(shè)置文件設(shè)置了 DjangoTemplates 后端,并將 APP_DIRS 設(shè)置成了 True,。這一選項(xiàng)將會(huì)讓 DjangoTemplates 在每個(gè) INSTALLED_APPS 文件夾中尋找 "templates" 子目錄,。這就是為什么盡管我們沒(méi)有像在第二部分中那樣修改 DIRS 設(shè)置,Django 也能正確找到 polls 的模板位置的原因,。
在你剛剛創(chuàng)建的 templates 目錄里,,再創(chuàng)建一個(gè)目錄 polls ,然后在其中新建一個(gè)文件 index.html ,。換句話(huà)說(shuō),,你的模板文件的路徑應(yīng)該是 polls/templates/polls/index.html ,。因?yàn)?Django 會(huì)尋找到對(duì)應(yīng)的 app_directories ,,所以你只需要使用 polls/index.html 就可以引用到這一模板了。
模板命名空間
雖然我們現(xiàn)在可以將模板文件直接放在 polls/templates 文件夾中(而不是再建立一個(gè) polls 子文件夾),,但是這樣做不太好,。Django 將會(huì)選擇第一個(gè)匹配的模板文件,如果你有一個(gè)模板文件正好和另一個(gè)應(yīng)用中的某個(gè)模板文件重名,,Django 沒(méi)有辦法 區(qū)分 它們,。我們需要幫助 Django 選擇正確的模板,最簡(jiǎn)單的方法就是把他們放入各自的 命名空間 中,,也就是把這些模板放入一個(gè)和 自身 應(yīng)用重名的子文件夾里,。
將下面的代碼輸入到剛剛創(chuàng)建的模板文件中:
polls/templates/polls/index.html ?
{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
然后,讓我們更新一下 polls/views.py 里的 index 視圖來(lái)使用模板:
from django.http import HttpResponse
from django.template import loader
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
template = loader.get_template('polls/index.html')
context = {
'latest_question_list': latest_question_list,
}
return HttpResponse(template.render(context, request))
上述代碼的作用是,,載入 polls/index.html 模板文件,,并且向它傳遞一個(gè)上下文(context)。這個(gè)上下文是一個(gè)字典,,它將模板內(nèi)的變量映射為 Python 對(duì)象,。
用你的瀏覽器訪問(wèn) "/polls/" ,,你將會(huì)看見(jiàn)一個(gè)無(wú)序列表,列出了我們?cè)? 教程第 2 部分 中添加的 “What's up” 投票問(wèn)題,,鏈接指向這個(gè)投票的詳情頁(yè),。
「載入模板,填充上下文,,再返回由它生成的 HttpResponse 對(duì)象」是一個(gè)非常常用的操作流程,。于是 Django 提供了一個(gè)快捷函數(shù),我們用它來(lái)重寫(xiě) index() 視圖:
from django.shortcuts import render
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
context = {'latest_question_list': latest_question_list}
return render(request, 'polls/index.html', context)
注意到,,我們不再需要導(dǎo)入 loader 和 HttpResponse ,。不過(guò)如果你還有其他函數(shù)(比如說(shuō) detail , results , 和 vote )需要用到它的話(huà),就需要保持 HttpResponse 的導(dǎo)入,。
The render() function takes the request object as its
first argument, a template name as its second argument and a dictionary as its
optional third argument. It returns an HttpResponse
object of the given template rendered with the given context.
拋出 404 錯(cuò)誤?
現(xiàn)在,,我們來(lái)處理投票詳情視圖——它會(huì)顯示指定投票的問(wèn)題標(biāo)題。下面是這個(gè)視圖的代碼:
from django.http import Http404
from django.shortcuts import render
from .models import Question
# ...
def detail(request, question_id):
try:
question = Question.objects.get(pk=question_id)
except Question.DoesNotExist:
raise Http404("Question does not exist")
return render(request, 'polls/detail.html', {'question': question})
這里有個(gè)新原則,。如果指定問(wèn)題 ID 所對(duì)應(yīng)的問(wèn)題不存在,,這個(gè)視圖就會(huì)拋出一個(gè) Http404 異常。
我們稍后再討論你需要在 polls/detail.html 里輸入什么,,但是如果你想試試上面這段代碼是否正常工作的話(huà),,你可以暫時(shí)把下面這段輸進(jìn)去:
polls/templates/polls/detail.html ?
這樣你就能測(cè)試了。
嘗試用 get() 函數(shù)獲取一個(gè)對(duì)象,,如果不存在就拋出 Http404 錯(cuò)誤也是一個(gè)普遍的流程,。Django 也提供了一個(gè)快捷函數(shù),下面是修改后的詳情 detail() 視圖代碼:
from django.shortcuts import get_object_or_404, render
from .models import Question
# ...
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
The get_object_or_404() function takes a Django model
as its first argument and an arbitrary number of keyword arguments, which it
passes to the get() function of the
model's manager. It raises Http404 if the object doesn't
exist.
也有 get_list_or_404() 函數(shù),,工作原理和 get_object_or_404() 一樣,,除了 get() 函數(shù)被換成了 filter() 函數(shù)。如果列表為空的話(huà)會(huì)拋出 Http404 異常,。
使用模板系統(tǒng)?
回過(guò)頭去看看我們的 detail() 視圖,。它向模板傳遞了上下文變量 question 。下面是 polls/detail.html 模板里正式的代碼:
polls/templates/polls/detail.html ?
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
模板系統(tǒng)統(tǒng)一使用點(diǎn)符號(hào)來(lái)訪問(wèn)變量的屬性,。在示例 {{ question.question_text }} 中,,首先 Django 嘗試對(duì) question 對(duì)象使用字典查找(也就是使用 obj.get(str) 操作),如果失敗了就嘗試屬性查找(也就是 obj.str 操作),,結(jié)果是成功了,。如果這一操作也失敗的話(huà),,將會(huì)嘗試列表查找(也就是 obj[int] 操作)。
在 {% for %} 循環(huán)中發(fā)生的函數(shù)調(diào)用:question.choice_set.all 被解釋為 Python 代碼 question.choice_set.all() ,,將會(huì)返回一個(gè)可迭代的 Choice 對(duì)象,,這一對(duì)象可以在 {% for %} 標(biāo)簽內(nèi)部使用。
查看 模板指南 可以了解關(guān)于模板的更多信息,。
去除模板中的硬編碼 URL?
還記得嗎,,我們?cè)?polls/index.html 里編寫(xiě)投票鏈接時(shí),鏈接是硬編碼的:
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
問(wèn)題在于,,硬編碼和強(qiáng)耦合的鏈接,,對(duì)于一個(gè)包含很多應(yīng)用的項(xiàng)目來(lái)說(shuō),修改起來(lái)是十分困難的,。然而,,因?yàn)槟阍?polls.urls 的 url() 函數(shù)中通過(guò) name 參數(shù)為 URL 定義了名字,你可以使用 {% url %} 標(biāo)簽代替它:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
這個(gè)標(biāo)簽的工作方式是在 polls.urls 模塊的 URL 定義中尋具有指定名字的條目,。你可以回憶一下,,具有名字 'detail' 的 URL 是在如下語(yǔ)句中定義的:
...
# the 'name' value as called by the {% url %} template tag
path('<int:question_id>/', views.detail, name='detail'),
...
如果你想改變投票詳情視圖的 URL,比如想改成 polls/specifics/12/ ,,你不用在模板里修改任何東西(包括其它模板),,只要在 polls/urls.py 里稍微修改一下就行:
...
# added the word 'specifics'
path('specifics/<int:question_id>/', views.detail, name='detail'),
...
為 URL 名稱(chēng)添加命名空間?
教程項(xiàng)目只有一個(gè)應(yīng)用,polls ,。在一個(gè)真實(shí)的 Django 項(xiàng)目中,,可能會(huì)有五個(gè),十個(gè),,二十個(gè),,甚至更多應(yīng)用。Django 如何分辨重名的 URL 呢,?舉個(gè)例子,,polls 應(yīng)用有 detail 視圖,,可能另一個(gè)博客應(yīng)用也有同名的視圖,。Django 如何知道 {% url %} 標(biāo)簽到底對(duì)應(yīng)哪一個(gè)應(yīng)用的 URL 呢?
答案是:在根 URLconf 中添加命名空間,。在 polls/urls.py 文件中稍作修改,,加上 app_name 設(shè)置命名空間:
from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/results/', views.results, name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
]
現(xiàn)在,編輯 polls/index.html 文件,,從:
polls/templates/polls/index.html ?
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
修改為指向具有命名空間的詳細(xì)視圖:
polls/templates/polls/index.html ?
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
當(dāng)你對(duì)你寫(xiě)的視圖感到滿(mǎn)意后,,請(qǐng)閱讀 教程的第 4 部分 了解簡(jiǎn)單的表單處理和通用視圖。
|