编写你的第一个 Django 应用,第 3 部分
Hello,我是 Alex 007,一個熱愛計算機編程和硬件設計的小白,為啥是007呢?因為叫 Alex 的人太多了,再加上每天007的生活,Alex 007就誕生了。
Django視圖+模板系統+URL命名空間
我們將繼續編寫投票應用,并且專注于如何創建公用界面——也被稱為“視圖”。
上一節:編寫你的第一個 Django 應用,第 2 部分
概況
Django 中的視圖的概念是「一類具有相同功能和模板的網頁的集合」。比如,在一個博客應用中,你可能會創建如下幾個視圖:
- 博客首頁——展示最近的幾項內容。
- 內容“詳情”頁——詳細展示某項內容。
- 以年為單位的歸檔頁——展示選中的年份里各個月份創建的內容。
- 以月為單位的歸檔頁——展示選中的月份里各天創建的內容。
- 以天為單位的歸檔頁——展示選中天里創建的所有內容。
- 評論處理器——用于響應為一項內容添加評論的操作。
而在我們的投票應用中,我們需要下列幾個視圖:
- 問題索引頁——展示最近的幾個投票問題。
- 問題詳情頁——展示某個投票的問題和不帶結果的選項列表。
- 問題結果頁——展示某個投票的結果。
- 投票處理器——用于響應用戶為某個問題的特定選項投票的操作。
在 Django 中,網頁和其他內容都是從視圖派生而來。每一個視圖表現為一個 Python 函數(或者說方法,如果是在基于類的視圖里的話)。Django 將會根據用戶請求的 URL 來選擇使用哪個視圖(更準確的說,是根據 URL 中域名之后的部分)。
URL模式是URL的一般形式 - 例如: /newsarchive/<year>/<month>/.
為了將 URL 和視圖關聯起來,Django 使用了 ‘URLconfs’ 來配置。URLconf 將 URL 模式映射到視圖。
本文只會介紹 URLconf 的基礎內容,你可以看看 URL調度器 以獲取更多內容。
編寫更多視圖
現在讓我們向 polls/views.py 里添加更多視圖。這些視圖有一些不同,因為他們接收參數:
def detail(request, question_id):return HttpResponse("You're looking at question %s." % question_id)def results(request, question_id):return HttpResponse("You're looking at the results of question %s." % question_id)def vote(request, question_id):return HttpResponse("You're voting on question %s." % question_id)把這些新視圖添加進 polls.urls 模塊里,只要添加幾個 url() 函數調用就行:
from django.urls import pathfrom . import viewsurlpatterns = [# 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/” ,Django 將會運行 detail() 方法并且展示你在 URL 里提供的問題 ID。再試試 “/polls/34/vote/” 和 “/polls/34/vote/” ——你將會看到暫時用于占位的結果和投票頁。
當某人請求你網站的某一頁面時——比如說, “/polls/34/” ,Django 將會載入 mysite.urls 模塊,因為這在配置項 ROOT_URLCONF 中設置了。然后 Django 尋找名為 urlpatterns 變量并且按序匹配正則表達式。在找到匹配項 ‘polls/’,它切掉了匹配的文本(“polls/”),將剩余文本——“34/”,發送至 ‘polls.urls’ URLconf 做進一步處理。在這里剩余文本匹配了 ‘<int:question_id>/’,使得我們 Django 以如下形式調用 detail():
detail(request=<HttpRequest object>, question_id=34)question_id=34 由 <int:question_id> 匹配生成。使用尖括號“捕獲”這部分 URL,且以關鍵字參數的形式發送給視圖函數。上述字符串的 :question_id> 部分定義了將被用于區分匹配模式的變量名,而 int: 則是一個轉換器決定了應該以什么變量類型匹配這部分的 URL 路徑。
寫一個真正有用的視圖
每個視圖必須要做的只有兩件事:返回一個包含被請求頁面內容的 HttpResponse 對象,或者拋出一個異常,比如 Http404 。至于你還想干些什么,隨便你。
你的視圖可以從數據庫里讀取記錄,可以使用一個模板引擎(比如 Django 自帶的,或者其他第三方的),可以生成一個 PDF 文件,可以輸出一個 XML,創建一個 ZIP 文件,你可以做任何你想做的事,使用任何你想用的 Python 庫。
Django 只要求返回的是一個 HttpResponse ,或者拋出一個異常。
因為 Django 自帶的數據庫 API 很方便,我們曾在 第 2 部分 中學過,所以我們試試在視圖里使用它。我們在 index() 函數里插入了一些新內容,讓它能展示數據庫里以發布日期排序的最近 5 個投票問題,以空格分割:
from django.http import HttpResponsefrom .models import Questiondef 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)這里有個問題:頁面的設計寫死在視圖函數的代碼里的。如果你想改變頁面的樣子,你需要編輯 Python 代碼。所以讓我們使用 Django 的模板系統,只要創建一個視圖,就可以將頁面的設計從代碼中分離出來。
首先,在你的 polls 目錄里創建一個 templates 目錄。Django 將會在這個目錄里查找模板文件。
你項目的 TEMPLATES 配置項描述了 Django 如何載入和渲染模板。默認的設置文件設置了 DjangoTemplates 后端,并將 APP_DIRS 設置成了 True。這一選項將會讓 DjangoTemplates 在每個 INSTALLED_APPS 文件夾中尋找 “templates” 子目錄。這就是為什么盡管我們沒有像在第二部分中那樣修改 DIRS 設置,Django 也能正確找到 polls 的模板位置的原因。
在你剛剛創建的 templates 目錄里,再創建一個目錄 polls,然后在其中新建一個文件 index.html 。換句話說,你的模板文件的路徑應該是 polls/templates/polls/index.html 。因為app_directories 模板加載器是通過上述描述的方法運行的,所以Django可以引用到 polls/index.html 這一模板了。
模板命名空間
雖然我們現在可以將模板文件直接放在 polls/templates 文件夾中(而不是再建立一個 polls 子文件夾),但是這樣做不太好。Django 將會選擇第一個匹配的模板文件,如果你有一個模板文件正好和另一個應用中的某個模板文件重名,Django 沒有辦法 區分 它們。我們需要幫助 Django 選擇正確的模板,最好的方法就是把他們放入各自的 命名空間 中,也就是把這些模板放入一個和 自身 應用重名的子文件夾里。
將下面的代碼輸入到剛剛創建的模板文件中:
{% 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 %}「載入模板,填充上下文,再返回由它生成的 HttpResponse 對象」是一個非常常用的操作流程。于是 Django 提供了一個快捷函數,我們用它來更新 一下 polls/views.py 里的 index 視圖來使用模板:
from django.shortcuts import renderfrom .models import Questiondef 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)render()函數的第一個參數是請求對象,第二個參數是模板名稱,第三個參數是字典。
render()作用是,載入 polls/index.html 模板文件,并且向它傳遞一個上下文(context)。這個上下文是一個字典,它將模板內的變量映射為 Python 對象。最后返回使用給定上下文呈現的給定模板的HttpResponse對象。
用你的瀏覽器訪問 “/polls/” ,你將會看見一個無序列表,列出了我們在 第 2 部分 中添加的 “What’s up” 投票問題,鏈接指向這個投票的詳情頁。
拋出 404 錯誤
現在,我們來處理投票詳情視圖——它會顯示指定投票的問題標題。下面是這個視圖的代碼:
from django.http import Http404 from django.shortcuts import renderfrom .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})這里有個新原則。如果指定問題 ID 所對應的問題不存在,這個視圖就會拋出一個 Http404 異常。
我們稍后再討論你需要在 polls/detail.html 里輸入什么,但是如果你想試試上面這段代碼是否正常工作的話,你可以暫時把下面這段輸進去:
{{ question }}這樣你就能測試了。
一個快捷函數: get_object_or_404()
嘗試用 get() 函數獲取一個對象,如果不存在就拋出 Http404 錯誤也是一個普遍的流程。Django 也提供了一個快捷函數,下面是修改后的詳情 detail() 視圖代碼:
from django.shortcuts import get_object_or_404, renderfrom .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() 函數將Django模型作為其第一個參數,并將任意數量的關鍵字參數傳遞給模型管理器的 get() 函數。如果對象不存在,它將引發Http404。
也有 get_list_or_404() 函數,工作原理和 get_object_or_404() 一樣,除了 get() 函數被換成了 filter() 函數。如果列表為空的話會拋出 Http404 異常。
使用模板系統
回過頭去看看我們的 detail() 視圖。它向模板傳遞了上下文變量 question 。下面是 polls/detail.html 模板里正式的代碼:
<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %}<li>{{ choice.choice_text }}</li> {% endfor %} </ul>模板系統統一使用點符號來訪問變量的屬性。在示例 {{ question.question_text }} 中,首先 Django 嘗試對 question 對象使用字典查找(也就是使用 obj.get(str) 操作),如果失敗了就嘗試屬性查找(也就是 obj.str 操作),結果是成功了。如果這一操作也失敗的話,將會嘗試列表查找(也就是 obj[int] 操作)。
在 {% for %} 循環中發生的函數調用:question.choice_set.all 被解釋為 Python 代碼 question.choice_set.all() ,將會返回一個可迭代的 Choice 對象,這一對象可以在 {% for %} 標簽內部使用。
去除模板中的硬編碼 URL
還記得嗎,我們在 polls/index.html 里編寫投票鏈接時,鏈接是硬編碼的:
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>問題在于,硬編碼和強耦合的鏈接,對于一個包含很多應用的項目來說,修改起來是十分困難的。然而,我們之前在 polls.urls 的 url() 函數中通過 name 參數為 URL 定義了名字,可以使用 {% url %} 標簽代替它:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>這個標簽的工作方式是在 polls.urls 模塊的 URL 定義中尋具有指定名字的條目。你可以回憶一下,具有名字 ‘detail’ 的 URL 是在如下語句中定義的:
# 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 名稱添加命名空間
教程項目只有一個應用,polls 。在一個真實的 Django 項目中,可能會有五個,十個,二十個,甚至更多應用。Django 如何分辨重名的 URL 呢?舉個例子,polls 應用有 detail 視圖,可能另一個博客應用也有同名的視圖。Django 如何知道 {% url %} 標簽到底對應哪一個應用的 URL 呢?
答案是:在根 URLconf 中添加命名空間。在 polls/urls.py 文件中稍作修改,加上 app_name 設置命名空間:
from django.urls import pathfrom . import viewsapp_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'), ]現在,編輯 polls/index.html 文件,修改為指向具有命名空間的詳細視圖:
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>關于視圖這方面的內容我們就講完了,下節課我們了解基礎的表單處理和通用視圖。
總結
以上是生活随笔為你收集整理的编写你的第一个 Django 应用,第 3 部分的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 120. Triangle 三角形最小路
- 下一篇: 96. Unique Binary Se