上一節(jié): Django 官網(wǎng)最新 Tutorial 渣翻 - Part 2
手寫第一個(gè)Django應(yīng)用, 第二部分
緊接著Tutorial 2 ,我們繼續(xù)開發(fā)投票這個(gè)web應(yīng)用,并將注意力集中在創(chuàng)建對外訪問的“視圖”界面上脓杉。
概覽
視圖(view)是Django應(yīng)用中的一“類”網(wǎng)頁,它通常有一個(gè)特定的函數(shù)以及一個(gè)特定的模板。例如,在博客應(yīng)用中芦昔,可能有以下視圖:
- 博客首頁 —— 顯示最新發(fā)表的文章鏈接。
- 博客“詳細(xì)”頁面 —— 單篇文章的詳細(xì)頁面娃肿。
- 基于年份的歸檔頁面 —— 顯示某給定年份里所有月份發(fā)表過的博客咕缎。
- 基于月份的歸檔頁面 —— 顯示在給定月份中發(fā)表過所有文章珠十。
- 基于日期的歸檔頁面 —— 顯示在給定日期中發(fā)表過的所有文章鏈接。
- 評論 —— 評論某博客
在我們的投票應(yīng)用中凭豪,將有以下四個(gè)視圖:
- Question首頁 —— 顯示最新發(fā)布的幾個(gè)Question宵睦。
- Question“詳細(xì)”頁面 —— 顯示單個(gè)Question的具體內(nèi)容,有一個(gè)投票的表單墅诡,但沒有投票結(jié)果壳嚎。
- Question“結(jié)果”頁面 —— 顯示某Question的投票結(jié)果。
- 投票功能 —— 可對Question中某個(gè)Choice的進(jìn)行投票末早。
在Django中烟馅,網(wǎng)頁的頁面和其他內(nèi)容都是由視圖來傳遞的.每個(gè)視圖都是由一個(gè)簡單的Python函數(shù)(或者是基于類的視圖的方法)。Django通過檢查請求的URL(準(zhǔn)確地說然磷,是URL里域名之后的那部分)來選擇使用哪個(gè)視圖郑趁。
平日你上網(wǎng)時(shí),可能會(huì)遇到像 “ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B”這樣優(yōu)美的URL姿搜。 你將會(huì)愉快地了解到寡润,Django允許我們使用更加優(yōu)雅的URL模式。
URL模式(URL pattern)就是一個(gè)URL的通用形式 —— 例如: /newsarchive/<year>/<month>/
.
Django使用叫做‘URLconfs’的配置來為URL匹配視圖舅柜。 一個(gè)URLconf負(fù)責(zé)將URL模式匹配到視圖梭纹。
本教程有URLconfs的基本使用方法,你可以在 URL dispatcher看到更詳細(xì)的信息 致份。
編寫更多的視圖
現(xiàn)在讓我們給polls/views.py添加一些更多的視圖变抽。這些視圖和之前的略有不同,因?yàn)樗鼈兞韼Я艘粋€(gè)參數(shù):
polls/views.py
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)
通過下面的path()
調(diào)用將這些新的視圖和polls.urls模塊關(guān)聯(lián)起來:
polls/urls.py
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'),
]
看看你的瀏覽器氮块,輸入“/polls/34/”, 它將運(yùn)行detail()方法并顯示你在URL中提供的ID绍载。 再試一下“/polls/34/results/”和“/polls/34/vote/” —— 它們將顯示出對應(yīng)的結(jié)果界面和投票界面。
當(dāng)有人請求你的網(wǎng)站的一個(gè)頁面時(shí) —— 比如“/polls/34/”滔蝉,Django 將加載 python 模塊 mysite.urls
, 因?yàn)樵O(shè)置文件中的ROOT_URLCONF
指定了他, 他會(huì)找變量名為urlpatterns
并按順序遍歷他, 當(dāng)找到匹配的'polls/'
后, 它會(huì)截?cái)嗥ヅ涞降淖址?"polls/"
), 然后發(fā)送剩余的字符串 --- "34/"
給 ‘polls.urls’ 這個(gè)URLconf作進(jìn)一步處理. 這里他僅匹配到了 '<int:question_id>/'
, 這樣一來他就會(huì)像這樣一樣調(diào)用 detail()
:
detail(request=<HttpRequest object>, question_id=34)
question_id=34部分來自<int:question_id>, 使用尖括號(hào)"捕獲"的URL, 并將他(尖括號(hào)中的內(nèi)容)作為關(guān)鍵字參數(shù)傳入視圖函數(shù)中, question_id>
, 部分的內(nèi)容當(dāng)作匹配的標(biāo)識(shí), <int
來決定怎么匹配.
我們不需要添加那些不像url的東西, 如 .html
- 除非你想這么做, 這種情況下你可以這樣弄:
path('polls/latest.html', views.index),
不過這真是一種**(文明你我他)的行為: )
寫點(diǎn)有意義的視圖
每個(gè)視圖都負(fù)責(zé)一兩件事, 返回一個(gè)包含整個(gè)請求頁面內(nèi)容的HttpResponse
對象, 或者拋出一個(gè)像 Http404
的異常, 這取決于你.
你的視圖可以是從數(shù)據(jù)庫讀取記錄, 或不讀取數(shù)據(jù)庫, 你能用Django自帶的模板系統(tǒng), 或第三方的模板系統(tǒng), 甚至你也可以不用模板, 你還可以動(dòng)態(tài)的生成一個(gè)PDF文件, 輸出XML文件, 創(chuàng)建一個(gè)ZIP文件击儡,使用你想用的Python 庫生成任何你想要的。
Django只要求返回一個(gè)HttpResponse
.或拋出異常.
讓我們來用用Tutorial 2學(xué)到的數(shù)據(jù)庫API蝠引,它是非常方便的阳谍。下面是一個(gè)新的index()視圖,它列出了最近的5個(gè)投票Question記錄立肘,并用逗號(hào)隔開边坤,以發(fā)布日期排序:
polls/views.py
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è)問題:頁面的設(shè)計(jì)被硬編碼在視圖中。 如果你想更改頁面的外觀谅年,就得編輯這段Python代碼。 因此肮韧,讓我們使用Django的模板系統(tǒng)融蹂,通過創(chuàng)建一個(gè)視圖能夠調(diào)用的模板旺订,將頁面的html代碼從Python中分離出來。
首先超燃,在你的polls目錄下創(chuàng)建一個(gè)叫做 templates的目錄区拳。Django將在這里查找模板。
你項(xiàng)目配置文件中的 TEMPLATES
決定了Django如何加載和渲染模板意乓。默認(rèn)配置下樱调,Django模板引擎在 APP_DIRS
中的設(shè)置為True。Django模板引擎會(huì)在INSTALLED_APPS
里的各個(gè)APP目錄下查找名為templates的子目錄届良。
在你剛剛創(chuàng)建的templates目錄中笆凌,創(chuàng)建另外一個(gè)目錄polls,并在其中創(chuàng)建一個(gè)文件index.html士葫。也就是說乞而,你的模板應(yīng)該位于 polls/templates/polls/index.html。由于app_directories 模板加載器按照上面描述的方式工作慢显,在Django中你可以簡單地用polls/index.html引用這個(gè)模板爪模。
模板命名空間
其實(shí)我們可以直接將我們的模板放在polls/templates中(而不用創(chuàng)建另外一個(gè)polls子目錄),但實(shí)際上這是個(gè)壞主意荚藻。Django將選擇它找到的名字匹配的第一個(gè)模板文件屋灌,如果你在不同的應(yīng)用有相同名字的模板文件,Django將不能區(qū)分它們应狱。我們需要將Django指向正確的模板声滥,最簡單的方式是使用命名空間。具體實(shí)現(xiàn)方式是侦香,將這些模板文件放在以應(yīng)用的名字來命名的另一個(gè)目錄下落塑。
將以下代碼寫入剛創(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 %}
Now let’s update our index view in polls/views.py to use the template:
現(xiàn)在我們用剛剛的模板來更新polls/views.py的視圖函數(shù):
polls/views.py
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對象罐韩,context是一個(gè)字典憾赁, 將模板的變量和python對象一一對應(yīng)。
瀏覽器訪問“/polls”散吵,你看到一個(gè)列表龙考,包含了我們在Tutorial 2創(chuàng)建的 “What’s up” question,這個(gè)鏈接指向了Question的詳細(xì)頁.
一種快捷方式: render()
加載模板矾睦、填充一個(gè)context 然后返回一個(gè)含有模板渲染結(jié)果的HttpResponse
對象是非常頻繁的晦款。Django為此提供一個(gè)快捷方式。下面是重寫后的index()
視圖:
polls/views.py
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)
注意枚冗,一旦我們在所有的視圖上都應(yīng)用這個(gè)快捷函數(shù)缓溅,我們將不再需要導(dǎo)入loader
和 HttpResponse
(如果你沒有改變先前的detail、results和 vote方法赁温,你將需要在導(dǎo)入中保留HttpResponse )坛怪。
render()
函數(shù)將請求對象(request)作為它的第一個(gè)參數(shù)淤齐,模板的名字作為它的第二個(gè)參數(shù),一個(gè)字典作為它可選的第三個(gè)參數(shù)袜匿。 它返回一個(gè) HttpResponse
對象更啄,含有用給定的context 渲染后的模板。
拋出一個(gè)404錯(cuò)誤
現(xiàn)在讓我們來處理question detail視圖——顯示某question內(nèi)容的頁面居灯,下面是該視圖:
polls/views.py
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è)視圖拋出了Http404
異常祭务,如果請求的question ID不存在的情況下。
稍后我們將討論polls/detail.html模板可以寫點(diǎn)什么怪嫌。但是如果你想快速讓上面的例子工作义锥,如下就可以:
polls/templates/polls/detail.html
{{ question }}
快捷方法: get_object_or_404
當(dāng)用 get()
時(shí),如果對象不存在拋出 Http404
異常是很常用的喇勋。Django提供了一個(gè)快捷方式缨该,重寫后的detail()視圖:
polls/views.py
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})
get_object_or_404()
函數(shù)將Django模型作為它的第一個(gè)參數(shù),任意數(shù)量關(guān)鍵詞參數(shù)川背,它將傳遞給作為模型管理器的 get()
函數(shù)贰拿,如果對象不存在,它就引發(fā)一個(gè) Http404
異常熄云。
哲學(xué)
為什么我們要用一個(gè)輔助函數(shù)get_object_or_404()
而不是在上層捕獲ObjectDoesNotExist
異常膨更,或者讓模型的API引發(fā)Http404
而不是ObjectDoesNotExist
因?yàn)槟菢訒?huì)將模型層與視圖層耦合。Django 最重要的設(shè)計(jì)目標(biāo)就是保持松耦合缴允。一些可控的耦合在django.shortcuts
有介紹荚守。
還有一個(gè) get_list_or_404()
函數(shù), 原理和get_object_or_404()
一樣——差別在與它用filter()
而不是 get()
. 當(dāng)列表為空時(shí),它拋出 Http404
異常练般。
使用模板系統(tǒng)
回到我們投票應(yīng)用的detail()
視圖矗漾。 根據(jù)context 變量question,下面是polls/detail.html模板可能的樣子:
polls/templates/polls/detail.html
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
模板系統(tǒng)使用點(diǎn)號(hào)查找語法來訪問變量的屬性薄料。在例子 {{ question.question_text }}
中, Django首先對question對象做字典查詢敞贡。如果失敗,Django會(huì)接著嘗試按屬性查詢 —— 在這個(gè)例子中摄职,屬性查詢會(huì)成功誊役。如果屬性查詢也失敗,Django將嘗試列表索引查詢谷市。
方法調(diào)用發(fā)生在 {% for %}
循環(huán)中: question.choice_set.all
被解釋為Python的代碼question.choice_set.all()
蛔垢,它返回一個(gè)由Choice
對象組成的可迭代對象,并將其用于{% for %}
標(biāo)簽迫悠。
查看 template guide 了解更多模板信息.
去除模板中的硬編碼
請記住鹏漆,當(dāng)我們在polls/index.html
模板中編寫一個(gè)指向Question的鏈接時(shí),鏈接中一部分是硬編碼的:
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
這種緊耦合的硬編碼有一個(gè)問題,就是如果我們想在模板眾多的項(xiàng)目中修改URLs甫男,將會(huì)變得非常困難且改。 但是验烧,如果你在polls.urls模塊的 path()
函數(shù)中定義了name 參數(shù)板驳,你可以通過使用 {% url %}
模板標(biāo)簽來移除對你的URL配置中定義的特定的URL的依賴:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
它的工作原理是在 polls.urls
模塊里查找指定的URL。你可以看到名為‘detail’的URL的準(zhǔn)確定義:
...
# {% url %} 模板Tag調(diào)用了'name' 的值
path('<int:question_id>/', views.detail, name='detail'),
...
如果你想把polls應(yīng)用中detail視圖的URL改成其它樣子比如 polls/specifics/12/
碍拆,就可以不必在該模板(或者多個(gè)模板)中修改它若治,只需要修改 polls/urls.py
:
...
# added the word 'specifics'
path('specifics/<int:question_id>/', views.detail, name='detail'),
...
URL命名空間
教程中的這個(gè)項(xiàng)目只有一個(gè)應(yīng)用polls。在真實(shí)的Django項(xiàng)目中感混,可能會(huì)有五個(gè)端幼、十個(gè)、二十個(gè)或者更多的應(yīng)用弧满。 Django如何區(qū)分它們URL的名字呢婆跑? 例如,polls
應(yīng)用具有一個(gè)detail 視圖庭呜,相同項(xiàng)目中的博客應(yīng)用可能也有這樣一個(gè)視圖滑进。當(dāng)使用模板標(biāo)簽{% url %}
時(shí),人們該如何做才能使得Django知道為一個(gè)URL創(chuàng)建哪個(gè)應(yīng)用的視圖募谎?
答案是在你的主URLconf下添加命名空間扶关。 在 polls/urls.py
文件中,增加一個(gè)app_name
的變量作為命名空間:
polls/urls.py
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 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
當(dāng)你對你寫的視圖感到滿意后数冬,請閱讀 part 4 of this tutorial來了解簡單的表單處理和通用視圖节槐。
下一節(jié): Django 官網(wǎng)最新 Tutorial 渣翻 - Part 3