创建公共接口视图

DjangoDjangoBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

本教程从《设置数据库》结束的地方开始。我们将继续Web投票应用程序,并专注于创建公共接口——“视图”。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL django(("`Django`")) -.-> django/CoreConfigurationandRoutingGroup(["`Core Configuration and Routing`"]) django(("`Django`")) -.-> django/DatabaseModelsandMigrationsGroup(["`Database, Models, and Migrations`"]) django(("`Django`")) -.-> django/UserInterfaceandInteractionGroup(["`User Interface and Interaction`"]) django/CoreConfigurationandRoutingGroup -.-> django/django_urls("`Django Urls`") django/CoreConfigurationandRoutingGroup -.-> django/django_exceptions("`Django Exceptions`") django/DatabaseModelsandMigrationsGroup -.-> django/models("`Models`") django/DatabaseModelsandMigrationsGroup -.-> django/databases("`Databases`") django/DatabaseModelsandMigrationsGroup -.-> django/request_and_response("`Request and Response`") django/DatabaseModelsandMigrationsGroup -.-> django/schemaeditor("`SchemaEditor`") django/UserInterfaceandInteractionGroup -.-> django/templates("`Templates`") subgraph Lab Skills django/django_urls -.-> lab-153743{{"`创建公共接口视图`"}} django/django_exceptions -.-> lab-153743{{"`创建公共接口视图`"}} django/models -.-> lab-153743{{"`创建公共接口视图`"}} django/databases -.-> lab-153743{{"`创建公共接口视图`"}} django/request_and_response -.-> lab-153743{{"`创建公共接口视图`"}} django/schemaeditor -.-> lab-153743{{"`创建公共接口视图`"}} django/templates -.-> lab-153743{{"`创建公共接口视图`"}} end

概述

视图是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使用所谓的“URL配置”。URL配置将URL模式映射到视图。

本教程提供了使用URL配置的基本指导,你可以参考 /topics/http/urls 获取更多信息。

编写更多视图

现在让我们在 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)

通过添加以下 ~django.urls.path 调用,将这些新视图连接到 polls.urls 模块:

编辑 polls/urls.py 文件并添加以下行:

from django.urls import path

from. import views

urlpatterns = [
    ## 例如:/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"),
]

现在,再次运行服务器:

cd ~/project/mysite
python manage.py runserver 0.0.0.0:8080

切换到 Web 8080 标签页,访问 /polls/34/。它将运行 detail() 方法并显示你在URL中提供的任何ID。也试试 /polls/34/results//polls/34/vote/ —— 这些将显示占位符结果和投票页面。

Django URL路由图

当有人从你的网站请求一个页面时 —— 比如说,/polls/34/,Django将加载 mysite.urls Python模块,因为它由 ROOT_URLCONF 设置指向。它找到名为 urlpatterns 的变量并按顺序遍历这些模式。在 'polls/' 处找到匹配项后,它去掉匹配的文本 ("polls/") 并将剩余的文本 ——"34/" —— 发送到 polls.urls URL配置进行进一步处理。在那里它匹配 '<int:question_id>/',从而调用 detail() 视图,如下所示:

detail(request=<HttpRequest对象>, question_id=34)

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

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

每个视图负责执行以下两件事之一:返回一个包含请求页面内容的 ~django.http.HttpResponse 对象,或者引发一个异常,如 ~django.http.Http404。其余的就由你决定了。

你的视图可以从数据库读取记录,也可以不读取。它可以使用诸如Django的模板系统 —— 或者第三方Python模板系统 —— 也可以不使用。它可以生成PDF文件、输出XML、即时创建ZIP文件,使用任何你想要的Python库来做任何你想做的事情。

Django所需要的只是那个 ~django.http.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)


## 其余的视图(detail、results、vote)保持不变

不过,这里有个问题:页面的设计在视图中是硬编码的。如果你想改变页面的外观,就必须编辑这段Python代码。所以,让我们使用Django的模板系统,通过创建一个视图可以使用的模板,将设计与Python分开。

首先,在你的 polls 目录中创建一个名为 templates 的目录。Django会在那里寻找模板。

你的项目的 TEMPLATES 设置描述了Django将如何加载和渲染模板。默认的设置文件配置了一个 DjangoTemplates 后端,其 APP_DIRS <TEMPLATES-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找到正确的模板,而确保这一点的最佳方法是通过 命名空间 它们。也就是说,把那些模板放在另一个以应用本身命名的目录中。

在那个模板中放入以下代码:

{% 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 视图以使用该模板:

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对象的字典。

再次运行服务器:

python manage.py runserver 0.0.0.0:8080

通过在浏览器中访问 “/polls/” 来加载页面,你应该会看到一个包含教程2中 “What's up” 问题的项目符号列表。该链接指向问题的详情页面。

Django投票索引页面

一个快捷方式:~django.shortcuts.render

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

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)

请注意,一旦我们在所有这些视图中都这样做了,我们就不再需要导入 ~django.template.loader~django.http.HttpResponse(如果你仍然有 detailresultsvote 的存根方法,你可能还需要保留 HttpResponse)。

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

引发404错误

现在,让我们来处理问题详情视图 —— 即显示给定投票的问题文本的页面。以下是该视图:

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("问题不存在")
    return render(request, "polls/detail.html", {"question": question})

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

稍后我们将讨论可以在 polls/detail.html 模板中放入什么内容,但如果你想快速使上述示例生效,一个只包含以下内容的文件:

{{ question }}

现在就可以让你开始了。

一个快捷方式:~django.shortcuts.get_object_or_404

使用 ~django.db.models.query.QuerySet.get 并在对象不存在时引发 ~django.http.Http404 是一种非常常见的习惯用法。Django提供了一个快捷方式。以下是重写后的 detail() 视图:

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

~django.shortcuts.get_object_or_404 函数将一个Django模型作为其第一个参数,并接受任意数量的关键字参数,它将这些参数传递给模型管理器的 ~django.db.models.query.QuerySet.get 函数。如果对象不存在,它将引发 ~django.http.Http404

为什么我们使用辅助函数 ~django.shortcuts.get_object_or_404,而不是在更高层次自动捕获 ~django.core.exceptions.ObjectDoesNotExist 异常,或者让模型API引发 ~django.http.Http404 而不是 ~django.core.exceptions.ObjectDoesNotExist 呢?

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

还有一个 ~django.shortcuts.get_list_or_404 函数,它的工作方式与 ~django.shortcuts.get_object_or_404 相同 —— 只是使用 ~django.db.models.query.QuerySet.filter 而不是 ~django.db.models.query.QuerySet.get。如果列表为空,它将引发 ~django.http.Http404

使用模板系统

回到我们投票应用的 detail() 视图。给定上下文变量 questionpolls/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 %}<for> 循环中:question.choice_set.all 被解释为Python代码 question.choice_set.all(),它返回一个 Choice 对象的可迭代对象,适用于 {% for %}<for> 标签。

移除模板中的硬编码URL

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

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

这种硬编码、紧密耦合的方法的问题在于,在有很多模板的项目中更改URL变得很有挑战性。但是,由于你在 polls.urls 模块的 ~django.urls.path 函数中定义了 name 参数,你可以通过使用 {% url %} 模板标签来消除对URL配置中定义的特定URL路径的依赖:

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

其工作方式是通过查找 polls.urls 模块中指定的URL定义。你可以确切地看到下面定义了 “detail” 的URL名称的位置:

## 被 {% url %} 模板标签调用的 'name' 值
path("<int:question_id>/", views.detail, name="detail"),

如果你想将投票详情视图的URL更改为其他内容,例如改为 polls/specifics/12/,而不是在模板中进行更改(或多个模板),你可以在 polls/urls.py 中进行更改:

你根本不需要更改模板。

## 添加了单词'specifics'
path("specifics/<int:question_id>/", views.detail, name="detail"),

为URL名称添加命名空间

本教程项目只有一个应用,即 “polls”。在实际的Django项目中,可能会有五个、十个、二十个或更多的应用。Django如何区分它们之间的URL名称呢?例如,“polls” 应用有一个 “detail” 视图,同一个项目中用于博客的应用可能也有。如何确保在使用 {% url %} 模板标签时,Django知道为某个URL创建哪个应用的视图呢?

答案是为你的URL配置添加命名空间。在 polls/urls.py 文件中,继续添加一个 app_name 来设置应用命名空间:

from django.urls import path

from. import views

app_name = "polls"
urlpatterns = [
    ## 例如:/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/index.html 模板从:

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

改为指向带有命名空间的详情视图:

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
URL命名空间示例

当你对编写视图感到得心应手时,请阅读 表单处理与简化代码 来学习表单处理和通用视图的基础知识。

总结

恭喜你!你已经完成了“创建公共接口视图”实验。你可以在LabEx中练习更多实验来提升你的技能。

您可能感兴趣的其他 Django 教程