Django–03視圖和模板
Part 3: Views and templates
本教程承接第二部分,我們將繼續開發投票應用,重點介紹 Django 的表單處理和通用視圖。
文章目錄
- Django--03視圖和模板
- 前言
- 概述
- 一、編寫更多視圖
- 二、編寫實際執行操作的視圖
- 三、快捷方式:render ()
- 四、拋出 404 錯誤
- 五、快捷方式:get_object_or_404 ()
- 5.1 理念
- 5.2 使用模板系統
- 六、移除模板中的硬編碼 URL
- 七、URL 名稱的命名空間
前言
本系列博客內容全部在windows系統實現,本專欄以Django官方文檔為基礎,從零開始,對官方文檔翻譯+修改。例如,運行圖、效果圖添加,不好理解的地方詳細講解,適合0基礎學習。
它包含以下內容
Part 1: Requests and responses
Part 2: Models and the admin site
Part 3: Views and templates
Part 4: Forms and generic views
Part 5: Testing
Part 6: Static files
Part 7: Customizing the admin site
Part 8: Adding third-party packages
概述
視圖是 Django 應用中一種特定類型的網頁,通常用于實現特定功能并對應特定模板。例如,在博客應用中,可能包含以下視圖:
博客首頁 —— 顯示最新的幾篇文章
文章詳情頁 —— 單篇文章的永久鏈接頁
按年歸檔頁 —— 顯示指定年份中有文章的所有月份
按月歸檔頁 —— 顯示指定月份中有文章的所有日期
按日歸檔頁 —— 顯示指定日期的所有文章
評論功能 —— 處理對某篇文章的評論提交
在我們的投票應用中,將包含以下四個視圖:
問題列表頁 —— 顯示最新的幾個問題
問題詳情頁 —— 顯示問題文本,不含結果但包含一個投票表單
結果頁 —— 顯示特定問題的投票結果
投票功能 —— 處理對特定問題中某個選項的投票提交
在 Django 中,網頁和其他內容通過視圖來交付。每個視圖由一個 Python 函數(或類方法,對于基于類的視圖而言)表示。Django 通過分析請求的 URL(準確地說,是域名后的 URL 部分)來選擇對應的視圖。
你在上網時可能見過類似這樣復雜的 URL:ME2/Sites/dirmod.htm sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B
。值得慶幸的是,Django 允許我們使用更簡潔優雅的 URL 模式。
URL 模式是 URL 的通用形式,例如:/newsarchive/<year>/<month>/。
為了將 URL 映射到視圖,Django 使用所謂的 “URLconf”(URL 配置)。URLconf 將 URL 模式映射到視圖。
本教程將介紹 URLconf 的基本使用方法,更多信息可參考《URL 調度器》文檔。
《URL 調度器》文檔:https://docs.djangoproject.com/en/5.2/topics/http/urls/
一、編寫更多視圖
現在我們在 polls/views.py 中添加幾個視圖。這些視圖略有不同,因為它們需要接收參數:
# polls/views.py
def detail(request, question_id):return HttpResponse(f"你正在查看問題 {question_id}。")def results(request, question_id):response = "你正在查看問題 %s 的結果。"return HttpResponse(response % question_id)def vote(request, question_id):return HttpResponse(f"你正在為問題 {question_id} 投票。")
將這些新視圖添加到 polls/urls.py 模塊中,方法是添加以下 path() 調用:
# polls/urls.py
from django.urls import path
from . import viewsurlpatterns = [# 示例:/polls/path("", views.index, name="index"),# 示例:/polls/5/path("<int:question_id>/", views.detail, name="detail"),# 示例:/polls/5/results/path("<int:question_id>/results/", views.results, name="results"),# 示例:/polls/5/vote/path("<int:question_id>/vote/", views.vote, name="vote"),
]
在瀏覽器中查看 “/polls/34/”。它將運行 detail () 函數,并顯示你在 URL 中提供的任何 ID。也可以嘗試 “/polls/34/results/” 和 “/polls/34/vote/”—— 這些將顯示占位的結果頁和投票頁。
當有人請求你網站上的一個頁面時,例如 “/polls/34/”,Django 會加載 mysite.urls Python 模塊,因為它是由 ROOT_URLCONF 設置指定的。它找到名為 urlpatterns 的變量,并按順序遍歷其中的模式。在找到與 ‘polls/’ 匹配的模式后,它會剝離匹配的文本(“polls/”),并將剩余的文本 ——“34/”—— 發送到 ‘polls.urls’ URLconf 進行進一步處理。在那里,它與 ‘int:question_id/’ 匹配,結果如下所示調用 detail () 視圖:
detail(request=<HttpRequest object>, question_id=34)
question_id=34 部分來自 int:question_id。使用尖括號 “捕獲” URL 的一部分,并將其作為關鍵字參數發送到視圖函數。字符串的 question_id 部分定義了用于標識匹配模式的名稱,而 int 部分是一個轉換器,用于確定哪些模式應該匹配 URL 路徑的這一部分。冒號(:)分隔轉換器和模式名稱。
二、編寫實際執行操作的視圖
每個視圖負責做以下兩件事之一:
返回一個包含請求頁面內容的 HttpResponse 對象,或者引發一個異常,例如 Http404。其余的都由你決定。
你的視圖可以從數據庫讀取記錄,也可以不讀取。它可以使用 Django 的模板系統 —— 或者第三方 Python 模板系統 —— 或者不使用。它可以生成 PDF 文件、輸出 XML、動態創建 ZIP 文件,任何你想要的,使用任何你想要的 Python 庫。
Django 只要求 HttpResponse。或者一個異常。
為了方便起見,讓我們使用 Django 自己的數據庫 API,這是我們在教程 2 中介紹過的。下面是一個新的 index () 視圖,它根據發布日期顯示系統中最新的 5 個投票問題,用逗號分隔:
polls/views.py
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)# Leave the rest of the views (detail, results, vote) unchanged
不過,這里有一個問題:頁面的設計是硬編碼在視圖中的。如果你想改變頁面的外觀,你必須編輯這段 Python 代碼。所以讓我們使用 Django 的模板系統,通過創建一個視圖可以使用的模板,將設計與 Python 分離。
首先,在你的 polls 目錄中創建一個名為 templates 的目錄。Django 會在那里查找模板。
你項目的 TEMPLATES 設置描述了 Django 如何加載和渲染模板。默認的設置文件配置了一個 DjangoTemplates 后端,其 APP_DIRS 選項設置為 True。
按照慣例,DjangoTemplates 會在每個 INSTALLED_APPS 中查找一個 “templates” 子目錄。
在你剛剛創建的 templates 目錄中,再創建一個名為 polls 的目錄,并在其中創建一個名為 index.html 的文件。
換句話說,你的模板應該位于 polls/templates/polls/index.html。由于上面描述的 app_directories 模板加載器的工作方式,你可以在 Django 中將此模板稱為 polls/index.html。
模板命名空間
現在,我們可以將模板直接放在 polls/templates 中(而不是再創建另一個 polls 子目錄),但這實際上是一個壞主意。Django 會選擇它找到的第一個名稱匹配的模板,如果你在另一個應用中有一個同名的模板,Django 將無法區分它們。我們需要能夠向 Django 指出正確的那個,而確保這一點的最佳方法是對它們進行命名空間。也就是說,將這些模板放在另一個以應用本身命名的目錄中。
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 %}
注意
為了縮短教程,所有模板示例都使用不完整的 HTML。在你自己的項目中,你應該使用完整的 HTML 文檔。
這里我給出兩個方案
1.官方代碼:不影響使用,效果圖放在下面
2.補全代碼:較美觀
現在讓我們更新 polls/views.py 中的 index 視圖以使用該模板:
官方代碼polls/views.py
from django.http import HttpResponse
from django.template import loaderfrom .models import Questiondef 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/views.py
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>投票列表 | 我的 Django 應用</title><!-- 可選:添加基礎樣式 --><style>body {font-family: Arial, sans-serif;max-width: 800px;margin: 0 auto;padding: 20px;}h1 {color: #333;border-bottom: 2px solid #eee;padding-bottom: 10px;}ul {list-style: none;padding: 0;}li {margin: 10px 0;padding: 10px;background: #f9f9f9;border-radius: 4px;}a {color: #007bff;text-decoration: none;}a:hover {text-decoration: underline;}</style>
</head>
<body><h1>最新投票</h1>{% 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 %}
</body>
</html>
這段代碼加載名為 polls/index.html 的模板,并向其傳遞一個上下文。上下文是一個將模板變量名稱映射到 Python 對象的字典。
通過在瀏覽器中指向 “/polls/” 來加載頁面,你應該會看到一個項目符號列表,其中包含教程 2 中的 “What’s up” 問題。該鏈接指向問題的詳情頁。
官方代碼圖:
補全代碼圖:
三、快捷方式:render ()
加載模板、填充上下文并返回一個渲染了給定上下文的模板的 HttpResponse 對象是一個非常常見的做法。Django 提供了一個快捷方式。下面是重寫后的完整 index () 視圖:
polls/views.py
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)
請注意,一旦我們在所有這些視圖中都這樣做了,我們就不再需要導入 loader 和 HttpResponse 了(如果你仍然有 detail、results 和 vote 的存根方法,你會想要保留 HttpResponse)。
render () 函數將請求對象作為第一個參數,模板名稱作為第二個參數,字典作為可選的第三個參數。它返回一個給定模板用給定上下文渲染的 HttpResponse 對象。
修改后的polls\views.py
from django.http import HttpResponse
from django.shortcuts import render
from .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)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)# Create your views here.
四、拋出 404 錯誤
現在,讓我們處理問題詳情視圖 —— 顯示給定投票的問題文本的頁面。以下是視圖:
polls/views.py
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 模板中放入什么內容,但如果你想快速讓上面的示例工作,一個僅包含以下內容的文件:
polls/templates/polls/detail.html
{{ question }}
現在就可以讓你開始了。
五、快捷方式:get_object_or_404 ()
使用 get () 并在對象不存在時引發 Http404 是一個非常常見的做法。Django 提供了一個快捷方式。以下是重寫后的 detail () 視圖:
polls/views.py
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。
查看改函數具體定義:鼠標選中–>右鍵–>轉到定義
5.1 理念
為什么我們使用輔助函數 get_object_or_404 () 而不是在更高的級別自動捕獲 ObjectDoesNotExist 異常,或者讓模型 API 引發 Http404 而不是 ObjectDoesNotExist 呢?
因為這會將模型層與視圖層耦合在一起。Django 最重要的設計目標之一是保持松耦合。在 django.shortcuts 模塊中引入了一些受控的耦合。
還有一個 get_list_or_404 () 函數,它的工作方式與 get_object_or_404 () 類似 —— 除了使用 filter () 而不是 get ()。如果列表為空,它會引發 Http404。
5.2 使用模板系統
回到我們投票應用的 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>
模板系統使用點查找語法來訪問變量屬性。在 {{question.question_text}} 的示例中,Django 首先對對象 question 進行字典查找。如果失敗,它會嘗試屬性查找 —— 在這種情況下是有效的。如果屬性查找失敗,它會嘗試列表索引查找。
方法調用發生在 {% 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>
這種硬編碼、緊耦合的方法的問題在于,在有很多模板的項目中,更改 URL 會變得很有挑戰性。但是,由于你在 polls.urls 模塊的 path () 函數中定義了 name 參數,你可以通過使用 {% url %} 模板標簽來消除對 url 配置中定義的特定 URL 路徑的依賴:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
其工作方式是查找在 polls.urls 模塊中指定的 URL 定義。你可以確切地看到 ‘detail’ 的 URL 名稱是在以下 polls.urls 模塊中定義的:
...
# 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 視圖,同一個項目中用于博客的應用也可能有一個 detail 視圖。當使用 {% url %} 模板標簽時,如何讓 Django 知道要為哪個應用視圖創建 url 呢?
答案是向你的 URLconf 添加命名空間。在 polls/urls.py 文件中,繼續添加一個 app_name 來設置應用命名空間:
polls/urls.py
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 模板從:
polls/templates/polls/index.html
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
更改為指向命名空間的 detail 視圖:
polls/templates/polls/index.html
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
當你熟悉編寫視圖后,請閱讀本教程的第四部分,了解表單處理和通用視圖的基礎知識。