Django--03视图和模板

发布于:2025-07-14 ⋅ 阅读:(17) ⋅ 点赞:(0)

Django–03视图和模板

Part 3: Views and templates
本教程承接第二部分,我们将继续开发投票应用,重点介绍 Django 的表单处理和通用视图。

前言

本系列博客内容全部在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 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"),
]

在浏览器中查看 “/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 文档。

这里我给出两个方案
1.官方代码:不影响使用,效果图放在下面
2.补全代码:较美观

现在让我们更新 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/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 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 对象。

修改后的polls\views.py

from django.http import HttpResponse
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)




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 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。
在这里插入图片描述
查看改函数具体定义:鼠标选中–>右键–>转到定义

在这里插入图片描述

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

在这里插入图片描述

当你熟悉编写视图后,请阅读本教程的第四部分,了解表单处理和通用视图的基础知识。


网站公告

今日签到

点亮在社区的每一天
去签到