目录
前言:
针对Django框架的内容,需要展开不同功能的了解,本章节主要学习Django路由层的一些用法,其中包含:路由分配、无名有名分组、反向解析、路由分发、名称空间。大致熟悉一下Django的请求生命周期,那么我们开始本章节的学习吧!
一、Django请求生命周期
整体过程如图所示:
Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口。当然我们也可以称它为:WSGI协议
而wsgiref模块就是python基于WSGI协议开发的服务模块。其支持的并发量不高,但用于我们开发环境足够了,待程序的上线再使用其他的Web服务提高我们运行程序的服务器性能。
二、Django框架路由层
2.1 路由的作用
路由也就是我们在Django内常见的urls.py文件,其作用是:
URL配置(URLconf)就像Django 所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表;你就是以这种方式告诉Django,对于客户端发来的某个URL调用哪一段逻辑代码对应执行
实例:
from django.urls import path
urlpatterns = [
path('index', views.index),
]
index这个路由对应着视图函数中index这个方法。
浏览器输入这个链接,就会响应到index这个函数来执行浏览器发送的请求
2.2 路由配置
我们平常使用路由只给其传递了两个参数:URL、视图函数。但路由其实还有另外两个参数
from django.conf.urls import path
urlpatterns = [
path(url, views视图函数,参数,别名),
]
- url:用户输入的
http://ip+port/...
根据 ip + port/ 后面的内容来选择路由 - views:一个可调用对象,通常为一个视图函数或一个指定视图函数路径的字符串
- 参数:可选的要传递给视图函数的默认参数(字典形式)
- 别名:一个可选的name参数,给路由设置别名。
我们在url参数向也可以规定为正则表达式来匹配浏览器提交的URL,Django1.x内使用的url
函数默认支持这种写法,而目前我们需要使用:re_path
才能达到这种效果
from django.conf.urls import path,re_path
urlpatterns = [
re_path(正则表达式, views视图函数,参数,别名),
]
第一项参数可以写:一个正则表达式字符串
3.3 无名分组
当我们使用正则在定义匹配URL内使用()
表示一个组,它会将这个组内产生的结果,发送给视图函数。
from django.urls import path,re_path
from app01 import views
urlpatterns = [
re_path(r'^articles/2003/$', views.special_case_2003),
re_path(r'^articles/([0-9]{4})/$', views.year_archive),
re_path(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
re_path(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]
注意:
- 若要从URL 中捕获一个值,只需要在它周围放置一对圆括号。
- 不需要添加一个前导的反斜杠,因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。
- 每个正则表达式前面的’r’ 是可选的但是建议加上。它告诉Python 这个字符串是“原始的” —— 字符串中任何字符都不应该转义
- urlpatterns中的元素按照书写顺序从上往下逐一匹配正则表达式,一旦匹配成功则不再继续
示例:
'''
一些请求的例子:
/articles/2005/03/ 请求将匹配列表中的第三个模式。Django 将调用函数views.month_archive(request, '2005', '03')。
/articles/2005/3/ 不匹配任何URL 模式,因为列表中的第三个模式要求月份应该是两个数字。
/articles/2003/ 将匹配列表中的第一个模式不是第二个,因为模式按顺序匹配,第一个会首先测试是否匹配。请像这样自由插入一些特殊的情况来探测匹配的次序。
/articles/2003 不匹配任何一个模式,因为每个模式要求URL 以一个反斜线结尾。
/articles/2003/03/03/ 将匹配最后一个模式。Django 将调用函数views.article_detail(request, '2003', '03', '03')。
'''
如: re_path(r'^articles/([0-9]{4})/$', views.year_archive)
这种路由,我们需要在页面这样输入:http://127.0.0.1:8080/articles/1234/
,其中1234可以匹配上,并且在路由内定义的是一个组,所以它会将这段内容传递给视图函数。
视图函数:
def year_archive(request, ids):
return HttpResponse('捕捉到的数字:' + ids)
有没有很奇怪,有时候我们输入的URL后面并没有携带/
,但是我们路由内定义的是结尾必须是/
,场景如下:
- 浏览器:“向
http://127.0.0.1:8080/index
发送请求” - Django:“哥们,不好意思,你没匹配上我的路由!但是你可以在结尾加上
/
试试” - 浏览器:“重新尝试
http://127.0.0.1:8080/index/
发送请求” - Django:“ok 匹配上了,返回给你数据”。
如果第二次浏览器在结尾补上/
还没有成功则页面抛出异常。
如果强制要求浏览器必须要和路由一毛一样,那么我们可以在settings.py里面进行如下配置:
# 是否开启URL访问地址后面不为/跳转至带有/的路径的配置项
APPEND_SLASH = True
如果在settings.py中设置了 APPEND_SLASH=False,此时我们再请求 http://www.example.com/blog
时就会提示找不到页面。
2.4 有名分组
上面的示例使用简单的、没有命名的正则表达式组(通过圆括号)来捕获URL 中的值并以位置 参数传递给视图。在更高级的用法中,可以使用命名的正则表达式组来捕获URL 中的值并以关键字 参数传递给视图。
在Python 正则表达式中,命名正则表达式组的语法是(?Ppattern),其中name 是组的名称,pattern 是要匹配的模式。
下面是上面实例的重写:
urlpatterns = [
re_path(r'^articles/2003/$', views.special_case_2003),
re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
]
这个实现与前面的示例完全相同,只有一个细微的差别:捕获的值作为关键字参数而不是位置参数传递给视图函数。
前面示例我们可以定义任意形参接收,通过有名分组传递给视图函数的只能通过分组名还获取。
两种分组对应视图函数接收方式:
# 无名分组
re_path(r'^articles/([0-9]{4})/$', views.year_archive)
def year_archive(request,任意形参):
return HttpResponse('捕捉到的数字:' + 任意形参)
# 有名分组
re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive)
def year_archive(request,year): # 必须使用分组名才能接收到
return HttpResponse('捕捉到的数字:' + year)
三、反向解析
3.1 简介
在使用Django 项目时,一个常见的需求是获得URL 的最终形式,以用于嵌入到生成的内容中(视图中和显示给用户的URL等)或者用于处理服务器端的导航(重定向等)。人们强烈希望不要硬编码这些URL(费力、不可扩展且容易产生错误)或者设计一种与URLconf 毫不相关的专门的URL 生成机制,因为这样容易导致一定程度上产生过期的URL。
在需要URL 的地方,对于不同层级,Django 提供不同的工具用于URL 反查:
- 在模板中:使用url 模板标签。
- 在Python 代码中:使用from django.urls import reverse函数
反向解析为的就是防止路由经常变动,这样我们页面的链接、或者函数的重定向就能动态获取到路由可接收的URL。
3.2 普通反向解析
要想达到反向解析也很简单,给路由设置一个别名
from django.urls import path
from blog import views
urlpatterns = [
path('index/', views.index,name='index_name'),
path('login/', views.login)
]
此时如果模板页面需要指向这个URL提交请求的话,且这个接收的URL是不固定的,那么我们就可以使用这个路由个别名来获取它可以接收的URL。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<h1>登录</h1>
<a href="{% url 'index_name' %}">进入首页</a>
</body>
</html>
此时我们页面不管这个别名为:index_name的路由可接收的URL怎么变动,我们都可以直接指向它。
并且,重定向也是可以直接指向到路由别名的。
def login(request):
return redirect('index_name')
但是!如果路由可接收的URL里面存在无名或者有名分组时,我们就不能这么随身所欲了哦
3.3 无名有名分组:反向解析
在Django1.x中,路由中使用的url
作为匹配,并且URL匹配可以使用正则
而到了Django2.x、Django3.x则改为了path
匹配,只有URL完全相同的才能匹配上。
但是Django2.x、Django3.x也可以使用URL匹配使用正则,那就需要使用:re_path
urls.py
from django.urls import path,re_path
from blog import views
urlpatterns = [
re_path('index/(?P<ids>[0-9])', views.index,name='index_name'),
path('login/', views.login)
]
此时我们的HTML文件如果要指定某个路由,但是这个路由还必须要接收一个参数,那么我们只能在后面手动指定了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<h1>登录</h1>
<a href="{% url 'index_name' 1 %}">进入首页</a>
</body>
</html>
而视图函数如要要动态指向这个路由可接收的URL的话,同时也要向它传递一个参数,将整个URL给补全:
def index(request,ids):
return render(request,'inde.html')
def login(request):
# reverse可以根据别名找到路由可接收的URL,args则是将URL需要的额外参数填了进去
url = reverse('index_name',args=(1,))
return redirect(url)
无名分组与有名分组不建议混合使用:
url('^index/(\d+)/(?P<id>[a-z])/$') # 不建议
但是可以单个配对使用:
url('^index/(?P<id>[0-9])/(?P<eng>[a-z])/$')
上序操作归总:
无名分组的URL:
路由:url(r'^index/(\d+)/',views.index,name='index_name')
后端:reverse('index_name',args=(1,)) # 只要给个数字即可
前端:<a href="{% url 'index_name' 1 %}"></a> # 只要给个数字即可
有名分组的URL:
路由:url(r'^index/(?P<id>\d+)/',views.index,name='index_name')
后端:reverse('index_name',kwargs={'id':123}) # 只要给个数字即可
前端:<a href="{% url 'index_name' id=666 %}"></a> # 只要给个数字即可
在后端与前端加上关键字是为了更好辨识而已
总结
无名有名都可以使用一种(无名)反向解析的形式
一般开发项目使用的路径都不会使用很绝对的,都需要动态获取路径来进行自动更新,动态解析静态文件接口、反向解析路由都是很常用的。
四、路由分发
4.1 简介
django是专注于开发应用的,当一个django项目特别庞大的时候所有的路由与视图函数映射关系全部写在总的urls.py很明显太冗余不便于管理,其实django中的每一个app应用都可以有自己的urls.py、static文件夹、templates文件夹
,基于上述特点,使用django做分组开发非常的简便。
可以分开写多个app应用,最后再汇总到一个空的Django项目然后使用路由分发将多个app应用关联起来。
未使用路由分发前:所有请求都由主路由转发到对应视图函数,全部都在一个urls.py文件内。
使用路由分发后:将请求的URL由主路由转发到其它负责这个URL的子路由上面去。每个子路由文件都应该存放在对应的app应用下。
那么我们来了解一下路由分发是如何实现的吧!
4.2 路由分发实现
首先我们创建一个新的Django项目,然后创建两个app应用,保持如下目录结构:
别忘了配置app应用的路径到settings.py里面
templates文件夹路径也需要配置一下
此时我们开始配置主路由,分配URL请求转发给哪个子路由执行;主路由在我们的Django项目文件夹内urls.py
from django.contrib import admin
from django.urls import path,include
from app01 import urls as app01_urls
from app02 import urls as app02_urls # 增加别名是因为我们导入的两个urls名称相同
urlpatterns = [
path('admin/', admin.site.urls),
path('home/', include(app01_urls)), # 将ip+port/home/ 以这些为起始的请求交给app01>urls.py路由处理
path('userinfo/', include(app02_urls)) # 将ip+port/userinfo/ 以这些为起始的请求交给app02>urls.py路由处理
]
如果使用的是url作为路由的话,切记路由规则结尾不能使用$
此时我们分别配置两个应用下的子路由、视图函数、模板文件。
app01>urls.py
from django.urls import path
from app01 import views
urlpatterns = [
path('index/', views.index),
]
app01>templates>index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<h1>This Is App01 Index</h1>
</body>
</html>
app01>views.py
from django.shortcuts import render
def index(request):
return render(request,'index.html')
app02>urls.py
from django.urls import path
from app02 import views
urlpatterns = [
path('login/', views.login)
]
app02>templates>login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<h1>This Is App02 Login</h1>
</body>
</html>
app02>templates>views.py
from django.shortcuts import render
def login(request):
return render(request,'login.html')
此时已经完成了所有配置,那么开始根据我们配置的访问:
注意:port一定要是本机的Django服务端口。
上面主路由配置难免不够简单,随着子路由越来越多,导入的模块也越来越多了,继续缩短写法!
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
path('home/', include('app01.urls')), # 将ip+port/home/ 以这些为起始的请求交给app01>urls.py路由处理
path('userinfo/', include('app02.urls')) # 将ip+port/userinfo/ 以这些为起始的请求交给app02>urls.py路由处理
]
当一个Django项目存在多个app应用,使用路由分法是很有必要的,减少了单个文件代码冗余,更便于管理。
五、名称空间
其实每个路由都可以有属于自己的名称空间,只是目前我们并没有使用到而已。请见如下例子:
如果当多个应用在反向解析的时候出现了路由别名冲突的情况,那么就会无法识别。
这些HTML文件向根据路由别名来动态指向URL,但是此时别名冲突了,我们该如何解决呢
<a href={% url 'index_name' %}>app01应用</a>
<a href={% url 'index_name' %}>app02应用</a>
解决方式一:名称空间,总路由内定义
urlpatterns = [
path('admin/', admin.site.urls),
path('home/', include('app01.urls',namespace='app01')),
path('userinfo/', include('app02.urls',namespace='app02'))
]
那么HTML文件可以根据主路由设置的名称空间,正确找到其下面的子路由的别名;
<a href={% url 'app01:index_name' %}>app01应用</a>
<a href={% url 'app02:index_name' %}>app02应用</a>
reverse解析也可以这样找到某个主路由下面的子路由别名,获取其路由规则
reverse('app01:index_name')
解决方式2:别名不能冲突(加上自己应用名作为前缀),在子路由内定义
# app01>urls.py
path('login/', views.login,name='app01_index_name')
# app02>urls.py
path('index/', views.index,name='app02_index_name')
但是更推荐使用方式一,因为每次只需要加上名称空间作为前缀就可以找到其下面对应的子路由别名。更主要的是:少写很多字符
如果本文对您有帮助,别忘一键3连,您的支持就是笔者最大的鼓励,感谢阅读!
下一章节传送门:FBV、CBV源码解析、settings源码解析
技术小白记录学习过程,有错误或不解的地方请指出,如果这篇文章对你有所帮助请
点赞 收藏+关注
子夜期待您的关注,谢谢支持!