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反向引用。
  1. 在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是两个数字,返回的结果也必须是两个数字
    
  2. 在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'),
    
    ]
    
  3. 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')
    
  4. 测试

    # 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位
    
posted @ 2023-06-10 12:34  FirstReed  阅读(8)  评论(0编辑  收藏  举报