Skip to content

编写第一个Django应用,第三部分

本教程从第二部分的地方开始。我们将继续开发网络投票应用程序,并将重点介绍如何创建公共界面 - “视图”。

概述

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

    • 博客主页——显示最新的几篇文章。
    • 条目“详细信息”页面——单个条目的永久链接页面。
    • 基于年份的存档页面——显示给定年份的所有月份的条目。
    • 基于月的存档页面——显示给定月份的所有日期的条目。
    • 基于天的存档页面——显示给定天的所有条目。
    • 评论操作——处理对给定条目发表评论。
  • 在我们的民意调查应用中,我们将有以下四个观点:

    • 问题“索引”页面——显示最新的几个问题。
    • 问题“详细信息”页面——显示问题文本,没有结果,但有一个投票表格。
    • 问题“结果”页面——显示特定问题的结果。
    • 投票行动——处理针对特定问题的特定选择的投票。
在 Django 中,网页和其他内容通过视图传递。每个视图都由一个 Python 函数(或基于类的视图中的方法)表示。Django 将通过检查所请求的 URL(准确地说,是 URL 中域名后的部分)来选择视图。
现在,您在上网时可能遇到过诸如 这样的美妙事物 ME2/Sites/dirmod.htm?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B。您会很高兴知道 Django 允许我们使用比这更优雅的 URL 模式。
URL 模式是 URL 的一般形式 - 例如: /newsarchive///。
为了从 URL 转到视图,Django 使用所谓的“URLconfs”。URLconf 将 URL 模式映射到视图。
本教程提供了使用URLconfs的基本指导,您可以参考URL dispatcher了解更多信息。

编写更多视图

现在让我们向中添加更多视图polls/views.py。这些视图略有不同,因为它们接受一个参数:

Example

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():

Example

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 进行进一步处理。在那里它匹配'/',从而调用视图,detail()如下所示:

detail(request=<HttpRequest object>, question_id=34)

部分question_id=34来自。使用尖括号“捕获” URL 的一部分并将其作为关键字参数发送到视图函数。question_id字符串的部分定义将用于标识匹配模式的名称,int部分是一个转换器,它确定哪些模式应该匹配 URL 路径的这一部分。冒号 ( :) 将转换器和模式名称分隔开。

编写可以实际执行某些操作的视图

每个视图负责做两件事之一:返回 HttpResponse包含所请求页面内容的对象,或引发异常(例如)Http404。其余的由您决定。
您的视图可以从数据库读取记录,也可以不读取。它可以使用模板系统(例如 Django 的模板系统)或第三方 Python 模板系统,也可以不使用。它可以生成 PDF 文件、输出 XML、动态创建 ZIP 文件,以及使用任何 Python 库来执行任何您想要的操作。
Django 想要的只是这个HttpResponse。或者一个例外。
因为方便,我们使用 Django 自己的数据库 API,我们在教程 2中介绍过。下面是尝试使用一个新index() 视图,它根据发布日期显示系统中最新的 5 个民意调查问题,用逗号分隔:

Example

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 分开。

templates首先,在您的目录中创建一个名为的目录polls。Django 将在其中查找模板。

您的项目TEMPLATES设置描述了 Django 将如何加载和呈现模板。默认设置文件配置一个DjangoTemplates 后端,其APP_DIRS选项设置为 True。按照惯例,DjangoTemplates在每个 中查找“templates”子目录INSTALLED_APPS。

templates刚刚创建的目录中,创建另一个名为 的目录polls,并在其中创建一个名为 的文件 index.html。换句话说,您的模板应该位于 polls/templates/polls/index.html。由于app_directories 模板加载器的工作方式如上所述,您可以在 Django 中将此模板引用为polls/index.html

将以下代码放入该模板中:

Example

{% 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 %}

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

Example

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的“有什么新鲜事”问题。链接指向问题的详细信息页面

快捷方式:render()

加载模板、填充上下文并返回 HttpResponse带有渲染模板结果的对象是一种非常常见的习惯用法。Django 提供了一种快捷方式。以下是index()重写的完整视图:

Example

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需要保留)。HttpResponsedetailresultsvote

该render()函数将请求对象作为其第一个参数,将模板名称作为其第二个参数,将字典作为其可选的第三个参数。它返回使用HttpResponse 给定上下文渲染的给定模板的对象。

引发 404 错误

现在,让我们处理问题详细信息视图 - 显示给定民意调查的问题文本的页面。以下是视图:

Example

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稍后我们将讨论您可以在该模板中放入什么内容,但如果您想快速使上述示例正常运行,则只需包含以下内容的文件:

Example

{{ question }}

快捷方式:get_object_or_404()

如果对象不存在,则使用get() 并引发异常是一种非常常见的习惯用法。Django 提供了一种快捷方式。以下是重写的视图:Http404detail()

Example

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如果对象不存在,则会引发异常。

使用模板系统

回到detail()我们的投票应用程序的视图。给定上下文变量question,模板可能如下所示polls/detail.html

Example

<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.allquestion.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以设置应用程序命名空间:

Example

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模板从:

Example

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

指向命名空间详细视图:

Example

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