跳到主要内容

编写你的第一个 Django 应用程序,第 3 部分

官网链接

本教程从教程 2结束的地方开始。我们将继续开展网络投票应用程序,并将重点放在创建公共界面——“视图”上。

从哪里获得帮助:

如果您在完成本教程时遇到问题,请转到常见问题解答的“获取帮助”部分。

概述

视图是 Django 应用程序中的一种网页“类型”,通常提供特定功能并具有特定模板。例如,在博客应用程序中,您可能有以下视图:

  • 博客主页 – 显示最新的几个条目。
  • 条目“详细信息”页面 – 单个条目的永久链接页面。
  • 基于年份的存档页面 – 显示给定年份中包含条目的所有月份。
  • 基于月份的存档页面 – 显示给定月份中包含条目的所有日期。
  • 基于天的存档页面 – 显示给定日期的所有条目。
  • 评论操作 – 处理对给定条目发表评论。

在我们的民意调查应用程序中,我们将有以下四个视图:

  • 问题“索引”页面 – 显示最新的几个问题。
  • 问题“详细信息”页面 – 显示问题文本,没有结果,但带有投票表格。
  • 问题“结果”页面 – 显示特定问题的结果。
  • 投票操作 – 处理对特定问题中特定选择的投票。

在 Django 中,网页和其他内容是通过视图传递的。每个视图都由一个 Python 函数(或方法,如果是基于类的视图)表示。 Django 将通过检查请求的 URL(准确地说,是 URL 中域名后面的部分)来选择视图。

现在,在您上网的时候,您可能会遇到过这样的美女 ME2/Sites/dirmod.htm?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B。您会很高兴知道 Django 为我们提供了 比这更优雅的URL 模式。

URL 模式是 URL 的一般形式 - 例如: /newsarchive/<year>/<month>/

为了从 URL 获取视图,Django 使用所谓的“URLconfs”。 URLconf 将 URL 模式映射到视图。

本教程提供了 URLconfs 使用的基本说明,您可以参考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)

polls.urls通过添加以下 调用将这些新视图连接到模块中path()

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.urlsPython 模块,因为它是由设置指向的 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 库。

姜戈想要的只是这样HttpResponse。或者是一个例外。

因为它很方便,所以我们使用 Django 自己的数据库 API,我们在教程 2中介绍过该 API 。这是一个新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 分离。

首先,创建一个名为templatesin yourpolls目录的目录。 Django 将在那里寻找模板。

您的项目TEMPLATES设置描述了 Django 将如何加载和呈现模板。默认设置文件配置一个选项设置为 的 DjangoTemplates 后端。按照惯例,在每个.APP_DIRSTrue``DjangoTemplatesINSTALLED_APPS

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 文档

现在让我们更新index视图polls/views.py以使用模板:

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需要保留)。HttpResponse``detail``results``vote

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})

这里的新概念是:Http404如果具有请求的 ID 的问题不存在,则视图会引发异常。

polls/detail.html稍后我们将讨论您可以在该模板中添加哪些内容,但如果您想快速使上面的示例正常工作,则文件只包含:

polls/templates/polls/detail.html

{{ question }}

现在就让您开始吧。

快捷方式:get_object_or_404()

这是一个非常常见的习惯用法,如果对象不存在则使用get() 和引发。 Http404Django 提供了一个快捷方式。这是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 raiseHttp404而不是 ObjectDoesNotExist

因为这会将模型层耦合到视图层。 Django 最重要的设计目标之一是保持松散耦合。模块中引入了一些受控耦合django.shortcuts

还有一个get_list_or_404()函数,其工作原理与get_object_or_404()– 相同,只是使用 filter()代替 get()Http404如果列表为空,则会引发该异常 。

使用模板系统

返回到detail()我们的民意调查应用程序的视图。给定上下文变量questionpolls/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>

模板系统使用点查找语法来访问变量属性。在示例中,首先 Django 对对象进行字典查找。如果失败,它会尝试属性查找——在本例中这是有效的。如果属性查找失败,它会尝试列表索引查找。{{ question.question_text }}``question

方法调用发生在循环中: 被解释为 Python 代码 ,它返回对象的可迭代对象,适合在标记中使用。{% for %}question.choice_set.all``question.choice_set.all()``Choice{% for %}

有关模板的更多信息,请参阅模板指南。

删除模板中的硬编码 URL

请记住,当我们在模板中编写问题的链接时polls/index.html ,该链接是部分硬编码的,如下所示:

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

这种硬编码、紧密耦合的方法的问题在于,更改具有大量模板的项目上的 URL 变得具有挑战性。但是,由于您在模块的函数name中定义了参数,因此您可以使用模板标签消除对 url 配置中定义的特定 URL 路径的依赖:path()polls.urls``{% 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以设置应用程序命名空间:

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>

指向命名空间详细视图:

polls/templates/polls/index.html

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>