【实战案例】Django框架使用模板渲染视图页面及异常处理

本文基于之前内容列表如下:
【图文指引】5分钟搭建Django轻量级框架服务
【实战案例】Django框架基础之上编写第一个Django应用之基本请求和响应
【实战案例】Django框架连接并操作数据库MySQL相关API

视图概述

Django中的视图的概念是一类具有相同功能和模板的网页的集合,在Django中,网页和其他内容都是从视图派生而来,每一个视图表现为一个Python函数(或者说方法,如果是在基于类的视图里的话),Django将会根据用户请求的URL来选择使用哪个视图(更准确的说,是根据URL中域名之后的部分),为了将URL和视图关联起来,Django使用了'URLconfs'来配置,URLconf将URL模式映射到视图。

基于之前的投票应用,在polls/views.py 里添加更多视图,与之前不同的是他们接收参数:

python 复制代码
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模块里,只要添加几个url()函数调用就行:

python 复制代码
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"),
]

这时候运行项目后在地址栏中访问http://localhost:8000/polls/27/即可发现如下页面

也可以尝试 "/polls/27/results/" 和 "/polls/27/vote/",这些将显示占位的结果和投票页面。

当请求网站的页面如"/polls/27/",Django会加载myself.urls模块,因为它被ROOT_URLCONF设置指向。它会找到名为urlpatterns的变量并按顺序遍历这些模式,在找到匹配项'polls/'之后会剥离匹配的文本("polls/"),然后将剩余的文本"27/"发送给'polls.urls'URL 配置以进行进一步处理匹配 'int:question_id/',从而调用 detail() 视图

python 复制代码
detail(request=<HttpRequest object>, question_id=27)

问题question_id=27来自int:question_id。使用尖括号"获得"网址部分后发送给视图函数作为一个关键字参数。字符串的question_id部分定义了要使用的名字,用来识别相匹配的模式,而int部分是一种转换形式,用来确定应该匹配网址路径的什么模式。冒号: 用来分隔转换形式和模式名。

上述过程仅仅是写一个demo用于测试页面返回,实际中一个视图要做的事是返回一个包含被请求页面内容的HttpResponse对象或者抛出一个异常,如Http404,至于其他事情也可以做但理论上不属于页面的职责。

视图模板系统

为了不将页面的设计写死在视图函数的代码中,接下来使用Django的模板系统,只要创建一个视图就可以将页面的设计从代码中分离出来。

在polls应用路径下创建polls/templates/polls/index.html

创建模板文件的通用规则如下:
目录结构 :在每个应用(如polls)中,应该有一个templates目录,接着在这个目录下创建一个与应用名称相同的子目录(如polls),这样可以避免模板名称冲突。
文件命名 :文件命名:模板文件通常以.html为后缀,文件名应描述其功能,如index.html、detail.html等。
TEMPLATES配置:在项目的settings.py文件中,TEMPLATES设置应确保APP_DIRS为True,以允许Django在每个已安装应用的templates目录中查找模板。

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>这是Polls的index模板渲染</title>
</head>
<body>
    {% 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/views.py里的index视图来使用模板:

python 复制代码
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模板文件,并且向它传递一个上下文(context)。这个上下文是一个字典,它将模板内的变量映射为Python对象。访问 "/polls/"会看见一个无序列表,与数据库中的数据对应,列出了之前添加的投票问题,链接指向这个投票的详情页。


render()函数

"载入模板,填充上下文,再返回由它生成的HttpResponse对象"是一个非常常用的操作流程,于是Django提供了一个快捷函数render()用它来重写index()视图:

python 复制代码
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,当然其他函数还在使用的话就需要保持导入。

render()函数的作用和参数如下:
作用 :返回一个HttpResponse对象,这个对象包含了渲染后的模板内容,用户可以在浏览器中看到这个结果。
参数

  1. 第一个参数(request):传入的HTTP请求对象,包含用户请求的信息。
  2. 第二个参数(template_name):要渲染的模板的名称,以字符串形式表示,通常包括应用名称和模板路径。
  3. 第三个参数(context, 可选):一个字典,包含要传递给模板的上下文数据,以便在模板中动态显示内容。

对于第三个参数示例如下:

python 复制代码
from django.shortcuts import render

def my_view(request):
    context = {
        'greeting': 'Hello, World!',
        'items': ['Item 1', 'Item 2', 'Item 3'],
    }
    return render(request, 'polls/polls/index.html', context)

在模板index.html中,可以通过以下方式访问这些数据:

html 复制代码
<h1>{{ greeting }}</h1>
<ul>
    {% for item in items %}
        <li>{{ item }}</li>
    {% endfor %}
</ul>

接下来处理异常问题,拿detail举例,先在polls/views.py中更新如下代码:

python 复制代码
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)
    # 如果指定问题 ID 所对应的问题不存在,这个视图就会抛出一个 Http404 异常。
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, "polls/detail.html", {"question": question})

polls/templates/polls/detail.html中添加代码如下:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>这是detail的模板渲染</title>
</head>
<body>
    {{ question.question_text }}
</body>
</html>

当访问数据库中不存在的id时会出现404页面,如下所示:

get_object_or_404()函数

尝试用get()函数获取一个对象,如果不存在就抛出Http404错误也是一个普遍的流程,Django也提供了一个快捷函数get_object_or_404()作用于此,下面是修改后的详情 detail()视图代码:

python 复制代码
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() 而不是自己捕获ObjectDoesNotExist异常呢?还有为什么模型API不直接抛出ObjectDoesNotExist而是抛出Http404呢?

因为这样做会增加模型层和视图层的耦合性,指导Django设计的最重要的思想之一就是要保证松散耦合,一些受控的耦合将会被包含在django.shortcuts模块中。

也有get_list_or_404()函数,工作原理和get_object_or_404()一样,除了get()函数被换成了filter()函数,如果列表为空的话会抛出Http404异常。

更新polls/detail.html 模板里的代码:

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 对象使用字典查找(也就是使用 obj.get(str) 操作),如果失败了就尝试属性查找(也就是 obj.str 操作),结果是成功了。如果这一操作也失败的话,将会尝试列表查找(也就是 obj[int] 操作)。

在 {% for %} 循环中发生的函数调用:question.choice_set.all 被解释为 Python 代码 question.choice_set.all() ,将会返回一个可迭代的 Choice 对象,这一对象可以在 {% for %} 标签内部使用。

去除硬编码

之前polls/index.html 里编写投票链接时,链接是硬编码的:

html 复制代码
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

这种硬编码、强耦合的方法的问题在于,在具有大量模板的项目中更改URL变得具有挑战性。

然而由于在polls.urls模块中的path()函数中定义了name参数,所以可以通过使用 {% url %} 模板标签来消除对 url 配置中定义的特定 URL 路径的依赖:

html 复制代码
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

这个标签的工作方式是在polls.urls模块的URL定义中寻具有指定名字的条目。之前 'detail' 的 URL 是在如下语句中定义的:

python 复制代码
path("<int:question_id>/", views.detail, name="detail"),

如果想改变投票详情视图的URL,比如想改成polls/specifics/12/,就不用在模板里修改任何东西(包括其它模板),只要在polls/urls.py里稍微修改一下就行:

python 复制代码
path("specifics/<int:question_id>/", views.detail, name="detail"),

添加不同应用的命名空间

在实际Django项目中可能会有很多个应用,Django如何分辨重名的 URL 呢?举个例子,polls应用有detail 视图,可能另一个应用也有同名的视图。Django如何知道 {% url %} 标签到底对应哪一个应用的URL呢?答案是在根 URLconf 中添加命名空间。

在polls/urls.py文件中稍作修改,加上 app_name 设置命名空间:

python 复制代码
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 文件,从:

html 复制代码
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

修改为指向具有命名空间的详细视图:

html 复制代码
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
相关推荐
杨DaB2 分钟前
【SpringBoot】Dubbo、Zookeeper
spring boot·后端·zookeeper·dubbo·java-zookeeper
深盾安全3 分钟前
uv,下一代Python包管理工具
python
一语长情17 分钟前
Netty流量整形:保障微服务通信稳定性的关键策略
java·后端·架构
冲鸭ONE17 分钟前
java数据类型与语句结构
后端
柯南二号31 分钟前
【后端】SpringBoot中HttpServletRequest参数为啥不需要前端透传
前端·spring boot·后端
山烛32 分钟前
OpenCV 图像处理基础操作指南(二)
人工智能·python·opencv·计算机视觉
跟橙姐学代码1 小时前
学Python,先把这“三板斧”练到炉火纯青!(零基础也能看懂)
前端·python
白鲸开源1 小时前
收藏!史上最全 Apache SeaTunnel Source 连接器盘点 (2025版),一篇通晓数据集成生态
大数据·数据库·开源
MonKingWD1 小时前
MySQL事务篇-事务概念、并发事务问题、隔离级别
数据库·后端·mysql
华仔啊1 小时前
别学23种了!Java项目中最常用的6个设计模式,附案例
java·后端·设计模式