书籍名称:[Python3 自动化软件发布系统-Django2 实战]
Django 路由 URL
Django 的 URL 功能,可以说是在 models 层,view 层,url 层,都实现统一形式。path,include,reverse,resolve,get_absolute__rule 结合 namespace,使我们在制作 URL 时,正向解析和反向解析都易如反掌,千变万化,又不失规范优雅。
URLConf 的简介
URL 配置(URLConf)就像 Django 所支撑网站的目录。它的本质是 URL 与要为该 URL 调用的 view 函数之前的映射表;也就是告诉 Django,对于哪个 URL 调用哪段代码。
Django 处理接收到的 URL 的流程如下:
- 首先确定使用的 URLConf 模块,默认情况下使用的是 settings.py 中的 ROOT_URLCONF 对应的模块。如果接收到的 HttpRequest 经由 Middlewares 配置了 URLConf 属性,则就会使用该属性配置的模块;
- Django 在该模块中查找 urlpatterns 变量,这个变量必须是 django.urls.path() 或者 django.urls.re_path() 实例的列表;
- Django 按顺序匹配 urlpatterns 中的模式,使用首先匹配到的模式;
- 匹配到模式后,会执行该模式对应的视图函数,或者视图类,并把以下参数传递过去:
- HttpRequest 实例;
- 如果匹配到的模式没有返回 named group,则正则表达式返回的匹配内容就会作为 positional arguments。
- 路径表达式匹配到的 named part 作为 keyword arguments,会被 django.urls.path() 和 django.urls.re_path() 所指定的 kwargs 所覆盖;
- 如果没有匹配到模式,或者处理过程中抛出了异常,则 Django 会调用处理异常的视图。
URLConf 的 urlpatterns
urls.py 中默认就有 Urlpatterns,可以把它看做一个存放了映射关系的列表。
Django 2.x 中常用的是 path() 方法,还可以使用 re_path() 方法来兼容 1.x 版本中的 url() 方法。
这里,将会使用以下虚构的 URL 文件作为示例讲解:
1 | from django.contrib import admin |
函数 path() 具有四个参数,其中有:两个必选参数 route 和 view,两个可选参数 kwargs 和 name。
接下来看看 path() 函数的四个参数含义:
- route[必选参数]: route 是一个匹配 URL 的准则(类似正则表达式)。这些准则不会匹配 GET 和 POST 参数或域名;
- view[必选参数]: 当 Django 找到了一个匹配的准则时,就会调用这个特定的视图函数,并传入一个 HttpRequest 对象作为第一个参数,被捕获的参数以关键字参数的形式传入;
- kwargs[可选参数]: 任意一个关键字参数都可以作为一个字典传递给目标视图函数;
- name[可选参数]: 为你的 URL 取名能使你在 Django 的任意地方唯一地引用它,尤其是在模板中。这个特性允许你只改一个文件就能全局地修改某个 URL 模式。
Path 的 route 字符串中,可以使用 < >
获取符合条件的字符串,转换成对应数据类型传递给 views 处理函数中的变量名,这个带有 < >
的字符串,称为 path 转换器。
默认情况下,Django 内置下面的路径转换器:
- str: 匹配任何非空字符串,但不含 "/",默认使用;
- int: 匹配 0 和正整数,返回一个 int 类型;
- slug: 可以理解为注释,后缀,附属等概念,是 URL 在最后的一部分解释性字符。该转换器匹配任何 ASCII 字符以及连接符和下划线;
- uuid: 匹配一个 uuid 格式的对象。为了防止冲突,规定必须使用连字符,所有字母必须小写,例如:0836561d3-9527-633c-b9b6-8a032e1565f0。返回一个 UUID 对象;
- path: 匹配任何非空字符串,重点是可以包含路径分隔符 “/”,这个转换器可以帮住你匹配整个 URL 而不是一段一段的 URL 字符串。
URLConf 的路由分发
在同一个 Django 项目中有多个 APP 应用,如果大家共用一个 URL,那么在根 urls.py 中就要写巨多的 urls 映射关系。这样看起来很不灵活,而且杂乱无章,容易造成混淆。
路由分发就是让每个 APP 拥有自己单独的 URL,方便以后的维护和管理。也就是在每个 APP 里,各自创建一个 urls.py 路由模块,然后从根路由出发,将 APP 所属的路由请求全部转发到相应的 urls.py 模块中。
路由转发使用的是 include() 方法,所以需要导入它,它的参数是转发目的地路径的字符串,路径以圆点分割。
URLConf 的反向解析
在使用 Django 项目时,一个常见的需求是获得 URL 的最终形式,以用于嵌入到生成的内容(视图或显示给用户的 URL 等)中,或者用于处理服务器端的导航(重定向等)。
Django 提供了一个解决方案,使得 URL 映射器是 URL 设计的唯一存储库。用你的 URLConf 提供给它,然后它可以再两个方向上使用:
- 从用户/浏览器请求的 URL 开始,它调用正确的 Django 视图,提供它可能需要的任何参数以及从 URL 中提取的值;
- 从相应的 Django 视图的标识以及将传递给它的参数的值开始,获取关联的 URL;
第一种使我们前面讨论过的用法,可以称之为 URL 正向解析;第二种是所谓的 URL 反向解析,反向 URL 匹配,反向 URL 查询或者 URL 反转;
Django 提供了用于执行 URL 反转的工具,以匹配需要 URL 的不同图层。如要使用 URL 反转功能,需要在 urls.py 中起一个别名 name;如果有多个 APP 应用,最好和下面讲的 URL 名称空间结合起来。
- 在 Template 模板中,使用 URL 模板标签(URL’别名’);
- 在 Python 代码中,使用 reverse() 函数;
- 在处理 Django 模型实例的 URL 相关的更高级别的代码中,使用 get_absolute_url() 方法(也就是在模型 model 中);
例如,一个最简单的 URL 定义如下:
1 | from django.urls import path |
那么,如果想要在模板中跳转到此 URL,则使用如下语法:
1
<a href="{% url 'bbs_year_2019' %}">2019 bbs</a>
如果想在 view 中跳转到此 URL,则使用如下语法,需要先导入 reverse 方法:
1
2
3
4from django.urls import reverse
def bbs_year_2019(request):
return HttpResponseRedirect(reverse('bbs_year_2019'))而在 models.py 中,则使用的语法类似如下,前提是先定义好带参数的 bbs_view:
1
2def get_absolute_url(self):
return reverse('bbs_view', args=[self.slug])
URLConf 的命名空间
命名空间是表示标识符的可见范围。一个标识符可以再多个命名空间中定义,它在不同命名空间中的含义是互不相干的。
如果 name 没有作用域,则 Django 在反解 URL 时,会在项目全局顺序搜索,当查找到第一个 name 指定的 URL 时,立即返回。
在开发项目时,会经常使用 name 属性反解出 URL。当不小心在不同的 APP 的 urls 中定义了相同的 name 时,可能会导致 URL 反解错误。为了避免这种事情发生,引入了命名空间。
假如:如果 A 同事开发的应用里有一个 URL,命名为 index; 而同事 B开发的应用里也有一个 URL,同样命名为 index, 那么 Django 应该怎么区分这两个 URL 呢?
按照上面路由分发的思路,A 在自己应用的 urls.py 文件中,新增一个 app_name=’A’ 的命名空间;而 B 在自己应用的 urls.py 文件中,新增一个 app_name=’B’ 的命名空间。
经过这样一区分,如果想在模板中跳转到 A 的 index URL,则使用如下语法:
1
<a href="{% url 'A:index' %}">A index</a>
如果想在模板中跳转到 B 的 index URL,则使用如下语法:
1
<a href="{% url 'B:index' %}">B index</a>
如果想在 View 视图中跳转到 A 的 index URL,则使用如下语法:
1
return HttpResponseRedirect(reverse('A:index'))
如果想在 View 视图中跳转到 B 的 index URL,则使用如下语法:
1
return HttpResponseRedirect(reverse('B:index'))
Django 模板 Template
Django 的模板是一些文本字符串,它既可以存在于文件当中,也可以存在于内存当中,其作用是把文档的表现与数据区分开。
模板定义了一些占位符和基本的逻辑(模板标签),并规定如何显示文档。而 Django 会使用模板引擎来将一些上下文填充到占位符中。通常,模板用于生成 HTML,不过 Django 模板可以生成任何基于文本的格式。
使用模板后大致有以下几个优点:
- 将业务逻辑的 Python 代码和页面设计的 HTML 代码分离;
- 使代码更干净整洁,更容易维护;
- 使 Python 程序员和 HTML/CSS 程序员分工协作,提高生产效率;
- 将 HTML 代码分离出来,使其能够复用。
Django 新建项目完成后,在 settings.py 文件中的 Template 变量中,配置了一个默认的模板引擎,即 django.template.backends.django.DjangoTemplates
,此引擎支持 Django Template Language(DTL)。
Django Template Language 简介
若想在 Python 代码中使用 Django 的模板系统,基本方式如下:先以字符串的形式提供原始的模板代码,创建 Template 对象。然后在 Template 对象上调用 render() 方法,传入一系列变量(上下文)。返回的是完全渲染模板后得到的字符串,模板中的变量和模板标签已经根据上下文求出值了。
- 使用
python manage.py shell
命令进入支持 Django 的 Shell,运行以下示例,以便了解 DTL 的基本工作机制:
1 | In [1]: from django.template import Template, Context |
代码解释:
- 第一行,导入我们需要的 Template 和 Context 模块;
- 第二行,定义一个 bbs 字典变量,在真正的开发当中,上下文的变量是相当灵活的;
- 第三行,创建一个模板对象 t,使用 {{ }} 来作变量占位 tag;
- 第四行,将上下文实例化为第2行定义的 bbs 变量;
- 第五行,在模板对象上调用 render() 方法,传递 Context 实例。这是返回渲染后模板的方法,这样会替换模板变量为真实的值和执行块标签。
模板变量,常用标签和过滤器
DTL 的模板变量,常用标签和过滤器如下表所示
类别 | 细分 | 代码 |
---|---|---|
模板变量 | 普通变量 | {{ name }} |
对象变量 | {{ bbs.title }} (使用点号访问对象属性和方法,方法不加括号) |
|
常用模板标签 | if 标签 | {% if %}...{% endif %} |
for 标签 | {% for item in items %}...{% endfor %} | |
相等判断 | {% ifequal x y %}...{% endifequal %} {% ifnotequal x y %}...{% endifnotequal %} |
|
注释 | {# 单行注释 #} {% comment %} 多行注释{% endcomment %} |
|
过滤器 | 全小写 | {{ name|lower }} |
全大写 | {{ name|upper }} | |
首字母大写 | {{ name|title }} | |
第一个元素 | {{ user_list|first }} | |
最后一个元素 | {{ user_list|last }} | |
返回长度 | {{ word|length }} | |
模板引用 | {% include url %} | |
模板继承 | 块(block) | {% block block_name %}{% endblock %} |
继承母版 | {% extends url %} | |
静态文件 | {% load static %} | |
csrf_token | {% csr_token %} |
Django Template 加载配置及基本使用
平时我们开发项目时,都会讲模板存为一个 HTML 文件,然后通过 Django Template 加载机制,讲这些模板加载进内存,以备渲染。
为了从文件系统加载模板,首先要告诉框架模板的存储位置。这个位置在 settings.py 文件中,找到 TEMPLATES 设置。它的值是一个列表,分别针对各个模板引擎:
1 | ... |
BACKEND
的值是一个点分 Python 路径,指向实现 Django 模板后端 API 的模板引擎类。Django 默认实现的内置后端有 django.template.backends.django.DjangoTemplates 和 django.template.backends.jinja2.Jinja2;
因为 多数引擎从文件中加载模板,所以各个引擎的顶层配置包含三个通用的配置:
- DIRS: 定义一个目录列表,模板引擎按顺序在其中查找模板文件;
- APP_DIRS: 设定是否在安装的应用中查找模板。按约定,APP_DIRS 设为 true 时,DjangoTemplates 会在 INSTALLED_APPS 中的各个应用里查找名为 templates 的子目录。这样,即使 DIRS 为空,模板引擎也能找到应用模板;
- OPTIONS: 是一些针对后端的设置
下面通过这种方式,来实现一个带模板功能的 Web 服务网页。
在 bbs 目录下的 views.py 文件里,加入一个新的视图函数
password_reset
1
2
3
4
5
6
7
8
9
10
11
12from django.http.response import HttpResponse
from django.template.loader import get_template
def password_reset(request):
user = {'username': 'CK Wong', 'email': 'CK@demo.com'}
t = get_template('bbs/password_reset.html')
context = {'user': user}
# 在 Django 1.1 版本中可以直接传入 Context 对象,
# 在 Django 1.11 后只能传入字典
html = t.render(context)
return HttpResponse(html)代码解释:
- 首先导入 get_template 以及 HttpResponse 方法;
- 定义一个 user 字典;
- 调用 get_template 方法,生成一个 Template 实例,指定的模板为 bbs 目录下的 password_reset.html;
- 定义一个 Context 上下文对象 context;
- 使用 templates 实例的 render() 方法,指定参数为一个字典对象;
- 此函数返回一个 HttpResponse 对象,成为一个视图。
在 bbs 目录下新建目录 templates,在 templates 目录下新建 bbs 目录,然后在 bbs 目录下新建一个模板文件 password_reset.html.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html lang="en">
<head>
<meta charset="UTF-8">
<title> Passwor Reset</title>
</head>
<body>
<pre>
Hi there,
Someone asked for a password reset for the email address {{ user.email }}.
Follow the link below:
In case you forgot your Django Boards username: {{ user.username }}.
If clicking the link above doesn't work, please copy and paste the URL
in a new browser windows instead.
</pre>
</body>
</html>代码解释:
- 因为在 settings.py 文件中的 TEMPLATES 中,已将 ‘APP_DIRS’ 变量设置为 True,所以,当 Django 框架寻找网页模板时,会在当前应用的 templates 下寻找指定的模板文件。
- 在此例中,指定的模板路径为
bbs/password_reset.html
,那么它的真实路径就是[项目根目录]/bbs/templates/bbs/password_reset.html
。
在 django_demo_project 目录下的 urls.py 文件里,加入新的 path 实例。
1
2
3
4
5from django.urls import path
urlpatterns = [
path('password_reset/', bbs_view.password_reset, name='password_reset'),
]在浏览器里面访问
http://127.0.0.1:8000/password_reset/
,查看网页显示的内容是否正常。
Django Template 的 Render 快捷使用
在 Django 的开发过程中,根据上下文渲染一个网页模板是一个最经常要实现的操作,因此 Django 框架的开发者们提供了一种更简单的快捷方式,只需要一行代码就能做到。这个简单的方式就是 django.shortcuts 模块中名为 render() 的函数。
render() 函数中的第一个参数是请求对象;第二个参数是模板名称;第三个参数可选,是一个字段,用于创建传给模板的上下文。如果不指定第三个参数,则 render() 使用一个空字典。
将 bbs 目录下 views.py 文件中 password_reset() 函数更改如下:
1 | from django.shortcuts import render |
还有一个类似的函数 render_to_response(),这个函数所有的模板路径为 django.shortcuts.render_to_response。自 Django 1.3 开始,render() 方法是 render_to_response() 的一个崭新的快捷方式,前者会自动使用 RequestContext,而后者必须编码出来,这是最明显的区别。render_to_response() 在 Django 2.0 版本后已移除。