3. Django 初探路由

3. 初探路由

handlebars 复制代码
一个完整的路由包含: 路由地址, 视图函数(或者视图类), 可选变量和路由命名.
本章讲述Django的路由编写规则与使用方法, 内容分为: 路由定义规则, 命名空间与路由命名, 路由的使用方式.

3.1 路由定义规则

handlebars 复制代码
路由称为URL (Uniform Resource Locator, 统一资源定位符), 也可以称为URLconf, 
是对可以从互联网上得到的资源位置和访问方法的一种简洁的表示, 是互联网上标准资源的地址.
互联网上的每个文件都有一个唯一的路由, 用于指出网站文件的路径位置.

简单地说, 路由可视为我们常说的网址, 每个网址代表不同的网页.

3.1.1 Django 2以上版本路由定义

handlebars 复制代码
我们知道完整的路由包含: 路由地址, 视图函数(或者视图类), 可选变量和路由命名.
其中基本的信息必须有: 
路由地址和视图函数(或者视图类), 路由地址即我们常说的网址, 
视图函数(或者视图类)即App的views.py文件所定义的函数或类.

在讲解路由定义规则之前, 需对MyDjango项目的目录进行调整, 使其更符合开发规范性.
在项目的index文件夹里添加一个空白内容的.py文件, 命为urls.py.
项目结构如图3-1所示.
handlebars 复制代码
在项目目录下的templates内创建index.html文件, 并在文件内添加如下代码:
html 复制代码
<!-- templates的index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello World</title>
</head>
<body>
    <span>Hello World!!</span>
</body>
</html>
handlebars 复制代码
图3-1 项目结构
handlebars 复制代码
在App(index文件夹)里添加urls.py是将所有属于App的路由都写入该文件中, 
这样更容易管理和区分每个App的路由地址, 而django3文件夹的urls.py是将每个App的urls.py统一管理.

这种路由设计模式是Django常用的, 其工作原理如下:
(1) 运行Django项目时, Django从Django3文件夹的urls.py找到各个App所定义的路由信息, 生成完整的路由列表.
(2) 当用户在浏览器上访问某个路由地址时, Django就会收到该用户的请求信息.
(3) Django从当前请求信息获取路由地址, 并在路由列表里匹配相应的路由信息,
   再执行路由信息所指向的视图函数(或视图类), 从而完成整个请求响应过程.
   
在这种路由设计模式下, MyDjango文件夹的urls.py代码如下所示:
python 复制代码
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    # 指向内置Admin后台系统的路由文件sites.py
    path('admin/', admin.site.urls),
    # 指向index的路由文件urls.py
    path('', include('index.urls')),
]
handlebars 复制代码
Django3文件夹的urls.py定义两条路由信息, 分别是Admin站点管理和首页地址(index). 
其中, Admin站点管理在创建项目时已自动生成, 一般情况下无须更改; 首页地址是指index文件夹的urls.py.
django3文件夹的urls.py的代码解释如下:
handlebars 复制代码
● from django.contrib import admin: 导入内置Admin功能模块.
● from django.urls import path, include: 导入Django的路由函数模块.
● urlpatterns: 代表整个项目的路由集合, 以列表格式表示, 每个元素代表一条路由信息.
● path('admin/', admin.site.urls): 设定Admin的路由信息.
  'admin/'代表 127.0.0.1:8000/admin 的路由地址, admin后面的斜杠是路径分隔符, 其作用等同于计算机中文件目录的斜杠符号;
  admin.site.urls指向内置Admin功能所自定义的路由信息,
  可以在Python目录Lib\site-packages\django\contrib\admin\sites.py找到具体定义过程.
● path('', include('index.urls')): 路由地址为'\', 即: 127.0.0.1:8000 , 通常是网站的首页;
  路由函数include是将该路由信息分发给index的urls.py处理.
  
由于首页地址分发给index的urls.py处理, 因此下一步需要对index的urls.py编写路由信息, 代码如下:
python 复制代码
# index的urls.py
from django.urls import path
from . import views
urlpatterns = [
    path('', views.index)
]
handlebars 复制代码
index的urls.py的编写规则与django3文件夹的urls.py大致相同, 这是最为简单的定义方法,
此外还可以参考内置Admin功能的路由定义方法.

在index的urls.py导入index的views.py文件, 该文件用于编写视图函数或视图类, 主要用于处理当前请求信息并返回响应内容给用户.
路由信息path('', views.index)的views.index是指视图函数index处理网站首页的用户请求和响应过程.

因此, 在index的views.py中编写index函数的处理过程, 代码如下:
python 复制代码
from django.shortcuts import render

def index(request):
    value = 'This is test!'
    print(value)
    return render(request, 'index.html')
handlebars 复制代码
index函数必须设置一个参数, 参数命名不固定, 但常以request进行命名, 代表当前用户的请求对象, 
该对象包含当前请求的用户名, 请求内容和请求方式等.
视图函数执行完成后必须使用return将处理结果返回, 否则程序会抛出异常信息.

启动MyDjango项目, 在浏览器里访问: 127.0.0.1:8000 , 运行结果如图3-2所示.
handlebars 复制代码
从上述例子看到, 当启动Django3项目时, Django会从配置文件settings.py读取属性ROOT_URLCONF的值,
默认值为django3.urls, 其代表django3文件夹的urls.py文件, 然后根据ROOT_URLCONF的值来生成整个项目的路由列表.

路由文件urls.py的路由定义规则是相对固定的, 路由列表由urlpatterns表示, 每个列表元素代表一条路由.
路由是由Django的path函数定义的, 该函数第一个参数是路由地址, 第二个参数是路由所对应的处理函数(视图函数或视图类),
这两个参数是路由定义的必选参数.

3.1.2 Django 1.X路由定义

handlebars 复制代码
3.1.1小节介绍了Django 2以上版本的路由定义规则, 而Django 1版本是不支持这种定义方式的.
虽然本书讲解的是Django 3, 但是到目前为止,
Django 3还能兼容Django 1的路由定义规则, 因此读者也有必要掌握Django 1的路由定义规则.

Django 1版本的路由定义规则是由Django的url函数定义的, 
url函数的第一个参数是路由地址, 第二个参数是路由所对应的处理函数, 这两个参数也是必选参数.

而路由地址需设置路由符号^和$. 
^代表当前路由地址的相对路径; $代表当前路由地址的终止符.
handlebars 复制代码
我们分别改写项目的django3文件夹和index文件夹的urls.py文件, 使用Django 1版本的路由定义规则, 代码如下:
python 复制代码
# MyDjango文件夹的urls.py
from django.contrib import admin
from django.conf.urls import url
from django.urls import include


urlpatterns = [
    # 指向内置Admin后台系统的路由文件sites.py
    url('admin/', admin.site.urls),
    # 指向index的路由文件urls.py
    url('^', include('index.urls')),
]
python 复制代码
# index文件夹的urls.py
from django.conf.urls import url
from . import views

urlpatterns = [
    url('^$', views.index),
    url('^new/$', views.new)
]
handlebars 复制代码
index文件夹的urls.py定义两条路由信息, 因此需要在index文件夹的views.py里定义相应的视图函数, 代码如下:
python 复制代码
# index的views.py
from django.shortcuts import render
from django.http import HttpResponse

def index(request):
    return render(request, 'index.html')

def new(request):
    return HttpResponse('This is new page')
handlebars 复制代码
在django3文件夹的urls.py文件里, 
url('^', include('index.urls'))的路由符号^代表当前路由地址的相对地址, 即: http://127.0.0.1:8000.
该路由使用了Django的路由函数include, 它将路由交给index文件夹的urls.py完成路由定义.

index文件夹的urls.py可以在: http://127.0.0.1:8000 的基础上定义多条路由.
上述代码定义了两条路由地址(127.0.0.1:8000和127.0.0.1:8000/new/), 分别对应视图函数的index和new.
路由符号$代表路由地址的终止位置, 如果没有终止符号$, 那么在浏览器输入任意地址都能成功访问该路由地址.
以url('^new/$', views.new)为例, 若将终止符号$去掉, 则在浏览器访问: http://127.0.0.1:8000/new/XXX 时,
其中XXX的内容不限, 这样的路由地址都符合url('^new/',views.new)定义规则, 从而执行视图函数new完成响应过程, 如图3-3所示.
handlebars 复制代码
图3-3 终止符的作用
handlebars 复制代码
综上所述, Django 1的路由规则是使用Django的url函数实现路由定义, 并且路由地址设有路由符号^和$,
读者需要区分路由符号^和$的作用与使用规则, 在某程度上, 它比Django 2版本复杂并且代码可读性差,
因此Django 1的路由规则在Django 2版本里将逐渐淘汰.

3.1.3 路由变量的设置

handlebars 复制代码
在日常开发过程中, 有时一个路由可以代表多个不同的页面, 如编写带有日期的路由, 
若根据前面的编写方式, 按一年计算, 则需要开发者编写365个不同的路由才能实现, 这种做法明显是不可取的.

因此, Django在定义路由时, 可以对路由设置变量值, 使路由具有多样性.
路由的变量类型有字符类型, 整型, slug和uuid, 最为常用的是字符类型和整型, 各个类型说明如下:
● 字符类型: 匹配任何非空字符串, 但不含斜杠. 如果没有指定类型, 就默认使用该类型.
● 整型: 匹配0和正整数.
● slug: 可理解为注释, 后缀或附属等概念, 常作为路由的解释性字符.
  可匹配任何ASCII字符以及连接符和下画线, 能使路由更加清晰易懂.
  比如网页的标题是'13岁的孩子', 其路由地址可以设置为'13-sui-de-hai-zi'.
● uuid: 匹配一个uuid格式的对象. 为了防止冲突, 规定必须使用'-'并且所有字母必须小写, 
  例如: 075194d3-6885-417e-a8a8-6c931e272f00.
handlebars 复制代码
根据上述变量类型, 在MyDjango项目的index文件夹的urls.py里新定义路由, 并且带有字符类型, 整型和slug的变量, 代码如下:
python 复制代码
# index的urls.py
from django.urls import path
from . import views

urlpatterns = [
    # 添加带有字符类型、整型和slug的路由
    path('<year>/<int:month>/<slug:day>', views.myvariable)
]
handlebars 复制代码
在路由中, 使用变量符号'<>'可以为路由设置变量.
在括号里面以冒号划分为两部分, 冒号前面代表的是变量的数据类型, 冒号后面代表的是变量名, 
变量名可自行命名, 如果没有设置变量的数据类型, 就默认为字符类型.

上述代码设置了3个变量, 分别是<year>, <int:month>和<slug:day>, 变量说明如下:
● <year>: 变量名为year, 数据格式为字符类型, 与<str:year>的含义一样.
● <int:month>: 变量名为month, 数据格式为整型.
● <slug:day>: 变量名为day, 数据格式为slug.
handlebars 复制代码
在上述新增的路由中, 路由的处理函数为myvariable, 因此在index的views.py中编写视图函数myvariable的处理过程, 代码如下:
python 复制代码
# views.py的myvariable函数
from django.http import HttpResponse

def myvariable(request, year, month, day):
    return HttpResponse(str(year) + '/' + str(month) + '/'+str(day))
handlebars 复制代码
视图函数myvariable有4个参数, 
其中参数year, month和day的参数值分别来自路由地址所设置的变量<year>, <int:month>和<slug:day>.
启动项目, 在浏览器上输入: 127.0.0.1:8000/2018/05/01 , 运行结果如图3-4所示.
handlebars 复制代码
图3-4 运行结果
handlebars 复制代码
从上述例子可以看出, 路由地址所设置的变量可在视图函数里以参数的形式使用, 
视图函数myvariable将路由地址的变量值作为响应内容(2018/5/01)输出到网页上,
由于路由地址的3个变量类型分别是字符类型, 整型和slug, 因此路由地址的05转化为数字5.
(解释: URL本质上是一个文本字符串, 无论是字母, 数字还是其他特殊字符, 它们都是作为字符串的一部分来处理的.
月份的字符串05转为整形后变成5, 日期的字符串01被转为解释性字符类型后还是05.)
handlebars 复制代码
如果浏览器输入的路由地址与其变量类型不相符, Django就会提示Page not found, 比如将路由地址的05改为字母AA, 如图3-5所示.
(解释: AA无法转换为整形.)
handlebars 复制代码
图3-5 运行结果
handlebars 复制代码
路由的变量和视图函数的参数要一一对应, 如果视图函数的参数与路由的变量对应不上, 那么程序会抛出参数不相符的报错信息.
比如路由地址里设置了3个变量, 而视图函数myvariable仅设置两个路由变量的参数year和month, 
当再次访问网页的时候, 浏览器就会提示报错信息, 如图3-6所示.
python 复制代码
# views.py的myvariable函数
from django.http import HttpResponse

def myvariable(request, year, month):
    return HttpResponse(str(year) + '/' + str(month))
handlebars 复制代码
图3-6 运行结果	
handlebars 复制代码
除了在路由地址设置变量外, Django还支持在路由地址外设置变量(路由的可选变量).
我们在index的urls.py和views.py中分别新增路由和视图函数, 代码如下:
python 复制代码
# index的urls.py
from django.urls import path
from . import views


urlpatterns = [
    # 添加带有字符类型, 整型和slug的路由
    path('<year>/<int:month>/<slug:day>',views.myvariable),
    # 添加路由地址外的变量month
    path('', views.index, {'month': '2019/10/10'})
]
python 复制代码
# index的views.py
from django.http import HttpResponse

def myvariable(request, year, month, day):
    return HttpResponse(str(year)+'/'+str(month)+'/'+str(day))

def index(request, month):
    return HttpResponse('这是路由地址之外的变量: '+month)
handlebars 复制代码
从上述代码可以看出, 路由函数path的第3个参数是{'month': '2019/10/10'}, 该参数的设置规则如下:
● 参数只能以字典的形式表示.
● 设置的参数只能在视图函数中读取和使用.
● 字典的一个键值对代表一个参数, 键值对的键代表参数名, 键值对的值代表参数值.
● 参数值没有数据格式限制, 可以为某个实例对象, 字符串或列表(元组)等.
视图函数index的参数必须对应字典的键, 如果字典里设置两对键值对, 视图函数就要设置相应的函数参数,
否则在浏览器上访问的时候就会提示图3-6的报错信息.
最后重新运行MyDjango项目, 在浏览器上访问: 127.0.0.1:8000, 运行结果如图3-7所示.
handlebars 复制代码
图3-7 运行结果

3.1.4 正则表达式的路由定义

handlebars 复制代码
从3.1.3小节的路由设置得知, 路由地址变量分别代表日期的年, 月, 日, 其变量类型分别是字符类型, 整型和slug,
因此在浏览器上输入: 127.0.0.1:8000/AAAA/05/01也是合法的, 但这不符合日期格式要求.
handlebars 复制代码
为了进一步规范日期格式, 可以使用正则表达式限制路由地址变量的取值范围.
在index文件夹的urls.py里使用正则表达式定义路由地址, 代码如下:
python 复制代码
# index的urls.py
from django.urls import re_path
from . import views

urlpatterns = [
    re_path('(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2}).html',
            views.mydate)
]
handlebars 复制代码
路由的正则表达式是由路由函数re_path定义的, 其作用是对路由变量进行截取与判断, 正则表达式是以小括号为单位的,
每个小括号的前后可以使用斜杠或者其他字符将其分隔与结束.
以上述代码为例,, 分别将变量year, month和day以斜杠隔开, 每个变量以一个小括号为单位, 
在小括号内, 可分为3部分, 以(?P<year>[0-9]{4})为例。
● ?P是固定格式, 字母P必须为大写.
● <year>为变量名.
● [0-9]{4}是正则表达式的匹配模式, 代表变量的长度为4, 只允许取0~9的值.

上述路由的处理函数为mydate函数, 因此还需要在index的views.py中编写视图函数mydate, 代码如下:
python 复制代码
# views.py的mydate函数
from django.http import HttpResponse

def mydate(request, year, month, day):
    return HttpResponse(str(year)+'/'+str(month)+'/'+str(day))
handlebars 复制代码
启动MyDjango项目, 在浏览器上输入: 127.0.0.1:8000/2018/05/01.html 即可查看运行结果, 如图3-8所示.
handlebars 复制代码
图3-8 运行结果
handlebars 复制代码
路由地址的末端设置了'.html', 这是一种伪静态URL技术, 可将网址设置为静态网址, 用于SEO搜索引擎的爬取, 如百度, 谷歌等.
此外, 在末端设置'.html'是为变量day设置终止符, 
假如末端没有设置'.html', 在浏览器上输入无限长的字符串, 路由也能正常访问, 如图3-9所示.
handlebars 复制代码
图3-9 运行结构

3.2 命名空间与路由命名

handlebars 复制代码
网站规模越大, 其网页的数量就会越多, 如果网站的网址过多, 在管理或者维护上就会存在一定的难度.
Django为了更好地管理和使用路由, 可以为每条路由设置命名空间或路由命名.

3.2.1 命名空间namespace

handlebars 复制代码
在MyDjango项目里创建新的项目应用user, 并且在user文件夹里创建urls.py文件,
然后在配置文件settings.py的INSTALLED_APPS中添加项目应用user,
使得Django在运行的时候能够识别项目应用user, 项目结构如图3-10所示.
apl 复制代码
# 1. 使用代码创建user应用
PS D:\django3> python manage.py startapp user 
python 复制代码
# 2. 在配置文件中添加user应用
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'index.apps.IndexConfig',
    'user',
]
handlebars 复制代码
3. 在urls目录下创建urls.py文件.
handlebars 复制代码
图3-10 目录结构
handlebars 复制代码
在MyDjango文件夹的urls.py中重新定义路由信息, 分别指向index文件夹的urls.py和user文件夹的urls.py, 代码如下:
python 复制代码
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    # 指向内置Admin后台系统的路由文件sites.py
    path('admin/', admin.site.urls),
    
    # 指向index的路由文件urls.py
    path('', include(('index.urls', 'index'),
                    namespace='index')),
    # 指向user的路由文件urls.py
    path('user/', include(('user.urls', 'user'),
                         namespace='user'))
]
handlebars 复制代码
上述代码中, 新增的路由使用Django路由函数include并且分别指向index文件夹的urls.py和user文件夹的urls.py.
在函数include里设置了可选参数namespace, 该参数是函数include特有的参数, 这就是Django设置路由的命名空间.
handlebars 复制代码
路由函数include设有参数arg和namespace,
参数arg: 指向项目应用App的urls.py文件, 其数据格式以元组或字符串表示;
可选参数namespace: 是路由的命名空间. 若要对路由设置参数namespace, 则参数arg必须以元组格式表示, 并且元组的长度必须为2.
元组的元素说明如下: 
● 第一个元素为项目应用的urls.py文件, 比如('index.urls', 'index')的'index.urls', 这是代表项目应用index的urls.py文件.
● 第二个元素可以自行命名, 但不能为空, 一般情况下是以项目应用的名称进行命名,
  如('index.urls', 'index')的'index'是以项目应用index进行命名的.
  
如果路由设置参数namespace并且参数arg为字符串或元组长度不足2的时候, 
在运行django3的时候, Django就会提示错误信息, 如图3-11所示.
handlebars 复制代码
图3-11 报错信息
handlebars 复制代码
下一步是分析路由函数include的作用, 它是将当前路由分配到某个项目应用的urls.py文件,
而项目应用的urls.py文件可以设置多条路由, 这种情况类似计算机上的文件夹A, 并且该文件夹下包含多个子文件夹,
而Django的命名空间namespace相当于对文件夹A进行命名.

假设项目路由设计为: 在django3文件夹的urls.py新定义4条路由,
每条路由都使用路由函数include, 并分别命名为A, B, C, D, 每条路由对应某个项目应用的urls.py文件,
并且每个项目应用的urls.py文件里定义若干条路由.

根据上述的路由设计模式, 将django3文件夹的urls.py视为计算机上的D盘, 
在D盘下有4个文件夹, 分别命名为A, B, C, D, 每个项目应用的urls.py所定义的若干条路由可视为这4个文件夹里面的文件.
在这种情况下, Django的命名空间namespace等同于文件夹A, B, C, D的文件名.
handlebars 复制代码
Django的命名空间namespace可以为我们快速定位某个项目应用的urls.py,
再结合路由命名name就能快速地从项目应用的urls.py找到某条路由的具体信息, 这样就能有效管理整个项目的路由列表.
(命名空间在总路由处为子路由设置了一个独特的标签或名称, 使得在整个应用程序中都可以通过这个标签或名称来引用和管理这些子路由.)
handlebars 复制代码
有关路由函数include的定义过程, 可以在Python安装目录下找到源码(Lib\site-packages\django\urls\conf.py)进行解读.

3.2.2 路由命名name

handlebars 复制代码
回顾3.2.1小节, 我们在django3文件夹的urls.py重新定义路由,
两条路由都使用路由函数include并且分别指向index文件夹的urls.py和user文件夹的urls.py,
命名空间namespace分别为index和user.
在此基础上, 我们在index文件夹的urls.py和user文件夹的urls.py中重新定义路由, 代码如下:
python 复制代码
# index文件夹的urls.py
from django.urls import re_path, path
from . import views

urlpatterns = [
    re_path('(?P<year>[0-9]{4}).html', views.mydate,name='mydate'),
    path('', views.index, name='index')
]
python 复制代码
# user文件夹的urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('index', views.index, name='index'),
    path('login', views.userLogin, name='userLogin')
]
handlebars 复制代码
每个项目应用的urls.py都定义了两条路由, 每条路由都由相应的视图函数进行处理,
因此在index文件夹的views.py和user文件夹的views.py中定义视图函数, 代码如下:
python 复制代码
# index文件夹的views.py
from django.http import HttpResponse
from django.shortcuts import render

def mydate(request, year):
    return HttpResponse(str(year))

def index(request):
    return render(request, 'index.html')
python 复制代码
# user文件夹的views.py
from django.http import HttpResponse

def index(request):
    return HttpResponse('This is userIndex')

def userLogin(request):
    return HttpResponse('This is userLogin')
handlebars 复制代码
运行程序(设置命名空间后,不同的命名空间内可以有相同的URL名称): 
输入地址: http://127.0.0.1:8000 访问index下的主页.
输入地址: http://127.0.0.1:8000/2018.html 访问index下的mydate页面.

输入地址: http://127.0.0.1:8000/user/index 访问user下的主页.
输入地址: http://127.0.0.1:8000/user/login 访问user下的登录页面.
handlebars 复制代码
项目应用index和user的urls.py所定义的路由都设置了参数name, 这是对路由进行命名, 它是路由函数path或re_path的可选参数.
从3.2.1小节的例子得知, 项目应用的urls.py所定义的若干条路由可视为D盘下的某个文件夹里的文件,
而文件夹的每个文件的命名是唯一的, 路由命名name的作用等同于文件夹里的文件名.

如果路由里使用路由函数include, 就可以对该路由设置参数name, 
因为路由的命名空间namespace是路由函数include的可选参数, 而路由命名name是路由函数path或re_path的可选参数,
两者隶属于不同的路由函数, 因此可在同一路由里共存.
一般情况下, 使用路由函数include就没必要再对路由设置参数name, 尽管设置了参数name, 但在实际开发中没有实质的作用.
handlebars 复制代码
从index的urls.py和user的urls.py的路由可以看出, 不同项目应用的路由命名是可以重复的,
比如项目应用index和user皆设置了名为index的路由, 这种命名方式是合理的.

在同一个项目应用里, 多条路由是允许使用相同的命名的, 但是这种命名方式是不合理的, 
因为在使用路由命名来生成路由地址的时候, Django会随机选取某条路由, 这样会为项目开发带来不必要的困扰.
handlebars 复制代码
综上所述, Django的路由命名name是对路由进行命名的, 
其作用是在开发过程中可以在视图或模板等其他功能模块里使用路由命名name来生成路由地址.

在实际开发过程中, 我们支持使用路由命名, 
因为网站更新或防止爬虫程序往往需要频繁修改路由地址, 倘若在视图或模板等其他功能模块里使用路由地址, 
当路由地址发生更新变换时, 这些模块里所使用的路由地址也要随之修改, 这样就不利于版本的变更和维护; 
相对而言, 如果在这些功能模块里使用路由命名来生成路由地址, 就能避免路由地址的更新维护问题.

3.3 路由的使用方式

handlebars 复制代码
路由为网站开发定义了具体的网址, 不仅如此, 它还能被其他功能模块使用, 
比如视图, 模板, 模型, Admin后台或表单等, 本节将讲述如何在其他功能模块中优雅地使用路由.

3.3.1 在模板中使用路由

handlebars 复制代码
通过前面的学习, 相信读者对Django的路由定义规则有了一定的掌握.
路由经常在模板和视图中频繁使用, 举个例子, 
我们在访问爱奇艺首页的时候, 网页上会有各种各样的链接地址, 通过单击这些链接地址可以访问其他网页, 如图3-12所示.
handlebars 复制代码
图3-12 爱奇艺首页
handlebars 复制代码
如果将图3-12当成一本书, 爱奇艺首页可作为书的目录, 通过书的目录就能快速找到我们需要阅读的内容;
同理, 网站首页的功能也是如此, 它的作用是将网站所有的网址一并显示.

从网站开发的角度分析, 网址代表路由, 若想将项目定义的路由显示在网页上, 则要在模板上使用模板语法来生成路由地址.
Django内置了一套模板语法, 它能将Python的语法转换成HTML语言, 然后通过浏览器解析HTML语言并生成相应的网页内容.
handlebars 复制代码
将当前项目django3关闭, 之后将所有项目移走, 重新创建项目MyDjango, 创建index应用, 并在templates目录下创建index.html.
handlebars 复制代码
打开MyDjango项目, 该项目仅有一个项目应用文件夹index和模板文件夹templates,
在index文件夹和模板templates文件夹中分别添加urls.py文件和index.html文件,
切勿忘记在配置文件settings.py中添加index文件夹和templates文件夹的配置信息.
项目结构如图3-13所示.
handlebars 复制代码
图3-13 目录结构
handlebars 复制代码
项目环境搭建成功后, 在MyDjango文件夹的urls.py中使用路由函数path和include定义项目应用文件夹index的路由, 代码如下:
python 复制代码
# MyDjango的urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    # 指向内置Admin后台系统的路由文件sites.py
    path('admin/', admin.site.urls),
    # 指向index的路由文件urls.py
    path('', include('index.urls')),
]
handlebars 复制代码
在项目应用index里, 分别在urls.py和views.py文件中定义路由和视图函数;
并且在模板文件夹templates的index.html文件中编写模板内容, 代码如下:
python 复制代码
# index的urls.py
from django.urls import path
from . import views

urlpatterns = [
    # 添加带有字符类型、整型和slug的路由
    path('<year>/<int:month>/<slug:day>', views.mydate, name='mydate'),
    # 定义首页的路由
    path('', views.index)
]
python 复制代码
# index的views.py
from django.http import HttpResponse
from django.shortcuts import render


def mydate(request, year, month, day):
    return HttpResponse(str(year) + '/' + str(month) + '/' + str(day))


def index(request):
    return render(request, 'index.html')
html 复制代码
<!-- templates的index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello World</title>
</head>
<body>
<span>Hello World!!</span>
<br>
<a href="{% url 'mydate' '2019' '01' '10' %}">查看日期</a>
</body>
</html>
handlebars 复制代码
项目应用index的urls.py和views.py文件的路由和视图函数定义过程不再详细讲述.
我们分析index.html的模板内容, 模板使用了Django内置的模板语法url来生成路由地址, 模板语法url里设有4个不同的参数, 其说明如下:
● mydate: 代表命名为mydate的路由, 即index的urls.py设有字符类型, 整型和slug的路由.
● 2019: 代表路由地址变量year, 它与mydate之间使用空格隔开.
● 01: 代表路由地址变量month, 它与2019之间使用空格隔开.
● 10: 代表路由地址变量day, 它与01之间使用空格隔开.
handlebars 复制代码
模板语法url的参数设置与路由定义是相互关联的, 具体说明如下:
● 若路由地址存在变量, 则模板语法url需要设置相应的参数值, 参数值之间使用空格隔开.
● 若路由地址不存在变量, 则模板语法url只需设置路由命名name即可, 无须设置额外的参数.
● 若路由地址的变量与模板语法url的参数数量不相同, 则在浏览器访问网页的时候会提示NoReverseMatch at的错误信息, 如图3-14所示.
handlebars 复制代码
图3-14 报错信息
handlebars 复制代码
从路由定义与模板语法url的使用对比发现, 路由所设置的变量month与模板语法url的参数'01'是不同的数据类型, 这种写法是允许的, 
因为Django在运行项目的时候, 会自动将模板的参数值转换成路由地址变量的数据格式.
运行MyDjango项目, 在浏览器访问127.0.0.1:8000并单击'查看日期'链接, 网页内容如图3-15所示.
handlebars 复制代码
图3-15 网页内容
handlebars 复制代码
上述例子中, MyDjango文件夹的urls.py在使用函数include定义路由时并没有设置命名空间namespace.
若设置了命名空间namespace, 则模板里使用路由的方式有所变化.
下面对MyDjango文件夹urls.py和模板文件夹templates的index.html代码进行修改:
python 复制代码
# MyDjango文件夹的urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    # 指向内置Admin后台系统的路由文件sites.py
    path('admin/', admin.site.urls),
    # 指向index的路由文件urls.py
    # path('', include('index.urls')),
    # 使用命名空间namespace
    path('', include(('index.urls', 'index'), namespace='index')),
]
html 复制代码
<!-- templates的index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello World</title>
</head>
<body>
  <span>Hello World!!</span>
  <br>
  {# <a href="{% url 'mydate' '2019' '01' '10' %}">查看日期</a>#}
  <a href="{% url 'index:mydate' '2019' '01' '10' %}">查看日期</a>
</body>
</html>
handlebars 复制代码
从模板文件index.html可以看出, 若项目应用的路由设有命名空间namespace, 
则模板语法url在使用路由时, 需要在命名路由name前面添加命名空间namespace并且使用冒号隔开, 如'namespace:name'.
若路由在定义过程中使用命名空间namespace, 而模板语法url没有添加命名空间namespace,
则在访问网页时, Django提示如图3-14所示的报错信息.
handlebars 复制代码
当Django模板引擎渲染这个模板时, 它会查找名为'index:mydate'的URL模式, 
并使用提供的参数('2019', '01', '10')来生成最终的URL.
这个生成的URL将替换{% url 'index:mydate' '2019' '01' '10' %}.

3.3.2 反向解析reverse与resolve

handlebars 复制代码
路由除了在模板里使用之外, 还可以在视图里使用.
我们知道Django的请求生命周期是指用户在浏览器访问网页时, Django根据网址在路由列表里查找相应的路由,
再从路由里找到视图函数或视图类进行处理, 将处理结果作为响应内容返回浏览器并生成网页内容.
这个生命周期是不可逆的, 而在视图里使用路由这一过程被称为反向解析.

Django的反向解析主要由函数reverse和resolve实现: 
函数reverse是通过路由命名或可调用视图对象来生成路由地址的; 
函数resolve是通过路由地址来获取路由对象信息的.

以MyDjango项目为例, 项目的目录结构不再重复讲述, 其结构与3.3.1小节的目录结构相同.
在MyDjango文件夹的urls.py和index文件夹的urls.py里定义路由地址, 代码如下:
python 复制代码
# MyDjango文件夹的urls.py
from django.urls import path, include

urlpatterns = [
    # 指向index的路由文件urls.py
    path('', include(('index.urls', 'index'), namespace='index')),
]
python 复制代码
# index文件夹的urls.py
from django.urls import path
from . import views

urlpatterns = [
    # 添加带有字符类型、整型和slug的路由
    path('<year>/<int:month>/<slug:day>', views.mydate, name='mydate'),
    # 定义首页的路由
    path('', views.index, name='index')
]
handlebars 复制代码
上述代码定义了项目应用index的路由信息, 路由命名分别为index和mydate, 路由定义过程中设置了命名空间namespace和路由命名name.
由于反向解析函数reverse和resolve常用于视图(views.py), 模型(models.py)或Admin后台(admin.py)等,
因此在视图(views.py)的函数mydate和index里分别使用reverse和resolve, 代码如下:
python 复制代码
# index文件夹的views.py
from django.http import HttpResponse
from django.shortcuts import reverse
from django.urls import resolve


def mydate(request, year, month, day):
    args = ['2019', '12', '12']
    # 先使用reverse, 再使用resolve
    result = resolve(reverse('index:mydate', args=args))
    print('kwargs:', result.kwargs)
    print('url_name:', result.url_name)
    print('namespace:', result.namespace)
    print('view_name:', result.view_name)
    print('app_name:', result.app_name)
    return HttpResponse(str(year) + '/' + str(month) + '/' + str(day))


def index(request):
    kwargs = {'year': 2010, 'month': 2, 'day': 10}
    args = ['2019', '12', '12']
    # 使用reverse生成路由地址(index是名称空间, mydate是路由模式)
    print(reverse('index:mydate', args=args))
    print(reverse('index:mydate', kwargs=kwargs))
    return HttpResponse(reverse('index:mydate', args=args))
handlebars 复制代码
函数index主要使用反向解析函数reverse来生成路由mydate的路由地址.
为了进一步了解函数reverse, 我们在PyCharm下打开函数reverse的源码文件, 如图3-16所示.
handlebars 复制代码
图3-16 函数reverse
handlebars 复制代码
从图3-16看到, 函数reverse设有必选参数viewname, 其余参数是可选参数, 各个参数说明如下:
● viewname: 代表路由命名或可调用视图对象, 一般情况下是以路由命名name来生成路由地址的.
● urlconf: 设置反向解析的URLconf模块.
  默认情况下, 使用配置文件settings.py的ROOT_URLCONF属性(MyDjango文件夹的urls.py).
● args: 以列表方式传递路由地址变量, 列表元素顺序和数量应与路由地址变量的顺序和数量一致.
● kwargs: 以字典方式传递路由地址变量, 字典的键必须对应路由地址变量名, 字典的键值对数量与变量的数量一致.
● current_app: 提示当前正在执行的视图所在的项目应用, 主要起到提示作用, 在功能上并无实质的作用.
handlebars 复制代码
一般情况下只需设置函数reverse的参数viewname即可, 如果路由地址设有变量, 那么可自行选择参数args或kwargs设置路由地址的变量值.
参数args和kwargs不能同时设置, 否则会提示ValueError报错信息, 如图3-17所示.
handlebars 复制代码
图3-17 ValueError报错信息
handlebars 复制代码
运行MyDjango项目, 在浏览器上访问: 127.0.0.1:8000 , 当前请求将由视图函数index处理,
该函数使用reverse来获取路由命名为mydate的路由地址并显示在网页上, 如图3-18所示.
(解释: reverse函数会查找index名称空间下名为mydate的URL模式, 
并使用提供的参数['2019', '12', '12']来填充URL模式( <year>/<int:month>/<slug:day> )中的占位符. )
handlebars 复制代码
图3-18 网页内容
handlebars 复制代码
从网页内容得知, 路由地址: /2019/12/12 是一个相对路径, 
在Django中, 所有路由地址皆以相对路径表示, 地址路径首个斜杆'/'代表域名(127.0.0.1:8000).
handlebars 复制代码
接下来分析视图函数mydate, 它是在函数reverse的基础上使用函数resolve.
我们在PyCharm里打开函数resolve的源码文件, 如图3-19所示.
handlebars 复制代码
图3-19 函数resolve
handlebars 复制代码
从图3-19可以看到, 函数resolve设有两个参数, 参数path是必选参数, urlconf是可选参数, 参数说明如下:
● path: 代表路由地址, 通过路由地址来获取对应的路由对象信息.
● urlconf: 设置反向解析的URLconf模块.
  默认情况下, 使用配置文件settings.py的ROOT_URLCONF属性(MyDjango文件夹的urls.py).
  
函数resolve是以路由对象作为返回值的, 该对象内置多种函数方法来获取具体的路由信息, 如表3-1所示.
函数方法 说明
func 路由的视图函数对象或视图类对象
args 以列表格式获取路由的变量信息
kwargs 以字典格式获取路由的变量信息
url_name 获取路由命名name
app_name 获取路由函数include的参数arg的第二个元素值
app_names 与app_name功能一致, 但以列表格式表示
namespace 获取路由的命名空间namespace
namespaces 与namespace功能一致, 但以列表格式表示
view_name 获取整个路由的名称, 包括命名空间
handlebars 复制代码
运行MyDjango项目, 在浏览器访问: 127.0.0.1:8000/2019/12/12 , 在PyCharm的下方查看函数resolve的对象信息, 如图3-20所示.
handlebars 复制代码
图3-20 函数resolve的对象信息
handlebars 复制代码
综上所述, 函数reverse和resolve主要是对路由进行反向解析, 通过路由命名或路由地址来获取路由信息.
在使用这两个函数的时候, 需要注意两者所传入的参数类型和返回值的数据类型.

3.3.3 路由重定向

handlebars 复制代码
重定向称为HTTP协议重定向, 也可以称为网页跳转, 它对应的HTTP状态码为301, 302, 303, 307, 308.
简单来说, 网页重定向就是在浏览器访问某个网页的时候, 这个网页不提供响应内容, 而是自动跳转到其他网址, 由其他网址来生成响应内容.
Django的网页重定向有两种方式: 
第一种方式是路由重定向;
第二种方式是自定义视图的重定向.
两种重定向方式各有优点, 前者是使用Django内置的视图类RedirectView实现的, 默认支持HTTP的GET请求;
后者是在自定义视图的响应状态设置重定向, 能让开发者实现多方面的开发需求.
handlebars 复制代码
我们在MyDjango项目里分别讲述Django的两种重定向方式, 在index的urls.py中定义路由turnTo, 其代码如下所示:
python 复制代码
from django.urls import path
from . import views
from django.views.generic import RedirectView

urlpatterns = [
    # 添加带有字符类型、整型和slug的路由
    path('<year>/<int:month>/<slug:day>', views.mydate, name='mydate'),
    # 定义首页的路由
    path('', views.index, name='index'),
    # 设置路由跳转
    path('turnTo', RedirectView.as_view(url='/'), name='turnTo')
]
handlebars 复制代码
在路由里使用视图类RedirectView必须使用as_view方法将视图类实例化, 
参数url用于设置网页跳转的路由地址, '/'代表网站首页(路由命名为index的路由地址).
然后在index的views.py中定义视图函数mydate和index, 代码如下:
python 复制代码
from django.http import HttpResponse
from django.shortcuts import redirect
from django.shortcuts import reverse


def mydate(request, year, month, day):
    return HttpResponse(str(year) + '/' + str(month) + '/' + str(day))


def index(request):
    print(reverse('index:turnTo'))
    return redirect(reverse('index:mydate', args=[2019, 12, 12]))
handlebars 复制代码
视图函数index是使用重定向函数redirect实现网页重定向的, 
这是Django内置的重定向函数, 其函数参数只需传入路由地址即可实现重定向.

运行MyDjango项目, 在浏览器上输入: 127.0.0.1:8000/turnTo , 
发现该网址首先通过视图类RedirectView重定向首页(路由命名为index),
然后在视图函数index里使用重定向函数redirect跳转到路由命名为mydate的路由地址, 如图3-21所示.
handlebars 复制代码
图3-21 网页重定向
handlebars 复制代码
从图3-21看到, 浏览器的开发者工具记录了3条请求信息, 其中名为turnTo的请求信息是我们在浏览器输入的网址,
而名为127.0.0.1的请求信息是网站首页, 两者的HTTP状态码都是302,
说明视图类RedirectView和重定向函数redirect皆能实现网站的重定向.

以视图类RedirectView和重定向函数redirect的功能划分, 两者分别隶属于视图类和视图函数,
有关两者功能的实现过程和源码剖析将会分别在5.1.1小节和4.1.2小节详细讲述.

3.4 本章小结

handlebars 复制代码
路由称为URL, 也可以称为URLconf, 是对可以从互联网上得到的资源位置和访问方法的一种简洁的表示, 是互联网上标准资源的地址.
互联网上的每个文件都有一个唯一的路由, 用于指出网站文件的路径位置.
简单地说, 路由可视为我们常说的网址, 每个网址代表不同的网页.
路由基本编写规则如下(以MyDjango文件夹的urls.py为例):
● from django.contrib import admin: 导入内置Admin功能模块.
● from django.urls import path,include: 导入Django的路由功能模块.
● urlpatterns: 代表整个项目的路由集合, 以列表格式表示, 每个元素代表一条路由信息.
● path('admin/', admin.site.urls): 设定Admin的路由信息.
  其中'admin/'代表127.0.0.1:8000/admin的路由地址,
  admin后面的斜杠是路径分隔符, 其作用等同于计算机文件目录的斜杠符号;
  admin.site.urls指向内置Admin功能所定义的路由信息, 
  可在Python目录Lib\site-packages\django\contrib\admin\sites.py找到具体定义过程.
● path('', include('index.urls')): 路由地址为'\', 即127.0.0.1:8000, 通常是网站的首页;
  路由函数include将该路由信息分发给index的urls.py处理.
  
路由文件urls.py的路由定义规则是相对固定的, 路由列表由urlpatterns表示, 每个列表元素代表一条路由.
路由是由Django的path函数定义的, 该函数的第一个参数是路由地址, 第二个参数是路由所对应的处理函数(视图函数或视图类),
这两个参数是路由定义的必选参数.

路由的变量类型有字符类型, 整型, slug和uuid, 最为常用的是字符类型和整型.
各个类型说明如下:
● 字符类型: 匹配任何非空字符串, 但不含斜杠.
  如果没有指定类型, 就默认使用该类型.
● 整型: 匹配0和正整数.
● slug: 可理解为注释, 后缀或附属等概念, 常作为路由的解释性字符, 
  可匹配任何ASCII字符以及连接符和下画线, 能使路由更加清晰易懂.
  比如网页的标题是'13岁的孩子', 其路由地址可以设置为'13-sui-de-hai-zi'.
● uuid: 匹配一个uuid格式的对象.
  为了防止冲突, 规定必须使用破折号并且所有字母必须小写, 例如: 075194d3-6885-417e-a8a8-6c931e272f00.
  
除了在路由地址设置变量外, Django还支持在路由地址外设置变量(路由的可选变量),
比如在路由函数path或re_path中设置第3个参数, 其内容为{'month': '2019/10/10'}, 
该参数的设置规则如下:
● 参数只能以字典的形式表示.
● 设置的参数只能在视图函数中读取和使用.
● 字典的一个键值对代表一个参数, 键值对的键代表参数名, 键值对的值代表参数值.
● 参数值没有数据格式限制, 可以为某个对象, 字符串或列表(元组)等.

路由的正则表达式是由路由函数re_path定义的, 其作用是对路由变量进行截取与判断, 
正则表达式是以小括号为单位的, 每个小括号的前后可以使用斜杠或者其他字符将其分隔与结束.
以上述代码为例, 分别将变量year, month和day以斜杠隔开, 
每个变量以一个小括号为单位, 在小括号内, 可分为3部分(以(?P<year>[0-9]{4})为例):
● ?P是固定格式, 字母P必须为大写.
● <year>为变量名.
● [0-9]{4}是正则表达式的匹配模式, 代表变量的长度为4, 只允许取0~9的值.

命名空间namespace可以为我们快速定位某个项目应用的urls.py, 
再结合路由命名name就能快速地从项目应用的urls.py找到某条路由的具体信息, 这样就能有效管理整个项目的路由列表.

Django的路由命名name是对路由进行命名, 其作用是在开发过程中可以在视图或模板等其他功能模块里使用路由命名name来生成路由地址.
模板语法url的参数设置与路由定义是相互关联的, 具体说明如下:
● 若路由地址存在变量, 则模板语法url需要设置相应的参数值, 参数值之间使用空格隔开.
● 若路由地址不存在变量, 则模板语法url只需设置路由命名name即可, 无须设置额外的参数.
● 若路由地址的变量与模板语法url的参数数量不相同, 则在浏览器访问网页的时候会提示NoReverseMatch at的错误信息.

Django的反向解析主要由函数reverse和resolve实现,
函数reverse是通过路由命名或可调用视图对象来生成路由地址的;
函数resolve是通过路由地址来获取路由对象信息的.
在使用这两个函数时, 需要注意两者所传入的参数类型和返回值的数据类型.

Django的网页重定向有两种方式: 
第一种方式是路由重定向;
第二种方式是自定义视图的重定向.
两种重定向方式各有优点, 前者是使用Django内置的视图类RedirectView实现的, 默认支持HTTP的GET请求;
后者是在自定义视图的响应状态设置重定向, 能让开发者实现多方面的开发需求.
相关推荐
bug菌¹29 分钟前
滚雪球学Oracle[6.2讲]:Data Guard与灾难恢复
数据库·oracle·data·灾难恢复·guard
一般路过糸.32 分钟前
MySQL数据库——索引
数据库·mysql
Cengineering1 小时前
sqlalchemy 加速数据库操作
数据库
wxin_VXbishe1 小时前
springboot合肥师范学院实习实训管理系统-计算机毕业设计源码31290
java·spring boot·python·spring·servlet·django·php
Cikiss1 小时前
微服务实战——平台属性
java·数据库·后端·微服务
小小不董2 小时前
《Linux从小白到高手》理论篇:深入理解Linux的网络管理
linux·运维·服务器·数据库·php·dba
无敌少年小旋风2 小时前
MySQL 内部优化特性:索引下推
数据库·mysql
柒小毓2 小时前
将excel导入SQL数据库
数据库
bug菌¹2 小时前
滚雪球学Oracle[2.5讲]:数据库初始化配置
数据库·oracle·数据库初始化·初始化配置
一休哥助手2 小时前
Redis 五种数据类型及底层数据结构详解
数据结构·数据库·redis