zoukankan      html  css  js  c++  java
  • Django之路由层

    Django之路由层

    路由是个啥

    undefined

    路由即请求地址与视图函数的映射关系,如果把网站比喻为一本书,那路由就好比是这本书的目录,在Django中路由默认配置在urls.py中,如下图:

    undefined

    简单的路由配置

    # urls.py 
    from django.conf.urls import url
    
    # 由一条条映射关系组成的urlpatterns这个列表称之为路由表
    urlpatterns = [
         url(regex, view, kwargs=None, name=None), # url本质就是一个函数
    ]
    #函数url关键参数介绍
    # regex:正则表达式,用来匹配url地址的路径部分,
            # 例如url地址为:http://127.0.0.1:8001/index/,正则表达式要匹配的部分是index/
    # view:通常为一个视图函数,用来处理业务逻辑
    # kwargs:略(用法详见有名分组)
    # name:略(用法详见反向解析)
    

    示例:

    urls.py文件

    from django.conf.urls import url
    from django.contrib import admin
    from app01 import views # 导入模块views.py
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        
        url(r'^index/$',views.index), # 新增一条
    ]
    

    views.py文件

    from django.shortcuts import render
    from django.shortcuts import HttpResponse # 导入HttpResponse,用来生成响应信息
    
    # 新增视图函数index
    def index(request):
        return HttpResponse('index page...')
    

    测试:

    python manage.py runserver 8001 # 在浏览器输入:http://127.0.0.1:8001/index/ 会看到 index page...
    

    注意一:

    刚刚我们在浏览器输入:http://127.0.0.1:8001/index/Django会拿着路径部分index/去路由表中自上而下匹配正则表达式,一旦匹配成功,则立即执行其后的视图函数,不会继续往下匹配,此处匹配成功的正则表达式是r'^index/$’。

    注意二:

    但是我们在浏览器输入:http://127.0.0.1:8001/index,Django同样会拿着路径部分index去路由表中自上而下匹配正则表达式,貌似并不会匹配成功任何正则表达式( r'^index/$'匹配的是必须以 / 结尾,所以不会匹配成功index),但实际上仍然会看到结果 index page..…,原因如下:

    在配置文件settings.py中有一个参数APPEND_SLASH,该参数有两个值TrueFalse

    APPEND_SLASH=True(如果配置文件中没有该配置,APPEND_SLASH的默认值为True),并且用户请求的url地址的路径部分不是以 / 结尾,例如请求的url地址是 http://127.0.0.1:8001/index,Django会拿着路径部分(即index)去路由表中匹配正则表达式,发现匹配不成功,那么Django会在路径后加/(即index/)再去路由表中匹配,如果匹配失败则会返回路径未找到,如果匹配成功,则会返回重定向信息给浏览器,要求浏览器重新向http://127.0.0.1:8001/index/地址发送请求。

    APPEND_SLASH=False时,则不会执行上述过程,即一旦url地址的路径部分匹配失败就立即返回路径未找到,不会做任何的附加操作

    分组

    undefined

    什么是分组、为何要分组呢?比如我们开发了一个博客系统,当我们需要根据文章的id查看指定文章时,浏览器在发送请求时需要向后台传递参数(文章的ID号),可以使用http://127.0.0.1:8001/article/?id=3,也可以直接将参数放到路径中http://127.0.0.1:8000/article/3/

    针对后一种方式Django就需要直接从路径中取出参数,这就用到了正则表达式的分组功能了,分组分为两种:无名分组与有名分组

    undefined

    无名分组

    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地址的路径部分为:article/数字/,匹配成功的分组部分会以位置参数的形式传给视图函数,有几个分组就传几个位置参数
        url(r'^aritcle/(d+)/$',views.article), 
    ]
    

    views.py文件

    from django.shortcuts import render
    from django.shortcuts import HttpResponse
    
    # 需要额外增加一个形参用于接收传递过来的分组数据
    def article(request,article_id):
        return HttpResponse('id为 %s 的文章内容...' %article_id)
    

    测试:

    python manage.py runserver 8000 # 在浏览器输入:http://127.0.0.1:8000/article/3/ 会看到: id为 3 的文章内容...
    

    有名分组

    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地址的路径部分为: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)
    

    测试:

    python manage.py runserver 8000 # 在浏览器输入:http://127.0.0.1:8000/article/3/ 会看到: id为 3 的文章内容...
    

    总结:有名分组和无名分组都是为了获取路径中的参数,并传递给视图函数,区别在于无名分组是以位置参数的形式传递,有名分组是以关键字参数的形式传递。

    强调:无名分组和有名分组不要混合使用

    路由分发

    undefined

    随着项目功能的增加,app会越来越多,路由也越来越多,每个app都会有属于自己的路由,如果再将所有的路由都放到一张路由表中,会导致结构不清晰,不便于管理,所以我们应该将app自己的路由交由自己管理,然后在总路由表中做分发,具体做法如下

    1. 创建两个app
    # 新建项目mystie2
    > django-admin startproject mysite2
    # 切换到项目目录下
    > cd mysite2
    # 创建app01和app02
    > python3 manage.py startapp app01
    > python3 manage.py startapp app02
    

    2.在每个app下手动创建urls.py来存放自己的路由,如下:

    app01下的urls.py文件

    from django.conf.urls import url
    # 导入app01的views
    from app01 import views
    
    urlpatterns = [
        url(r'^index/$',views.index), 
    ]
    

    app01下的views.py

    from django.shortcuts import render
    from django.shortcuts import HttpResponse
    
    def index(request):
        return HttpResponse('我是app01的index页面...')
    

    app02下的urls.py文件

    from django.conf.urls import url
    # 导入app02的views
    from app02 import views
    
    urlpatterns = [
        url(r'^index/$',views.index), 
    ]
    

    app02下的views.py

    from django.shortcuts import render
    from django.shortcuts import HttpResponse
    
    def index(request):
        return HttpResponse('我是app02的index页面...')
    
    1. 在总的urls.py文件中(mysite2文件夹下的urls.py)
    from django.conf.urls import url,include
    from django.contrib import admin
    
    # 总路由表
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        
        # 新增两条路由,注意不能以$结尾
        # include函数就是做分发操作的,当在浏览器输入http://127.0.0.1:8001/app01/index/时,会先进入到总路由表中进行匹配,正则表达式r'^app01/'会先匹配成功路径app01/,然后include功能会去app01下的urls.py中继续匹配剩余的路径部分
        url(r'^app01/', include('app01.urls')),
        url(r'^app02/', include('app02.urls')),
    ]
    

    测试:

    python manage.py runserver 8001  
    # 在浏览器输入:http://127.0.0.1:8000/app01/index/ 会看到:我是app01的index页面...
    # 在浏览器输入:http://127.0.0.1:8000/app02/index/ 会看到:我是app02的index页面...
    
    

    undefined

    反向解析

    undefined

    在软件开发初期,url地址的路径设计可能并不完美,后期需要进行调整,如果项目中很多地方使用了该路径,一旦该路径发生变化,就意味着所有使用该路径的地方都需要进行修改,这是一个非常繁琐的操作。

    解决方案就是在编写一条url(regex, view, kwargs=None, name=None)时,可以通过参数nameurl地址的路径部分起一个别名,项目中就可以通过别名来获取这个路径。以后无论路径如何变化别名与路径始终保持一致。

    上述方案中通过别名获取路径的过程称为反向解析

    案例:登录成功跳转到index.html页面

    在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'^login/$', views.login,name='login_page'), # 路径login/的别名为login_page
        url(r'^index/$', views.index,name='index_page'), # 路径index/的别名为index_page
    ]
    

    在views.py中

    from django.shortcuts import render 
    from django.shortcuts import reverse # 用于反向解析
    from django.shortcuts import redirect # 用于重定向页面
    from django.shortcuts import HttpResponse
    
    def login(request):
        if request.method == 'GET':
            # 当为get请求时,返回login.html页面,页面中的{% url 'login_page' %}会被反向解析成路径:/login/
            return render(request, 'login.html')
        
        # 当为post请求时,可以从request.POST中取出请求体的数据
        name = request.POST.get('name')
        pwd = request.POST.get('pwd')
        if name == 'kevin' and pwd == '123':
            url = reverse('index_page')  # reverse会将别名'index_page'反向解析成路径:/index/       
            return redirect(url) # 重定向到/index/
        else:
            return HttpResponse('用户名或密码错误')
    
    
    def index(request):
        return render(request, 'index.html')
    
    

    login.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>登录页面</title>
    </head>
    <body>
    <!--强调:login_page必须加引号-->
    <form action="{% url 'login_page' %}" method="post">
        {% csrf_token %} <!--强调:必须加上这一行,后续我们会详细介绍-->
        <p>用户名:<input type="text" name="name"></p>
        <p>密码:<input type="password" name="pwd"></p>
        <p><input type="submit" value="提交"></p>
    
    </form>
    </body>
    </html>
    
    
    

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>首页</title>
    </head>
    <body>
    <h3>我是index页面...</h3>
    </body>
    </html>
    
    

    测试:

    python manage.py runserver 8000  
    # 在浏览器输入:http://127.0.0.1:8000/login/ 会看到登录页面,输入正确的用户名密码会跳转到index.html
    # 当我们修改路由表中匹配路径的正则表达式时,程序其余部分均无需修改
    
    

    总结:

    在views.py中,反向解析的使用:
        url = reverse('index_page')
    在模版login.html文件中,反向解析的使用
        {% url 'login_page' %}
    

    拓展阅读

    如果路径存在分组的反向解析使用:

    from django.conf.urls import url
    from django.contrib import admin
    from app01 import views
    urlpatterns = [
        url(r'^admin/', admin.site.urls),   
        url(r'^aritcle/(d+)/$',views.article,name='article_page'), # 无名分组
        url(r'^user/(?P<uid>d+)/$',views.article,name='user_page'), # 有名分组
    ]
    # 1 针对无名分组,比如我们要反向解析出:/aritcle/1/ 这种路径,写法如下
    在views.py中,反向解析的使用:
        url = reverse('article_page',args=(1,)) 
    在模版login.html文件中,反向解析的使用
        {% url 'article_page' 1 %}
    
        
    # 2 针对有名分组,比如我们要反向解析出:/user/1/ 这种路径,写法如下
    在views.py中,反向解析的使用:
        url = reverse('user_page',kwargs={'uid':1}) 
    在模版login.html文件中,反向解析的使用
        {% url 'user_page' uid=1 %}
    
    

    名称空间

    undefined

    当我们的项目下创建了多个app,并且每个app下都针对匹配的路径起了别名,如果别名存在重复,那么在反向解析时则会出现覆盖,如下

    1.创建两个app

    # 新建项目mystie2
    > django-admin startproject mysite2
    # 切换到项目目录下
    > cd mysite2
    # 创建app01和app02
    > python3 manage.py startapp app01
    > python3 manage.py startapp app02
    
    

    2.在每个app下手动创建urls.py来存放自己的路由,并且为匹配的路径起别名

    app01下的urls.py文件

    from django.conf.urls import url
    from app01 import views
    
    urlpatterns = [
        # 为匹配的路径app01/index/起别名'index_page'
        url(r'^index/$',views.index,name='index_page'), 
    ]
    
    

    app02下的urls.py文件

    from django.conf.urls import url
    from app02 import views
    
    urlpatterns = [
        # 为匹配的路径app02/index/起别名'index_page',与app01中的别名相同
        url(r'^index/$',views.index,name='index_page'), 
    ]
    
    

    3.在每个app下的view.py中编写视图函数,在视图函数中针对别名index_page做反向解析

    app01下的views.py

    from django.shortcuts import render
    from django.shortcuts import HttpResponse
    from django.shortcuts import reverse
    
    def index(request):
        url=reverse('index_page')
        return HttpResponse('app01的index页面,反向解析结果为%s' %url)
    
    

    app02下的views.py

    from django.shortcuts import render
    from django.shortcuts import HttpResponse
    from django.shortcuts import reverse
    
    def index(request):
        url=reverse('index_page')
        return HttpResponse('app02的index页面,反向解析结果为%s' %url)
    
    

    4.在总的urls.py文件中(mysite2文件夹下的urls.py)

    from django.conf.urls import url,include
    from django.contrib import admin
    
    # 总路由表
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        
        # 新增两条路由,注意不能以$结尾
        url(r'^app01/', include('app01.urls')),
        url(r'^app02/', include('app02.urls')),
    ]
    
    

    测试

    python manage.py runserver 8001  
    

    在测试时,无论在浏览器输入:http://127.0.0.1:8001/app01/index/还是输入http://127.0.0.1:8001/app02/index/ 针对别名index_page反向解析的结果都是/app02/index/,覆盖了app01下别名的解析。

    undefined

    解决这个问题的方法之一就是避免使用相同的别名,如果就想使用相同的别名,那就需要用到django中名称空间的概念,将别名放到不同的名称空间中,这样即便是出现重复,彼此也不会冲突,具体做法如下

    1.总urls.py在路由分发时,指定名称空间

    from django.conf.urls import url, include
    from django.contrib import admin
    
    # 总路由表
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        
        # 传给include功能一个元组,元组的第一个值是路由分发的地址,第二个值则是我们为名称空间起的名字
        url(r'^app01/', include(('app01.urls','app01'))),
        url(r'^app02/', include(('app02.urls','app02'))),
    ]
    
    

    2.修改每个app下的view.py中视图函数,针对不同名称空间中的别名'index_page'做反向解析

    app01下的views.py

    from django.shortcuts import render
    from django.shortcuts import HttpResponse
    from django.shortcuts import reverse
    
    def index(request):
        url=reverse('app01:index_page') # 解析的是名称空间app01下的别名'index_page'
        return HttpResponse('app01的index页面,反向解析结果为%s' %url)
    
    

    app02下的views.py

    from django.shortcuts import render
    from django.shortcuts import HttpResponse
    from django.shortcuts import reverse
    
    def index(request):
        url=reverse('app02:index_page') # 解析的是名称空间app02下的别名'index_page'
        return HttpResponse('app02的index页面,反向解析结果为%s' %url)
    
    

    3.测试:

    python manage.py runserver 8001  
    
    

    浏览器输入:http://127.0.0.1:8001/app01/index/反向解析的结果是/app01/index/

    在浏览器输入http://127.0.0.1:8001/app02/index/ 反向解析的结果是/app02/index/

    总结+补充

    1、在视图函数中基于名称空间的反向解析,用法如下
    url=reverse('名称空间的名字:待解析的别名')
    
    2、在模版里基于名称空间的反向解析,用法如下
    <a href="{% url '名称空间的名字:待解析的别名'%}">哈哈</a>
    
    

    django2.0版的re_path与path

    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'))),
    ]
    
    

    path

    在Django2.0中新增了一个path功能,用来解决:数据类型转换问题与正则表达式冗余问题,如下

    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')
    
    

    undefined

    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),
    ]
    
    

    基本规则:

    • 使用尖括号(<>)从url中捕获值。
    • 捕获值中可以包含一个转化器类型(converter type),比如使用<int:name>捕获一个整数变量。若果没有转化器,将匹配任何字符串,当然也包括了 / 字符。
    • 无需添加前导斜杠。

    以下是根据 2.0官方文档 而整理的示例分析表:(跟上面url的匹配关系)

    undefined

    强调:

    # 1、path与re_path或者1.0中的url的不同之处是,传给path的第一个参数不再是正则表达式,而是一个完全匹配的路径,相同之处是第一个参数中的匹配字符均无需加前导斜杠
    
    # 2、使用尖括号(<>)从url中捕获值,相当于有名分组
    
    # 3、<>中可以包含一个转化器类型(converter type),比如使用 <int:name> 使用了转换器int。若果没有转化器,将匹配任何字符串,当然也包括了 / 字符
    
    

    undefined

    django默认支持一下5种转换器(Path converters)

    str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
    int,匹配正整数,包含0。
    slug,匹配字母、数字以及横杠、下划线组成的字符串。
    uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
    path,匹配任何非空字符串,包含了路径分隔符(/)(不能用?)
    
    

    例如

    path('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
    
    

    很明显针对月份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位
    
  • 相关阅读:
    Java实现 蓝桥杯VIP 算法训练 一元三次方程
    Java实现 蓝桥杯VIP 算法训练 乘法表
    Java实现 蓝桥杯VIP 算法训练 矩阵加法
    Java实现 蓝桥杯VIP 算法训练 一元三次方程
    Java实现 蓝桥杯VIP 算法训练 平方计算
    Java实现 蓝桥杯VIP 算法训练 平方计算
    Java实现 蓝桥杯VIP 算法训练 平方计算
    Java实现 蓝桥杯VIP 算法训练 乘法表
    Java实现 蓝桥杯VIP 算法训练 乘法表
    监管只是压倒网盘业务的一根稻草,但不是主要原因(答案只有一个:成本!)
  • 原文地址:https://www.cnblogs.com/Dr-wei/p/11735570.html
Copyright © 2011-2022 走看看