路由的作用
路由即请求地址与视图函数的映射关系,如果把网站比喻为一本书,那路由就好比是这本书的目录,在Django中路由默认配置在urls.py中
路由配置
from django.conf.urls import url
urlpatterns = [
url(正则表达式, views视图函数,参数,name=别名), # 参数,别名可不写
]
1. url()方法,第一个参数其实是一个正则表达式,一旦前面的正则匹配到了内容,就不会再往下继续匹配,而是直接执行对应的视图函数
正是由于此特性,当你的项目特别庞大的时候,url的前后顺序也是你需要你考虑极有可能会出现url错乱的情况
2. django在路由匹配的时候,当你在浏览器中没有敲最后的斜杠,django会先拿着你没有敲斜杠的结果取匹配,如果都没有匹配上,
会让浏览器在末尾加斜杠再发一次请求,再匹配一次,如果还匹配不上才会报错
取消该机制:
# 在settings.py文件中:
APPEND_SLASH = False # 该参数默认是True,其作用就是自动在网址结尾加'/'
补充:
# 匹配主页
url(r'^$',views.home),
# 不存在的页面
url(r'',views.error),
分组
什么是分组、为何要分组呢?比如我们开发了一个博客系统,当我们需要根据文章的id查看指定文章时,浏览器在发送请求时需要向后台传递参数(文章的id号),可以使用 http://127.0.0.1:8001/article/?id=3,也可以直接将参数放到路径中http://127.0.0.1:8001/article/3/
针对后一种方式Django就需要直接从路径中取出参数,这就用到了正则表达式的分组功能了,分组分为两种:无名分组与有名分组
- 无名分组
路由匹配的时候,会将括号内正则表达式匹配到的内容 当做位置参数传递给视图函数
urls.py文件:
urlpatterns = [
url(r'^admin/', admin.site.urls),
# 下述正则表达式会匹配url地址的路径部分为:article/数字/,匹配成功的分组部分会以位置参数的形式传给视图函数,有几个分组就传几个位置参数
url(r'^aritcle/(\d+)/$',views.article),
]
views.py文件:
from django.shortcuts import render
from django.shortcuts import HttpResponse
# 需要额外增加一个形参用于接收传递过来的分组数据
def article(request,id):
return HttpResponse('id为 %s 的文章内容...' %id)
- 有名分组
路由匹配的时候,会将括号内正则表达式匹配到的内容 当做关键字参数传递给视图函数
urls.py文件:
urlpatterns = [
url(r'^admin/', admin.site.urls),
# 该正则会匹配url地址的路径部分为:article/数字/,匹配成功的分组部分会以关键字参数(article_id=匹配成功的数字)的形式传给视图函数,有几个有名分组就会传几个关键字参数
url(r'^aritcle/(?P<article_id>\d+)/$',views.article),
]
views.py文件:
from django.shortcuts import render
from django.shortcuts import HttpResponse
# 需要额外增加一个形参,形参名必须为article_id
def article(request,article_id):
return HttpResponse('id为 %s 的文章内容...' %article_id)
总结:有名分组和无名分组都是为了获取路径中的参数,并传递给视图函数,区别在于无名分组是以位置参数的形式传递,有名分组是以关键字参数的形式传递。
强调:无名分组和有名分组不要混合使用
url(r'^test/(?P<kwargs>\d+)/(\d+)$', views.test), # 报错
但是用一种分组下 可以使用多个
# 无名分组支持多个
url(r'^test/(\d+)/(\d+)/', views.test),
def test(request, *args):
print(args) >>> ('12123', '123')
return HttpResponse('test')
# 有名分组支持多个
url(r'^test/(?P<kwargs>\d+)/(?P<xx>\d+)/', views.test),
def test(request, kwargs, xx):
print(kwargs)
print(xx)
return HttpResponse('test')
1.在更高级的用法中,可以使用分组命名匹配的正则表达式组来捕获URL中的值并以关键字参数形式传递给视图。
2.在Python的正则表达式中,分组命名正则表达式组的语法是(?P<name>pattern),其中name是组的名称,pattern是要匹配的模式。
3.每个在URLconf中捕获的参数都作为一个普通的Python字符串传递给视图,无论正则表达式使用的是什么匹配方式
反向解析
本质:其实就是给你返回一个能够对应url的地址,通过这个rul触发对应的视图函数
在软件开发初期,url地址的路径设计可能并不完美,后期需要进行调整,如果项目中很多地方使用了该路径,一旦该路径发生变化,就意味着所有使用该路径的地方都需要进行修改,这是一个非常繁琐的操作。
解决方案就是在编写一条url(regex, view, kwargs=None, name=None)时,可以通过参数name为url地址的路径部分起一个别名,项目中就可以通过别名来获取这个路径。以后无论路径如何变化别名与路径始终保持一致。
上述方案中通过别名获取路径的过程称为反向解析
使用
urls.py文件中:
url(r'^index/$',views.index,name='kkk')
views.py中:
from django.shortcuts import reverse
def index(request):
#后端反向解析 后端可以在任意位置通过reverse反向解析出对应的url
print(reverse('kkk')) # /index/
return HttpResponse('index')
.html文件:
#前端反向解析
{% url 'kkk' %}
无名分组反向解析
url(r'^index/(\d+)/$',views.index, name='kkk'),
# 后端反向解析
def index(request, xx):
print(reverse('kkk',args=(xx,))) # /index/xx 后面xx通常都是数据的id值
return render(request, 'login.html')
# 前端反向解析
<a href="{% url 'kkk' 1%}">11</a> # 后面数字通常都是数据的id值
# 一般配合前端模板语法使用
{% for user_obj in user_list %}
<a href='{{ url 'kkk' user_obj.pk }}/'>按钮</a>
{% endfor %}
有名分组反向解析
url(r'^index/(?P<year>\d+)/$',views.index,name='kkk')
# 后端反向解析
print(reverse('kkk',args=(1,))) # 推荐你使用上面这种 减少你的脑容量消耗
print(reverse('kkk',kwargs={'year':1}))
# 前端反向解析
<a href="{% url 'kkk' 1 %}">1</a> # 推荐你使用上面这种 减少你的脑容量消耗
<a href="{% url 'kkk' year=1 %}">1</a>
路由分发
为什么要用路由分发
1.当你的django项目特别庞大的时候,路由与视图函数对应关系特别特别多.那么你的总路由urls.py代码太过冗长,不易维护
2.每一个应用都可以有自己的urls.py,static文件夹,templates文件夹(******)
3.正是基于上述条件,可以实现多人分组开发,等多人开发完成之后,我们只需要创建一个空的django项目,然后将多人开发的app全部注册进来,在总路由实现一个路由分发,而不再做路由匹配(来了之后,我只给你分发到对应的app中)
4.当你的应用下的视图函数特别特别多的时候 你可以建一个views文件夹 里面根据功能的细分再建不同的py文件(******)
使用
1.在应用下新建urls.py文件,在该文件内写路由与视图函数的对应关系即可
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^index/',views.index)
...
]
2.在总路由文件内导入 include
from django.conf.urls import url,include
3.在总路由文件内编写应用名与应用路由对应关系
方法一:
from app01 import urls as app01_urls # 给url重命名
from app02 import urls as app02_urls
urlpatterns = [
url(r'^app01/',include(app01_urls)),
url(r'^app02/',include(app02_urls)),
...
]
方法二: # 推荐使用
urlpatterns = [
url(r'^app01/',include('app01.urls')),
url(r'^app02/',include('app02.urls')),
...
]
注意: 总路由中,一级路由(应用名)的后面千万不加$符号
名称空间
路由分发后,多个app中的url起了相同的别名,这个时候用反向解析并不会自动识别应用前缀,如果想避免这种问题的发生,就需要使用
使用
- 方式一:
# 总路由
url(r'^app01/',include('app01.urls',namespace='app01'))
url(r'^app02/',include('app02.urls',namespace='app02'))
# 后端解析的时候
reverse('app01:index')
reverse('app02:index')
# 前端解析的时候
{% url 'app01:index' %}
{% url 'app02:index' %}
- 方式二:推荐
起别名的时候不要冲突即可 ,一般情况下在起别名的时候通常建议以应用名作为前缀
url(r'^index/(\d+)/', views.index, name='app01_kkk'),
url(r'^index/(\d+)/', views.index, name='app02_kkk'),
# 后端解析的时候
reverse('app01_index')
reverse('app02_index')
# 前端解析的时候
{% url 'app01_index' %}
{% url 'app02_index' %}
django2.x版的re_path与path
路由层1.X用的是url
而2.X用的是path
2.X中的path第一个参数不再是正则表达式,而是写什么就匹配什么 是精准匹配
当你使用2.X不习惯的时候 2.X还有一个叫re_path
2.x中的re_path就是你1.X的url
虽然2.X中path不支持正则表达式 但是它提供了五种默认的转换器
1.0版本的url和2.0版本的re_path分组出来的数据都是字符串类型
默认有五个转换器,感兴趣的自己可以去试一下
str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
int,匹配正整数,包含0。
slug,匹配字母、数字以及横杠、下划线组成的字符串。
uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
path,匹配任何非空字符串,包含了路径分隔符(/)(不能用?)
path('index/<int:id>/',index) # 会将id匹配到的内容自动转换成整型
还支持自定义转换器
案例:
urls.py文件
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),
]
views.py文件
from django.shortcuts import render,HttpResponse
# Create your views here.
def year_archive(request,year):
print(year,type(year))
return HttpResponse('year_archive page')
def detail_view(request,article_id):
print(article_id, type(article_id))
return HttpResponse('detail_view page')
def edit_view(request,article_id):
print(article_id, type(article_id))
return HttpResponse('edit_view page')
def delete_view(request,article_id):
print(article_id, type(article_id))
return HttpResponse('delete_view page')
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。若果没有转化器,将匹配任何字符串,当然也包括了 / 字符