django之路由层
django之路由层
路由层,主要是路由地址与视图函数的映射关系,如果将网站比作一本书,那么路由就相当于这本书的目录。
路由匹配
在目前的主流版本2.x及以上版本中,我们默认采取path函数记录映射关系,
urlpatterns = [
path('admin/', admin.site.urls),
path('', views.home_page),
path('article/<str:info>/', views.article_page),
path('register/', views.register_page),
path('login/', views.login_page),
]
path函数的第一个参数就是预设的各种路由,当我们在网页中输入网址,我们浏览器会将网址中路由解析出来依次与我们预设的路由比对,如果比对成功会去path()的第二个参数所对应的视图函数中(自动传入request对象作为第一个实参)执行并返回一个界面。
如果没有这么一个界面,那么django也提供了默认返回的界面如下:
这个网页会提示我们没有找到对应的网页,并且提示我们为什么找不到。
如这里就是没有提前配置我们胡乱输入的路由asef
。
django1.x版本默认使用的配置对应函数的方式是
url("", views.func)
,其第一个参数是以正则表达式的方式匹配我们的路由的。
路由匹配小优化
在django的任何版本中,如果我们在输入路由时没有在后面跟/,那么在匹配不到路由时,会加上斜杠再请求匹配一遍。
验证:我们预设的路由中有'admin/',那么我们只输入路由中只输入admin,观察其网络请求的情况:
这个路由匹配优化可以在配置文件中设置取消APPEND_SLASH = False
,不过一般不会这样折磨用户。
转换器
正常情况下很多网站都会有很多相似的网址,如果我们每一个都单独开设路由不合理。
django2.X版本及以上提供了动态匹配转换器:path('article/<str:info>/', views.article_page)
其中用小尖括号括起来的<str:info>
就可以捕捉路由中这个位置的所有字符串,并以关键字参数的形式传送到对应的功能函数中,这种将一类路由都分组到一个路由配置的方式又称作有名分组。
我们定义以下视图函数来测试一下参数效果:
def article_page(request, info):
print(info)
return HttpResponse(f'article_{info}展示')
注意以下易错点
转换器所捕捉的路由会作为关键字参数传入功能函数,于是:
- 视图函数需要提前定义对应数量的形参
- 视图函数所定义参数的形参名必须与传入的关键字实参对应
转换器种类
- str:匹配除路径分隔符外的任何非空字符串。
- int:匹配0或者任意正整数。
- slug:匹配任意一个由字母或数字组成的字符串。
- uuid:匹配格式化后的UUID。
- path:能够匹配完整的URL路径
转换器不仅可以捕捉匹配的内容,还会将其转换成对应的数据格式,而我们常用的就是str转换器。
正则匹配
在1.X中为默认而在2.X及以上版本也依然可以使用的匹配方式,第一个参数为正则。
这种匹配方式很灵活,但是对于路由匹配而言,并不是匹配到越多的路由就越好,因为也并不一定有对应的界面可以返回给用户。
在2.X版本中,正则匹配关键字为re_path(),1.X版本则为url()
无名分组与有名分组
re_path('^test/\d{4}/', views.test)
如果匹配到test路由后跟四个数字的路由,就转去test函数re_path('^test/(\d{4})/', views.test)
匹配到后,会将括号中匹配的内容作为位置参数传入testre_path('^test/(?P<year>\d{4})/', views.test)
匹配到后,会将括号中匹配的内容作为关键字参数传入test
注意:可以分多组进行传参,但是不能混用无名分组和有名分组。
反向解析
静态路由的反向解析
path('login_page/', views.login)
↓↓↓↓↓↓
path('login/', views.login) # 改变了预设路由
在路由配置完成后,可能存在需要改路由名的情况,但是一旦改动,原本引用这处路由的网址都会失效:
- templates模板中的html文件的a标签
- 视图函数的重定向所引用的网址
django考虑这点,提供了反向解析的方法:
通过一个名字可以反向解析出一个结果,通过这个结果可以访问到某个路由。
我们只需要在path函数再添加一个关键字参数name,就相当于给路由起了个别名。
path('login_page/', views.login, name='login_view')
↓↓↓↓↓↓
path('login/', views.login, name='login_view') # 改变了预设路由
这样我们在可能会失效的链接处都采取反向解析语法,就可以通过login_view解析出路由,用户在去访问链接能对应上了。
反向解析语法
- urls文件:
path('别名', 视图函数, name='别名')
- html页面:
{% url '别名' %}
- 后端语法:
reverse('别名')
这两句语法都会返回一个完整的网址(即域名+路由)的形式。
{% url 'login_view' %} # 网页上的用法
reverse('login_view') # 后端的用法
动态路由的反向解析
对于静态路由,我们可以通过一个别名来对应这个路由,而面对可以匹配很多路由的动态路由,通过一个别名来对应显然是不合适的。
动态反向解析语法
- 路由文件:
path('路由/转换器/', 视图函数, name="别名")
- html页面:
{% url '别名' 匹配项 %}
- 后端语法:
reverse('别名', args=(匹配项1,))
例子:
# urls.py
path('modify/<str:modify_id>/', table_crud_views.modify_func, name='modify_func'),
# html页面
<a class="btn btn-info btn-sm" href={% url "modify_func" user_dict.id %}>修改</a>
# 如果这里的id值从字典里取出来是1,则得到 顶级域名/modify/1/
# 后端重定向
def func(request):
match1 = '一个匹配项'
return redirect(reverse("modify_func", args=(match1,)))
# reverse得到的 顶级域名/modify/一个匹配项/
路由分发
django支持每个应用可以有独立拥有路由层。而这样就方便我们每个开发项目的开发完全解耦合,而每个应用独立的路由文件又可以通过路由分发整合到一起。
如我们在app01的文件夹中新建一个urls.py
from django.urls import path # 需要导入path函数
from app01 import views # 这个软件的视图导入
urlpatterns = [
path("login/", views.login)
] # 在每个app中路由编写还是一样的
也可以相似的在app02中,也新建一个urls.py编写它的路由:path("login/", views.login)
两个软件的路由可能会冲突,但是用了路由分发就不会避免路由的冲突,视图函数的命名也不必特地避免重复了。
我们在django项目的同名文件夹下的总路由编写:
# django_project/urls.py中
from django.urls import path,include # 导入path和include方法
urlpatterns = [
path('app01/', include("app01.urls")),
path('app02/', include("app02.urls")),
] # include函数帮我们将urls文件中的路由全部拼起来了
路由分发后的反向解析
路由分发之后,针对相同的别名能否自动反向解析出不同的应用前缀?
答案是:默认情况下是无法直接识别应用前缀的
解决方案有两种:
-
别名定个规范:每个app的路由别名都加app前缀
# app自己的路由中 path('index/', views.index, name='app01_index_view') # app01中 path('index/', views.index, name='app02_index_view') # app02中
-
名称空间:官方提供的方法,能帮我们识别前缀
# 总路由中 path('app01/', include(('app01.urls', 'app01'), namespace='app01')), path('app02/', include(('app02.urls', 'app02'), namespace='app02')),
无论是哪种方法,在反向解析时,其别名的引用都发生了更改,如:
方法1reverse('app01_index_view')和方法2reverse("app01:index_view")
所以这必须是在立项前就要确认好的细节,或者干脆就不要起别名,把路由给定死。