前戏
在之前的文章中,我们已经知道了什么是路由。路由就是urls.py文件里urlpatterns下写的一个个路径,用户输入路径之后,Django在里面找对应的路径,然后去执行views.py里的函数。前面只是学了最简单的用法,今天来看看还有哪些用法
基本的格式:
from django.conf.urls import url urlpatterns = [ url(正则表达式, views视图,参数,别名), ]
正则表达式:一个正则表达式字符串
views视图:一个可调用对象,通常为一个视图函数
参数:可选的要传递给视图函数的默认参数(字典形式)
别名:一个可选的name参数
注意
Django2.xx的路由是下面这样的写法
from django.urls import path,re_path urlpatterns = [ path('login', views.login), ]
如果你想把1.xx的升级到2.xx,要把url换成re_path
正则表达式
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/[0-9]{2}/[0-9]{3}', views.login), ]
代码解释:
我们在路径里写了一个正则表达式,匹配两个数字/三个数字,这样就能访问到这个网站了。^表示以什么开头,如果不知道,可以去看看python的re模块
不知道有没有发现,如果你输入两个数字一个反斜杠,后面不管输入几个数字都可以匹配到,这是因为我们没有限制以什么结尾。正则只要匹配到就不再往下查找了,我们可以在最后加个结尾符$
urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login/[0-9]{2}/[0-9]{3}$', views.login), ]
这样我们的结尾就只能是三位数字
说明:
- urlpatterns中的元素按照顺序从上往下逐一匹配正则表达式,一旦匹配成功则不在往下匹配
- 不需要添加一个前面的反斜杠,因为每个URL都有,例如 ^login而不是 ^/login
- 每个正则表达式前面的‘r’是可选的,但是建议写上
- Django settings.py配置文件中默认没有 APPEND_SLASH 这个参数,但 Django 默认这个参数为 APPEND_SLASH = True。 其作用就是自动在网址结尾加'/'。如果不需要,在settings.py配置文件中设置APPEND_SLASH = FALSE
- 若要从URL中获取一个值,只需要用正则里的分组匹配
分组匹配
在Python的正则表达式中,分组命名正则表达式组的语法是(?P<name>pattern)
,其中name
是组的名称,pattern
是要匹配的模式。
在之前写的删除图书的时候,我们需要获取书的id,当时我们是这样写的,我们获取到id的值后传递给了id
<a href="/delete_book/?id={{ book.id }}">删除</a>
现在我们可以使用分组匹配来改写一下,首先是改写urls.py
原来的路由
url(r'^delete_book/', views.delete_book), # 删除图书
改成分组匹配的路由
url(r'^delete_book/(d+)/', views.delete_book), # 删除图书
对应的html也要改,直接去掉?id=就可以了
<a href="/delete_book/{{ book.id }}">删除</a>
然后我们点击删除,发现页面报错了
报错信息告诉我们需要一个位置参数,但给了两个,那我们去看看对应的函数是怎么写的
#删除图书 def delete_book(request): delete_book_id = request.GET.get('id') Book.objects.filter(id=delete_book_id).delete() return redirect('/book_list/')
函数接收一个参数没错呀,为什么报错信息提示我们给了两个参数?这是因为Django会把分组匹配里的内容当做一个参数传给对应的函数,这样我们就不需要从url里获取书的id值了,函数直接接收就行了,在来修改下我们的函数
def delete_book(request, delete_book_id): # delete_book_id = request.GET.get('id') Book.objects.filter(id=delete_book_id).delete() return redirect('/book_list/')
这样我们就能正常删除了,书的id传给了参数delete_book_id这个参数
除了上面的这种写法,我们还可以给分组起个名称,然后再函数里接收这个参数
在修改URL
url(r'^delete_book/(?P<book_id>d+)/', views.delete_book), # 删除图书
在去删除图书,发现页面又报错了
报错信息告诉我们,得到了一个意外的关键字参数book_id,这是因为,如果是分组命名的话,django会把我们起的名称以关键字(字典)的形式传给对应的函数,那我们把之前函数里写的 delete_book_id参数名改为book_id就可以了
#删除图书 def delete_book(request, book_id): # delete_book_id = request.GET.get('id') Book.objects.filter(id=book_id).delete() return redirect('/book_list/')
注意:
URL在匹配的时候,只匹配路径,也就是说?和后面的参数都不匹配
路由不检查请求方式,也就是说,get,post,delete,put等,只要是同一个路径,都会匹配到同一个函数
只要是正则表达式匹配的,获得的参数永远都是字符串(除非你自己转)
include(路由分发)
之前我们的路由都是写在项目里的urls.py里面,但是我们的项目可能会越来越大,如果都写在一个py文件里,以后找起来也很麻烦,那有没有更好的方法呢?之前说app的时候,我们知道我们可以把一个模块或功能放在一个app里面,那我们可以把对应这个模块的路由也放在对应的app下面,这样我们找起来就很容易了
在之前appTest01的app下创建一个urls.py文件,把相关的路由放到这里面
虽然这样改了,但是查找还是要在项目的urls.py里面进行查找,这时我们在项目里的urls.py里加上下面这句代码,让去appTest01下面去找,记得最后加"/"
from django.conf.urls import url,include urlpatterns = [ url(r'^appTest01/', include('appTest01.urls')), ]
改了之后,我们对应的请求也要改,要在前面加上app名,如
http://127.0.0.1:8080/appTest01/book_list/
这样我们访问之后,先去项目的urls.py找对应的的appTest01,找到之后,在去include里的urls里面找,这种方法叫路由分发
路由传递参数
在上面说路由的格式的时候,说可以给路由写参数,可以接收一个可选的第三个参数,它是一个字典,表示想要传递给视图函数的额外关键字参数。
urlpatterns = [
url(正则表达式, views视图,参数,别名),
]
例如:
from django.conf.urls import url from . import views urlpatterns = [ url(r'^login/', views.login, {'job': 'IT'}), ]
当请求访问login时,会给对应的函数传递一个关键字参数,如果额外传的这个关键字参数和URL中分组命名传的参数同名时,函数则使用额外给的这个参数,而不是URL中的参数
URL反向解析(别名)
我们之前的URL都是写死的,假如有一天,你看着这个URL名不爽了,然后你把这个名词换了一个,这时候你就要把所有引用到的这个URL都要改过来,又过了一段时间,你又觉着不爽。。。
Django提供了我们一种给URL起别名的方式。别名就是给URL起了一个名字,通过这个名字反向拿到了URL地址。以后你如果看URL不爽了,你只需要改路由里的一个就可以了,其他引用的地方都使用这个别名就不需要改了
例子;
url(r'^press_list/', views.press_list, name='press'), # 给url起了个别名为press
在视图函数里可以这样引用
from django.urls import reverse def press_list(request): ret = Press.objects.all() print(reverse('press')) return redirect(reverse('press')) # 通过别名去找对应的URL
结果:
/press_list/
拿到的就是URL前面的路径,所以不管你urls.py里怎么改,这里都不需要改
在模版里这么引用
{% url 'press' %}
分组匹配的别名
之前在写正则表达式的时候,写了正则匹配的一个路由
urlpatterns = [ url(r'^login/([0-9]{2})/([0-9]{3})', views.login, name='home'), ]
如果是通过正则表达式写的话,则需要传递参数,不传会报错
在views.py里引用
from django.urls import reverse reverse("home", args=("11","222" )) # 前面是两个数字,后面是三个数字,随便写
在模版中的引用
{% url 'home' '22' '333' %}
分组命名的别名
路由:
url(r'^delete_book/(?P<book_id>[0-9]{4})/(?P<press_id>[0-9]{2})/', views.delete_book, name='home')
在视图中的引用有两种方式,一种是上面的方式
reverse("home", args=("2019","07" ))
另一种方式
reverse("home", kwargs={"book_id":"2019","press_id":"07"})
在模版中引用也有两种方式,第一种,数字随便写,只要符合上面的正则表达式就行
{% url 'home' '2011' '33' %}
另一种:前面的名称要和路由里起的名称一样
{% url 'home' book_id='2011' press_id='33' %}
命名空间
假如我们有多个app,并且每个app下都有一个别名,name=’home‘
urlpatterns = [ url(r'^appTest01/', include('appTest01.urls')), url(r'^appTest02/', include('appTest02.urls')), ]
appTest01下的和appTest02下的别名都是一样的,如下
url(r'^author_list/', views.author_list, name='home'),
这时我们去打印别名,前面的路径都是appTest02,因为appTest02在下面,把上面的覆盖了
/appTest02/author_list/
这时候我们就需要在项目的urls.py里加上命名空间
urlpatterns = [ url(r'^login/[0-9]{2}/[0-9]{3}$', views.login), url(r'^appTest01/', include('appTest01.urls', namespace='appTest01')), url(r'^appTest02/', include('appTest02.urls', namespace='appTest01')), ]
在视图中使用
reverse("appTest01:home", kwargs={"book_id":"2019","press_id":"07"}) 或者 reverse("appTest02:home", kwargs={"book_id":"2019","press_id":"07"})
在模版中使用
{% url 'appTest01:home' '2011' '33' %} 或者 {% url 'appTest02:home' '2011' '33' %}