编写你的第一个 Django 应用,第 3 部分¶
本教程从教程 2结束的地方开始。我们将继续开发 Web 投票应用,并将重点放在创建公共界面 - “视图”。
获取帮助
如果您在学习本教程时遇到问题,请前往常见问题解答的获取帮助部分。
概述¶
视图是 Django 应用中一种“类型”的网页,通常执行特定功能并具有特定的模板。例如,在博客应用中,您可能会有以下视图
博客首页 - 显示最新的几篇文章。
文章“详情”页 - 单篇文章的永久链接页面。
按年份归档页面 - 显示给定年份中包含文章的所有月份。
按月份归档页面 - 显示给定月份中包含文章的所有日期。
按日期归档页面 - 显示给定日期的所有文章。
评论操作 - 处理向给定文章发布评论。
在我们的投票应用中,我们将有以下四个视图
问题“索引”页 - 显示最新的几个问题。
问题“详情”页 - 显示问题文本,不显示结果,但包含一个投票表单。
问题“结果”页 - 显示特定问题的投票结果。
投票操作 - 处理对特定问题中的特定选项进行投票。
在 Django 中,网页和其他内容由视图提供。每个视图都由一个 Python 函数(或者在基于类的视图的情况下为方法)表示。Django 将通过检查请求的 URL(准确地说,是域名之后的 URL 部分)来选择视图。
现在,在您使用 Web 的过程中,您可能遇到过像ME2/Sites/dirmod.htm?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B
这样难看的 URL。您会很高兴地知道,Django 允许我们使用比这更优雅的URL 模式。
URL 模式是 URL 的通用形式 - 例如:/newsarchive/<year>/<month>/
。
为了从 URL 导航到视图,Django 使用了所谓的“URLconf”。URLconf 将 URL 模式映射到视图。
本教程提供了有关使用 URLconf 的基本说明,您可以参考URL 分发器以获取更多信息。
编写更多视图¶
现在让我们向polls/views.py
添加几个视图。这些视图略有不同,因为它们接受一个参数
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()
调用,将这些新视图连接到polls.urls
模块
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/”。它将运行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 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
但是,这里有一个问题:页面的设计在视图中是硬编码的。如果要更改页面的外观,则必须编辑此 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 文档。
现在让我们更新 polls/views.py
中的 index
视图以使用该模板
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
的模板并传递上下文。上下文是一个字典,它将模板变量名映射到 Python 对象。
通过将浏览器指向“/polls/”来加载页面,您应该会看到一个包含来自教程 2的“What’s up”问题的项目符号列表。该链接指向问题的详细信息页面。
快捷方式:render()
¶
加载模板、填充上下文并返回一个包含渲染模板结果的HttpResponse
对象是一个非常常见的习惯用法。Django 提供了一个快捷方式。以下是重写的完整 index()
视图
polls/views.py
¶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)
请注意,一旦我们在所有这些视图中都这样做了,我们就不再需要导入loader
和 HttpResponse
(如果您仍然有 detail
、results
和 vote
的存根方法,则需要保留 HttpResponse
)。
render()
函数将请求对象作为其第一个参数,将模板名称作为其第二个参数,并将字典作为其可选的第三个参数。它返回一个给定模板的HttpResponse
对象,该对象使用给定的上下文进行渲染。
引发 404 错误¶
现在,让我们解决问题详细信息视图 - 显示给定投票的问题文本的页面。这是视图
polls/views.py
¶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})
这里的新概念:如果不存在具有请求 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, 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})
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
。
使用模板系统¶
回到我们投票应用程序的 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 定义。您可以在下面看到 URL 名称“detail”的确切定义位置。
...
# 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 项目中,可能会有 5 个、10 个、20 个或更多应用。Django 如何区分它们之间的 URL 名称?例如,polls
应用有一个 detail
视图,而同一个项目中的博客应用也可能有一个。如何让 Django 在使用 {% url %}
模板标签时知道为哪个应用视图创建 URL?
答案是在您的 URLconf 中添加命名空间。在 polls/urls.py
文件中,添加一个 app_name
来设置应用程序命名空间。
polls/urls.py
¶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"),
]
现在将您的 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>
当您熟悉编写视图后,请阅读 本教程的第 4 部分,了解有关表单处理和通用视图的基础知识。