Django之路由层
路由层
1、什么是路由
路由就是请求地址和视图函数的映射关系,如果把网站比喻为一本书,那么路由就是这本书的目录,在Django中路由默认配置在urls.py中。
from django.conf.urls import url
urlpatterns=[
url(正则表达式,views.视图函数名,参数,别名),
]
# 正则表达式:一个正则表达式字符串
# views视图函数:一个可调用对象,通常为一个视图函数或一个指定视图函数路径的字符串
# 参数:可选的要传递给视图函数的默认参数(字典形式)
# 别名:一个可选的name参数
以上是Django1.x版本的写法,注意在django2.0中。路由系统换成了path,不支持正则表达式,path功能主要用来解决正则表达式太冗余和数据类型转换(转换器)。如果2.0中需要用到正则表达式可以用re_path.
2、使用路由的注意事项
1、urlpatterns中的元素按照书写的顺序从上往下逐一匹配正则表达式,一旦匹配成功则不再继续。
2、若要从URL中捕获一个之,只需要在他的周围放置一对圆括号(分组匹配)
3、不需要添加一个前导的反斜杠,因为每个URL都有。列如^articles
,而不是^/articles
4、每个正则表达式前面的r
是可选的,但是建议加上。
5、在客户端,我们输完路径,就算不加反斜杠,后台也会自动帮我加上,这个可以设置自动加,或者取消。
#settings.py
APPEND_SLASH=True/False
3、分组命名匹配(重要知识点)
什么是分组、为何要分组呢?比如我们开发了一个博客系统,当我们需要根据文章的id查看指定文章时,浏览器在发送请求时需要向后台传递参数(文章的id号),可以使用 http://127.0.0.1:8001/article/?id=3,也可以直接将参数放到路径中http://127.0.0.1:8001/article/3/
针对后一种方式Django就需要直接从路径中取出参数,这就用到了正则表达式的分组功能了,分组分为两种:无名分组与有名分组
3.1 无名分组
urls.py文件
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index/(\d+)', views.index),
]
view.py
from django.shortcuts import render,HttpResponse,redirect
# Create your views here.
def index(request,index_id): # 使用位置形参来接收。返回值是str。
print(index_id,type(index_id))
return HttpResponse("hello index")
# 在浏览器输入:http://127.0.0.1:8000/index/1111
# 后台输出:
# 1111 <class 'str'>
3.1.1 注意事项
- 任意传n个无名分组过来,那么就要用n个无名形参来接收,或者用元组来接收。并且接收的值的类型都是str。
# urls.py
url(r'^home/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.home),
# view.py
def home(request,*args):
print(*args)
return HttpResponse("hello home")
# 浏览器输入: http://127.0.0.1:8000/home/2018/12/5677766/
# 输出结果
# 2018 12 5677766
3.2 有名分组
urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^getyear/(?P<year>\d+)', views.get_year),
]
view.py
def get_year(request,year):
print(year, type(year))
return HttpResponse("hello get_year")
# 浏览器输入:http://127.0.0.1:8000/getyear/2018
# 输出:
2018 <class 'str'>
3.2.1 注意事项
- 使用有名分组,路径中写
(?P<分组名称>正则表达式)
,视图函数中的位置形参必须和分组名称保持一致。 - 如果有多个有名分组,可以用**kwargs来接收,分组名称就变成了字典的k,输入的值就变成了字典的v。
#urls.py
url(r'^getymd/(?P<year>[0-9]{4})/(?P<mon>[0-9]{2})/(?P<day>[0-9]{2})', views.get_ymd),
#view.py
def get_ymd(request,**kwargs):
print(kwargs)
return HttpResponse("hello get_year")
# 在浏览器中输入: http://127.0.0.1:8000/getymd/2023/12/23
# 输出结果为:
{'year': '2023', 'mon': '12', 'day': '23'}
3.3无名分组和有名分组总结
总结:有名分组和无名分组都是为了获取路径中的参数,并传递给视图函数,区别在于无名分组是以位置参数的形式传递,有名分组是以关键字参数的形式传递。
强调:无名分组和有名分组不要混合使用
4、路由分发
随着项目功能的增加,app会越来越多,路由也越来越多,每个app都会有属于自己的路由,如果再将所有的路由都放到一张路由表中,会导致结构不清晰,不便于管理,所以我们应该将app自己的路由交由自己管理,然后在总路由表中做分发,具体做法如下:
4.1 模拟路由分发(老板省力看app模式)
4.1.1、创建两个App
python manage.py startapp app01
python manage.py startapp app02
4.1.2、在settings.py中INSTALLED_APPS中注册
'app01.apps.App02Config'
'app02.apps.App02Config'
4.1.3、在两个app的根目录下创建urls.py
## app01.urls.py
from django.conf.urls import url
from app01 import views
urlpatterns=[
(r'^home/$',views.home),
]
## app02.urls.py
from django.conf.urls import url
from app02 import views
urlpatterns=[
(r'^home/$',views.home),
]
4.1.4、项目下的总路由 urls.py
from django.conf.urls import url,include
urlpatterns=[
(r'^app01/',include('app01.urls')),
(r'^app02/',include('app02.urls')),
]
4.1.5、测试
# 浏览器中输入:
http://127.0.0.1:8000/app01/home/
http://127.0.0.1:8000/app02/home/
5、反向解析
在软件开发初期,url地址的路径设计可能并不完美,后期需要进行调整,如果项目中很多地方使用了该路径,一旦该路径发生变化,就意味着所有使用该路径的地方都需要进行修改,这是一个非常繁琐的操作。
解决方案就是在编写一条url(regex, view, kwargs=None, name=None)
时,可以通过参数name为url地址的路径部分起一个别名,项目中就可以通过别名来获取这个路径。以后无论路径如何变化别名与路径始终保持一致。
上述方案中通过别名获取路径的过程称为反向解析
5.1 反向解析案例: 登陆成功后,跳转到index页面(后端解析).
5.1.1 登陆页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
<p>username:
<input type="text" name="username">
</p>
<p>password:
<input type="text" name="password">
</p>
<input type="submit">
</form>
</body>
</html>
5.1.2 app01/urls.py路由
from django.conf.urls import url
from app01 import views
urlpatterns=[
url(r'^login/$',views.login),
url(r'^index/$',views.index,name='index'),
]
5.1.3 app01/view.py视图
def login(request):
if request.method=="POST":
username=request.POST.get('username')
password=request.POST.get('password')
if username=="lpc" and password=="123":
_url=reverse('index')
print(_url)
return redirect(_url)
else:
return HttpResponse('用户名或密码错误')
return render(request,'login.html')
def index(request):
return HttpResponse("hello index")
5.1.4 测试
# 输入:http://127.0.0.1:8000/app01/login/
# 输入lpc ,123
# 跳转到index
5.2 反向解析案例:(web页面点击按钮,跳转到Home页面)前端解析.
5.2.1 html代码
<a href="{% url 'home' %}">点击我跳转到主页</a>
5.2.2 ulr.py
url(r'^home/$',views.home,name='home'),
5.2.3 测试
# 点击a标签.
# 进行跳转.
5.3 总结
- 要反向解析一定要在url中给name参数加值.
url(r'^home/$',views.home,name='home'),
- 前端反向解析:
{% url 'home'%}
- 后端反向解析:
from django.shortcuts import reverse
_url=reverse('home')
5.4 分组中的反向解析
5.4.1 无名分组反向解析
url(r'^home/(\d+)/$',views.home,name='home'),
5.4.1.1 前端解析
<a href="{% url 'home' 1 %}">点击我跳转到主页</a>
5.4.1.2 后端解析
def home(request,*args,**kwargs):
_url=reverse('home',args=(1,))
print(_url)
return HttpResponse("hello home app02")
5.4.2 有名分组反向解析(也可以用无名分组的反向解析)
url(r'^getyear/(?P<year>[0-9]{4})/$',views.get_year,name='getyear'),
5.4.2.1 前端解析
<a href="{% url 'getyear' 2023 %}">点击我跳转到时间</a>
<a href="{% url 'getyear' year=2023 %}">点击我跳转到时间</a>
5.4.2.1 后端解析
def get_year(request,year):
_url = reverse('getyear', args=(2023,))
_url = reverse('getyear', kwargs={'year':2023,})
print(_url)
return HttpResponse("hello get_year")
5.4.3 总结
- 有名分组也可以用无名分组的方式反向解析.
- 注意
args=(1,)
里面的1只是做演示,实际开发,里面应该是个传递过来的形参
哪怕只有一个 参数,也要养成加逗号的习惯,因为接收的就是个元祖.
5.5、命名空间
当我们的项目下创建了多个app,并且每个app下都针对匹配的路径起了别名,如果别名存在重复,那么在反向解析时则会出现覆盖,如下
5.5.1、创建两个App
python manage.py startapp app01
python manage.py startapp app02
5.5.2、在settings.py中INSTALLED_APPS中注册
'app01.apps.App02Config'
'app02.apps.App02Config'
5.5.3、在两个app的根目录下创建urls.py
## app01.urls.py
from django.conf.urls import url
from app01 import views
urlpatterns=[
(r'^home/$',views.home,name='home'),
]
## app02.urls.py
from django.conf.urls import url
from app02 import views
urlpatterns=[
(r'^home/$',views.home,name='home'),
]
5.5.4、两个app的view.py
# app01.view.py
def home(request,*args,**kwargs):
_url=reverse('home',args=(1,))
print(_url)
return HttpResponse("hello home app01")
# app02.view.py
def home(request,*args,**kwargs):
_url=reverse('home',args=(1,))
print(_url)
return HttpResponse("hello home app02")
5.5.4、项目下的总路由 urls.py
from django.conf.urls import url,include
urlpatterns=[
(r'^app01/',include('app01.urls')),
(r'^app02/',include('app02.urls')),
]
5.5.5 运行结果
# 在浏览器中输入
http://127.0.0.1:8000/app01/home/123/
http://127.0.0.1:8000/app02/home/123/
# 输出结果:
/app02/home/1/
/app02/home/1/
5.5.6 引入命名空间
# 总路由
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^app01/', include('app01.urls',namespace='app01')),
url(r'^app02/', include('app02.urls',namespace='app02')),
]
# app01 反向解析
def home(request,*args,**kwargs):
_url=reverse('app01:home',args=(1,))
print(_url)
return HttpResponse("hello home app01")
# app02 反向解析
def home(request,*args):
_url=reverse('app02:home',args=(1,))
print(_url)
return HttpResponse("hello home app02")
5.5.6 运行结果
# 在浏览器中输入
http://127.0.0.1:8000/app01/home/1/
http://127.0.0.1:8000/app02/home/1/
# 输出结果:
/app01/home/1/
/app02/home/1/
5.5.7 总结
- 命名时,尽量不要重名,如果两个app确实有相同的url.可以用app01_home.app02_home的方式命名
- 如果真遇到的上面的情况,记得解析的时候,要选择app01:home app02:home
1、在视图函数中基于名称空间的反向解析,用法如下
url=reverse('名称空间的名字:待解析的别名')
2、在模版里基于名称空间的反向解析,用法如下
<a href="{% url '名称空间的名字:待解析的别名'%}">哈哈</a>
6、django 2.x中的re_path和path
6.1 re_path
Django2.0中的re_path与django1.0的url一样,传入的第一个参数都是正则表达式
from django.urls import re_path # django2.0中的re_path
from django.conf.urls import url # 在django2.0中同样可以导入1.0中的url
urlpatterns = [
# 用法完全一致
url(r'^app01/', include(('app01.urls','app01'))),
re_path(r'^app02/', include(('app02.urls','app02'))),
]
6.2 path
在Django2.0中新增了一个path功能,用来解决:数据类型转换问题与正则表达式冗余问题,如下
from django.urls import re_path
from app01 import views
urlpatterns = [
# 问题一:数据类型转换
# 正则表达式会将请求路径中的年份匹配成功然后以str类型传递函数year_archive,在函数year_archive中如果想以int类型的格式处理年份,则必须进行数据类型转换
re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
# 问题二:正则表达式冗余
# 下述三个路由中匹配article_id采用了同样的正则表达式,重复编写了三遍,存在冗余问题,并且极不容易管理,因为一旦article_id规则需要改变,则必须同时修改三处代码
re_path(r'^article/(?P<article_id>[a-zA-Z0-9]+)/detail/$', views.detail_view),
re_path(r'^articles/(?P<article_id>[a-zA-Z0-9]+)/edit/$', views.edit_view),
re_path(r'^articles/(?P<article_id>[a-zA-Z0-9]+)/delete/$', views.delete_view),
]
Django2.0中的path如何解决上述两个问题的呢?请看示例
from django.urls import path,re_path
from app01 import views
urlpatterns = [
# 问题一的解决方案:
path('articles/<int:year>/', views.year_archive), # <int:year>相当于一个有名分组,其中int是django提供的转换器,相当于正则表达式,专门用于匹配数字类型,而year则是我们为有名分组命的名,并且int会将匹配成功的结果转换成整型后按照格式(year=整型值)传给函数year_archive
# 问题二解决方法:用一个int转换器可以替代多处正则表达式
path('articles/<int:article_id>/detail/', views.detail_view),
path('articles/<int:article_id>/edit/', views.edit_view),
path('articles/<int:article_id>/delete/', views.delete_view),
]
强调:
#1、path与re_path或者1.0中的url的不同之处是,传给path的第一个参数不再是正则表达式,而是一个完全匹配的路径,相同之处是第一个参数中的匹配字符均无需加前导斜杠
#2、使用尖括号(<>)从url中捕获值,相当于有名分组
#3、<>中可以包含一个转化器类型(converter type),比如使用 <int:name> 使用了转换器int。若果没有转化器,将匹配任何字符串,当然也包括了 / 字符
6.2.1django默认支持一下5种转换器(Path converters)
Copystr,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
int,匹配正整数,包含0。
slug,匹配字母、数字以及横杠、下划线组成的字符串。
uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
path,匹配任何非空字符串,包含了路径分隔符(/)(不能用?)
例如
Copypath('articles/<int:year>/<int:month>/<slug:other>/', views.article_detail)
# 针对路径http://127.0.0.1:8000/articles/2009/123/hello/,path会匹配出参数year=2009,month=123,other='hello'传递给函数article_detail
6.2.2 自定义转换器示例:
很明显针对月份month,转换器int是无法精准匹配的,如果我们只想匹配两个字符,那么转换器slug也无法满足需求,针对等等这一系列复杂的需要,我们可以定义自己的转化器。转化器是一个类或接口,它的要求有三点:
regex
类属性,字符串类型to_python(self, value)
方法,value是由类属性regex
所匹配到的字符串,返回具体的Python变量值,以供Django传递到对应的视图函数中。to_url(self, value)
方法,和to_python
相反,value是一个具体的Python变量值,返回其字符串,通常用于url反向引用。
-
在app01下新建文件path_ converters.py,文件名可以随意命名
class MonthConverter: regex='\d{2}' # 属性名必须为regex def to_python(self, value): return int(value) def to_url(self, value): return value # 匹配的regex是两个数字,返回的结果也必须是两个数字
-
在urls.py中,使用
register_converter
将其注册到URL配置中:from django.urls import path,register_converter from app01.path_converts import MonthConverter register_converter(MonthConverter,'mon') from app01 import views urlpatterns = [ path('articles/<int:year>/<mon:month>/<slug:other>/', views.article_detail, name='aaa'), ]
-
views.py中的视图函数article_detail
from django.shortcuts import render,HttpResponse,reverse def article_detail(request,year,month,other): print(year,type(year)) print(month,type(month)) print(other,type(other)) print(reverse('xxx',args=(1988,12,'hello'))) # 反向解析结果/articles/1988/12/hello/ return HttpResponse('xxxx')
-
测试
# 1、在浏览器输入http://127.0.0.1:8000/articles/2009/12/hello/,path会成功匹配出参数year=2009,month=12,other='hello'传递给函数article_detail # 2、在浏览器输入http://127.0.0.1:8000/articles/2009/123/hello/,path会匹配失败,因为我们自定义的转换器mon只匹配两位数字,而对应位置的123超过了2位