Django 路由分发原理探究,re_path规则和reverse反向解析

路由控制之简单配置

  • path的传统写法

    1. path('home/',views.home)
    2. path('news/int:nid',views.news)
      用第二中路由匹配的时候,视图也要传参nid,并且常用的类型通常是int,str,还有slug,uuid,path等
      slug 只支持字母+数字+下划线+横杠 path('news/<slug:nid>',views.news)
      path 支持/ 比如/etc/passwd
  • 看urls.py中的导包from django.urls import path,re_path
    path和re_path区别在于re_path是用正则匹配路由路径,而path就是纯字符串匹配
    所以这里用re_path是为了展示1.0也是有这个功能
    在urls中添加路由列表,第一个参数是url路径中一旦匹配到这个字符串,就会执行views中的函数,
    第一个参数是支持正则写法的,比如以case/case01开头,但不限定结尾

    可以看到只要是以case/case01开头 后面无论添加什么 都返回一样的内容


    为了演示效果,views文件中,导入HttpResponse方法先.

  • 路由正则分组概念
    现在先在views新写一个函数case02,可以看到新增了一个新参,后面会提到

    在urls中我们对路由列表的正则表达式 添加了一个括号(),这个在正则代表分组的意思,这个分组如果配到的话,会连同request一起传给views里的函数,所以必须要有2个形参

    访问结果如下,年份会随着你的url变化而变化,

    然而路由中的分组可以有很多个,注意的一点就是无论是几个分组,都要相应加上request一起传给函数,函数的形参接受数量要一直

  • 路由正则分组规则不严格,导致从上至下解析错误解析的问题
    我们看下面,case02这里我们把前面正则的$结尾限定符号去掉.那么当访问的Url为http://127.0.0.1:8000/case/2088/02/时候会自上而下优先访问case02函数

    本意是要访问case03的,结果因为2条正则规则都符合,自上而下运行,就优先执行了上面一条规则,运行了case02, 要避免这个情况就把$加上去re_path(r"^case/([0-9]{4,})/$",case02),

    分组总结:

  • 如果从URL中获取一个值,那么在周围放置一堆圆括号

  • 根路径后面的第一个路径不需要加反斜杠,比如case是对的/case反而是错误的

  • 每个正则前面建议加上'r' 代表字符串中任何字符都无须转义
    其他的例子,便于理解

路由控制之有名分组

  • 上面的例子中函数传参是根据位置传的,但是如果我们需求是根据形参名传呢????
    我们需要在urls路由控制参数中用固定的?P<变量名>添加到正则的匹配语法中去,比如([0-9]{4,})就变成了?P<year>([0-9]{4,})
    所以re_path(r"^case/(?P<year>[0-9]{4,})/(?P<mon>[0-9]{2})/$",case03)会传参数request,year= mon=
    我们在case03中故意把年份和月份写反,

    但是输出结果还是正常

路由控制之分发解耦

前面的例子中,我们把项目所有的路由分发策略都写在了项目下的urls.py,如果我们有100个app呢,如果每个app下又有100个子页面呢? 我们需要进一步解耦,在项目路由表中按照应用名分发,然后在应用的包中再创建一个urls.py,用于该应用下的路由分发,这样就解耦开来了

  • 第一步需要在项目urls中导入include包 from django.urls import path,re_path,include
  • 第二步在应用下创建urls文件,然后在项目路由中添加分发规则,如果是my_app开头的全部分发到my_app.urls中
    re_path(r"my_app/",include("my_app.urls"))
  • 第三部my_pp中的urls创建规则
    from django.urls import path,re_path
    
    from my_app.views import *
    urlpatterns = [
        re_path(r"^case/(?P<year>[0-9]{4,})/(?P<mon>[0-9]{2})/$",case03),
        path("mytest/",my_test)
    ]
    
  • 访问页面
  • 整个代码页面

路由转发器

上面的路由考虑到项目不同而需要路由分发解耦,而路由的分发中,如果涉及到多个re_path的转发规则,则需要用路由转发器进行解耦.
所以路由转发器可以看成是对re_path(正则,视图)中的正则规则进行解耦.

  1. 表写一个类,代表这一类的正则匹配规范

    regex:代表正则规则

    to_python函数表示符合正则规范后,返回这个正则匹配到的内容

    class mobile():
        regex = "1[3-9]\d{9}"
    
         def to_python(self,value):
         print(type(value))
         # 将匹配结果传递到视图内部时使用
         # 返回str还是int主要看需求,纯数字的可以返回int
         return value
    
     def to_url(self,value):
         # 将匹配结果用于反向解析传值时使用
         return value
    
  2. 导方法from django.urls import register_converter

    然后用register_converter进行注册上面mobile类 语法是register_converter(类名,"给这个规则起的别名")

    在给这个类注册了别名后,下面的path路由就可以使用这个别名了

    这里给mobile类起个别名叫tel_num register_converter(mobile,"tel_num")

  3. 在路由分发中使用上述正则匹配,使用语法为<别名>

    path("mobile/<tel_num:m>",show_tel) 这里的m就是类似有名分组,需要在视图中也确认行参数名为m

  4. 最后在视图函数中传入m这个形参

    def show_tel(request,m):
        return HttpResponse(f"查找的手机号是 {m}")
    

路由控制之登录验证示例

django是根据标签的name属性提取里面的数值的

<form action="http://127.0.0.1:7777/echar_show/login/" method="post">
    <div class="form-group">
        <label for="">用户名</label>
        <input type="text" id="user" name="u">
    </div>
    <div class="form-group">
        <label for="">密码</label>
        <input type="password" id="pwd" name="p">
    </div>
    <div class="form-group">
        <input type="submit" class="btn btn-info" id="submit" value="登陆">
    </div>
</form>

H5后form表单的另一种写法

如前端网页视图,我们平时用get访问是访问网页,但是当我们在输入了账号密码按回车后又是需要递交参数的,

如果我们get访问的url和POST请求 都是http://127.0.0.1:8000/test/ 要怎么区分这两次访问呢?
==注:如果form action 递交数据还是跳回get的url地址,那么前面的ip可以省略 <form action="/test/" method="post">
在全局的路由分发中urls代码如下

from my_app.views import *
urlpatterns = [
    path('test/', beijing_time),
]

视图中的views代码如下: 可以看到这个形参request会把请求的方式和内容,并且把提交的信息以字典形式可以轻松获取.

def beijing_time(request):
    import time
    c = time.time()
    b= time.localtime(c)
    a = time.strftime("%Y-%m-%d %H:%M:%S")
    if request.method=="POST": #这里区分提交方式,如果是POST就开始验证账号密码.
        if request.POST.get("user") == "sxf" and request.POST.get("pwd")=="123":
            return HttpResponse("登陆成功")
        else:
            return HttpResponse("登陆失败")
    elif request.method=="GET":
        return render(request,"test.html",{"data":a})

html模版中反向解析

还是上面的例子,根据路由分发规则,我们get访问的url是http://127.0.0.1:8000/test/,然后POST请求的地址也是http://127.0.0.1:8000/test/
POST请求的HTML文件和GET请求是同一个HTML文件,但是考虑到后期get的请求地址会变动,那么我们改了get请求的路由规则,比如把test改成login

from my_app.views import *
urlpatterns = [
    path('login/', beijing_time),
]

但是在我们输入了账号密码递交,却显示404了,因为action的网址还没改,因为把他写死了.

我们需要用反向解析给路由分发器中取个别名 name="log"

from my_app.views import *
urlpatterns = [
    path('login/', beijing_time,name="log"),
]

然后HTML文件中用蒙版语法引用这个POST地址,语法为{% url 别名 %}
<form action="{% url 'log' %}" method="post">
这样无论前面路由地址怎么更改,都可以获取到路由中的地址路径

视图函数中反向解析

反向解析还有种用法,这种用法是在view函数中根据路由分发器中的name变量

# 项目的路由分发器代码如下
urlpatterns = [
    re_path(r"my_app/",include("my_app.urls"))
]

my_app中的分发路由如下

urlpatterns = [
    re_path(r"^case3/([0-9]{4,})/([0-9]{2})/$",case03,name = "mytest_reverse3"),  
    # 路径为my_app/case/2019/02/
    path("mytest/2003",my_test,name="mytest_reverse")  
    #路径为my_app/mytest/2003
]

views中的函数如下

def case03(request,mon,year):
    print(reverse("mytest_reverse2",args=(2004,"09",)))
    return HttpResponse("你输入的年份是%s,月份是%s"%(year,mon))

def my_test(request):

    print(reverse("mytest_reverse"))
    return HttpResponse("my_test页面")

HTML中可以把反向解析的域名写进去,具体写法结合流程控制之反向解析1的示例
比如这里的写法应该改成
<form action="{% url 'mytest_reverse' %}" method="post">
<form action="{% url 'mytest_reverse3' %}" method="post">

  • 以路径http://127.0.0.1:8000/my_app/mytest/2003为例,会运行my_test函数,会打印输出/my_app/mytest/2003
    reverse("mytest_reverse")会解析出mytest_reverse这个别名的url地址,这里的url是写死的,下面举例一个正则的
  • 以路径my_app/case/2019/02/为例,会运行 case03 函数,别名是mytest_reverse3,那么在case03函数中reverse("mytest_reverse2",args=(2004,"09",))
  • 因为分发路由中的re_parth中有了2个分组,因此我们在reverse函数中要添加2个参数,参数必须符合分组的正则表达规则,打印输出的内容为/my_app/case/2004/09/
  • 这里强调的一点是:函数里的reverse()进行的反向解析和所在哪个函数没有关系 假设我们访问路径为http://127.0.0.1:8000/my_app/mytest/2003

    可以看到我们把2个反向解析都放在一个My_test函数中,他的会因为路径找到my_test这个函数执行,然后reverse反向解析会根据别名解析到下面2个路由分发的规则中
    根据mytest_reverse2别名解析里面的url解析规则,在根据2个分组传入参数2004,09,输出路径/my_app/case/2004/09/
    根据mytest_reverse别名解析里面的url解析规则,解析出固定的路径mytest/2003
urlpatterns = [
    re_path(r"^case3/([0-9]{4,})/([0-9]{2})/$",case03,name = "mytest_reverse2"),  
    # 路径为my_app/case/2019/02/
    path("mytest/2003",my_test,name="mytest_reverse")  
    #路径为my_app/mytest/2003
]

反向解析之传参

reverse 可以接受如下传参方式

from django.url import reverse
url = reverse("login" args=(1,2,3),kwargs={"name":"aa"})

def test(name,*args,**kwargs)
	url = reverse("login" args=args,kwargs=kwargs)

示例2

路由控制之名称空间

我们因为需要反向解析,在应用的分发器中会加入Name参数,以便反向解析动态获取一些数值

但是反向解析是根据name这个参数来的,不同的应用下name取值可能会重复,就会导致下面这个情况,my_app和my_app2下有相同的函数index,并且他们的命名都是name
所以my_app下的反向解析会被my_app2的反向解析覆盖,出现了运行各自应用下的index,但是反向解析出现了错误,所以我们需要把命名空间也按照不同的应用进行分隔.

按照应用分隔命名空间需要2步
第一:项目总路由那边include里面要添加元祖,参数一是按应用的分发路由,参数二是按应用定义的命名空间 path("myapp/", include(("app01.urls","app1"),namespace="app1"))
include(("app01.urls","app1")这里注意,如果使用namespace,include参数就得是个元祖,第二个参数填的就是后面namespace的参数
第二:子路由设置name参数作为路由的别名path("app1/test/",app1_view,name="index")
第三:视图函数中reverse函数中使用命名空间:子路由别名 就可以在反向解析中获取url了

def app1_view(requests):
    url = reverse("app1:index")
    return HttpResponse(url)

看正确的结果

代码示例2
命名空间就是为了解决不同include的url下的name重名的情况

#多层namespace嵌套需要从最外层的namespace和冒号进行拼接才能反向解析
urlpatterns = [
    path(r'^rbac/', (
        [path(r'^xx/', admin.site.urls, name="xx"), #/rbac/xx/  "a1:xx"
         path(r'^yy/', ([path(r'^zz/', admin.site.urls, name="zz"),]), namespace="b1"),]), # /rbac/yy/zz/ "a1:b1:zz"
         namespace='a1'),
    path(r'^', include('web.urls')),
]
posted @ 2021-07-27 22:55  零哭谷  阅读(350)  评论(0编辑  收藏  举报