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

    一、路由层(urls.py)

    Django中的路由指的就是urls.py文件,称之为路由层

    路由层即用户请求地址与视图函数的映射关系,如果被一个网站比喻成一本字典的话,我们这个路由(urls.py)就号比是这个字典的目录,在Django中路由默认在urls.py文件中,如下图:

    二、简单的路由配置

    # urls.py路由配置文件
    from Django.conf.urls import url
    
    # 有一条条映射关系组成的urlpatterns这个列表称之为路由表
    urlpatterns = [
        url(regex,view,kwargs=None,name=None),
        #url本质就是一个函数
    ]
    

    函数url关键参数介绍:

    案例如下:

    # 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),# 这里是新建一个django项目默认自带的,不用管它
        
        url(r'^index/$',views.index),#这是新增的一条地址与视图函数的对应关系
    ]
    
    # views.py视图函数文件
    
    from django.shortcuts import render 
    from django.shortcuts import redirect
    from django.shortcuts import HttpResponse # 导入HttpResponse,用来生成响应信息,就是直接可以返回字符串,将字符串处理过后响应给客户端
    
    def index(request):
        return HttpResponse('这是index.page')
    

    测试如下:

    # 启动服务端方式一:命令行
    -->:python manage.py runserver 8001 # 这里启动的时候,可以指定端口号:8001,默认为8000
    在浏览器中输入http://127.0.0.1:8001/index/ 就会看到‘我是index.page’    
        
        
    # 启动服务端方式二:直接在pycharm窗口中启动
    -->:这里要提前在下图中进行配置:
    

    pycharm窗口:启动Django项目server端方法如下图:

    2.1 注意一

    刚刚我们在浏览器输入:Http: //127.0.0.1:8001/index/,Django会拿着路径部分index/去路由表(urls.py文件)中自上而下匹配正则表达式,一旦匹配成功,则会立即执行该路径对应的视图函数,也就是上面路由表(urls.py文件)中的uelspatterns列表中的url('^index/$',views.index)-----也就是views.py视图函数文件的index函数

    2.2 注意二

    当我们在浏览器中输入:Http: //127.0.0.1:8001/index,Django同样会拿着路径部分index去路由表中自上而下匹配正则表达式,看起来好像是匹配不到正则表达式(r'^index/$' 匹配的是必须以/结尾,所以必会匹配到成功index),但是实际上我们依然在浏览器宽口中看到了 ‘我是indexpage’,其原因如下:

    在配置文件settings.py中有一个参数APPEND_SLASH,该参数有连个值True/False:

    • 当APPEND_SLASH = True(如果配置文件中没有该配置,则默认值为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地址的路径部分匹配失败就立即返回路劲未找到,不会做任何的附加操作

    三、路由分组

    什么是分组,为何要分组呢?

    比如我们开发了博客系统,当我们需要根据文章的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 # 添加app001应用中的views视图函数文件
    
    urlpatterns = [
        url(r'^admin',admin.site.urls),
        
        # 下面的正则表达式会匹配url地址的路径部分为:article/数字/,匹配成功的分组部分会以位置参数的形式传给视图函数,有几个分组就传几个位置参数
        url(r'aritcle/(d+)/$',view.article),#(d+)代表匹配数字1-无穷个
    ]
    
    # 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 8001 
    在浏览器输入:http: //127.0.0.1:8001/article/3 会看到:id为3的文章内容....
    

    3.2 有名分组

    # urls.py文件
    from django.conf.urls import url
    from django.contrib import admin
    from app01 import views # 添加app001应用中的views视图函数文件
    
    urlpatterns = [
        url(r'^admin',admin.site.urls),
        
        # 下面的正则表达式会匹配url地址的路径部分为:article/数字/,匹配成功的分组部分会以关键字参数(article_id=..)的形式传给视图函数,有几个分组就传几个关键字参数
        url(r'aritcle/(?P<article_id>d+)/$',view.article),#(d+)代表匹配数字1-无穷个
    ]
    
    # 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 8001 
    在浏览器输入:http: //127.0.0.1:8001/article/3 会看到:id为3的文章内容....
    

    3.3 无名分组和有名分组的区别

    无名分组和有名分组都是为了获取路径中参数,并传递给视图函数,区别如下:

    1. 无名分组是以位置参数的形式传递给对应的视图函数,此时在路由表(urlpatterns)中的正则中有几个无名分组,那么在其对应的视图函数中,就要有几个位置参数去接收,在上面的例子中,只有一个无名分组3 ,那么传递给视图函数的则是(views.article(request,'3'))
    2. 有名分组是以关键字参数的形式传递给对应的视图函数的,此时在路由表(urlpatterns)中的正则中有几个又名分组,那么在其对应的视图函数中,可以用**kwargs来收参数,也可以直接写成关键字参数的形式,上面的例子则是views.article(request,article_id='3')

    注意:无名分组和又名分组不要混合使用

    四、路由分发

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

    4.1 创建两个app

    # 新建项目mysite1---此处用命令行命令创建
    # 切换到存储项目的目录下
    django-admin startproject mysite1
    
    # 切换到新建的项目下
    # 创建两个app项目(app01和app02)
    python3 startapp app01
    python3 startapp app02
    

    此处注意以下三点:

    1. 自己用命令行创建的django项目,需要自己手动去创建templates文件夹来存放需要用到的.html文件
    2. 并且去settings的TEMPLATES中配置:'DIRS': [os.path.join(BASE_DIR,'templates')]
    3. 在settings的INSTALLED_APPS中去配置‘app01’ ‘app02’

    4.2 手动在app中创建urls.py存放自己的路由

    1. 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页面。。。。。')
    

    2. 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页面。。。。。')
    

    4.3 总的mysite1文件夹的路由表中urls.py

    from django.conf.urls import url,include
    from django.contrib import admin
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
    
        # 因为功能太多,所以在总的路由表中,做路由分发,分发给各个app功能应用
        # 新增的两条路由,注意不能以$结尾
        # 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'))
    ]
    

    测试:

    # 先启动项目server端---命令行式:python manage.py runserver 8001
    1.在浏览器中输入:Http://127.0.0.1:8001/app01/index/ 
      会看到我是app01下的index页面。。。
    
    2.在浏览器中输入:Http://127.0.0.1:8001/app02/index/ 
      会看到我是app02下的index页面。。。
    

    五、反向解析

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

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

    上述这样通过别名获取路径的过程称之为反向解析:也就是说,通过一串字符串反向解析为访问这个地址的路径

    案例:登录成功后跳转到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),
        
        # login/别名的为login_page
        url(r'^login/$',views.login,name='login_page'),
        
        # index/别名的为index_page
        url(r'^index/$',views.index,name='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 == 'cecilia' and pwd == '123':
    
            # reverse会将别名index_page反向解析为路径/index/
            url = reverse('index_page')
            # 重定向到/index/
            return redirect(url)
        
        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>
    

    测试:

    # 先启动django项目server端----命令行 
    python manage.py runserver 8001
    
    1. 在浏览器输入:Http://127.0.0.1:8001/login 
       # 会看到登录页面,输入正确的用户名密码会跳转到index.html
    2.当我们修改路由表中匹配路径的正则表达式时,程序其他部分都不用修改
    

    总结:

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

    扩展阅读:如果路径存在分组的方向解析使用

    # 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'^article/(d+)/$',view.article,name='article_page'),#无名分组
        url(r'^user/(?p<uid>d+)/$',view.article,name='user_page'),#有名分组
    ]
    
    # 1. 针对无名分组,比如我们要反向解析处:/article/1/这种路径,写法如下
    在views.py中,反向解析的使用
    url =  reverse('article_page',args=(1,))
    在模板login.html文件中,反向解析的使用
    {% url 'article_page' 1 %}
    
    
    # 2.针对有名分组,比如我们要反向解析出:/user/1 这种路径,写法如下
    在view.py中,反向解析的使用
    方式一: url =  reverse('article_page',args=(1,))
    方式二: url =  reverse('user_page',kwargs={'uid':1})
    在模板login.html文件中,反向解析的使用
    方式一:{% url 'article_page' 1 %}
    方式二:{% url 'user_page' uid=1 %}
    
    
    总结:无名分组和有名分组的反向解析,只要记一种就行了。
    

    六、名称空间

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

    6.1 创建两个app

    # 新建项目mysite2---此处用命令行命令创建
    # 切换到存储项目的目录下
    django-admin startproject mysite2
    
    # 切换到新建的项目下
    # 创建两个app项目(app01和app02)
    python3 startapp app01
    python3 startapp app02
    

    此处注意以下三点:

    1. 自己用命令行创建的django项目,需要自己手动去创建templates文件夹来存放需要用到的.html文件
    2. 并且去settings的TEMPLATES中配置:'DIRS': [os.path.join(BASE_DIR,'templates')]
    3. 在settings的INSTALLED_APPS中去配置‘app01’ ‘app02’

    6.2 手动在app中创建urls.py存放自己的路由

    1. app01下的urls.py路由表文件

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

    2.app02下的urls.py路由文件

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

    6.3 在每个app下的views.py中编写视图函数

    在视图函数中正对别名’index_page‘ 做反向解析

    1. app01下的views.py视图函数文件

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

    app02下的views.py视图函数文件

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

    6.4 总的mysite2文件夹的路由表中urls.py

    from django.conf.urls import url,include
    from django.contrib import admin
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
    
        # 因为功能太多,所以在总的路由表中,做路由分发,分发给各个app功能应用
        # 新增的两条路由,注意不能以$结尾
        # 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'))
    ]
    

    6.5 测试:(覆盖问题)

    # 先启动项目server端---命令行式:python manage.py runserver 8001
    1.在浏览器中输入:Http://127.0.0.1:8001/app01/index/ 
      会看到我是app02下的index页面。。。,反向解析的结果为/app02/index/
    
    2.在浏览器中输入:Http://127.0.0.1:8001/app02/index/ 
      会看到我是app02下的index页面。。。,反向解析的结果为/app02/index/
    

    在测试时,我们发现如论在浏览器中输入:Http: //127.0.0.1:8001/app01/index/ 还是

    Http: //127.0.0.1:8001/app02/index/ 针对别名’index_page‘方向解析的结果都是/app02/index/,覆盖了app01下别名的解析。

    6.6 解决办法

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

    1. 总( mysite2 下)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下的views.py中的视图函数,针对不同的名称空间中的别匿名’index_page‘做反向解析

      app01下的view.py视图函数

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

      app02下的view.py视图函数

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

    6.7 测试(解决测试问题)

    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/

    6.8 总结+补充

    1. 在视图函数中基于名称空间的反向解析,用法如下
    url = reverse('名称空间的名字:待解析的别名')
    
    2.在模板里基于名称空间的反向解析,用法如下
    {% url '名称空间的名字:待解析的别名'%}
    

    七、Django2.0版的re_path与path

    7.1 re_path

    Django 2.0 中的re_path 与 Django 1.0 的 url 一样,传入的第一个参数都是正则表达式

    from django.urls import re_path # django 2.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'))),
    ]
    

    7.2 path

    在Django 2.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 采用了同样的正则表达式,重发编写了三遍,存在冗余问题,并且极不容易管理,因为以但articles_id规则需要改变,则必须同时修改三处的代码
        re_path(r'^articles/(?P<article_id>[a-zA-z0-9]+)/detail/$',view,detail_view),
        re_path(r'^articles/(?P<article_id>[a-zA-z0-9]+)/edit/$',view,edit_view),
        re_path(r'^articles/(?P<article_id>[a-zA-z0-9]+)/delete/$',view,delete_view),
    ]
    
    
    # views.py视图文件
    from django.shortcuts import render,HttpResponse
    
    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如何解决上述两个问题的呢?请看示例

    # urls.py路由文件
    
    from django.urls import 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/',view,detail_view),
        path('^articles/<int:article_id>edit/',view,edit_view),
        path('^articles/<int:article_id>delete/',view,delete_view),
    ]
    
    

    强调:

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

    django:默认支持5中转换器(Path converters)

    str:匹配处理路径分隔符(/)之外的非空字符串,这是默认的形式
    int:匹配正整数,包含0
    slug:匹配字母,数字及斜杠、下划线组成的字符串
    uuid:匹配格式化的uuid,如075194d3-6885-417e-a8a8-6c931e272f00 
    path:匹配任何非空字符串,包含路径分隔符(/)(不能用?)    
    
    
  • 相关阅读:
    ESP8266 RTOS SDK烧写环境构建
    杂记:解决Android扫描BLE设备名称不刷新问题
    deviceMotion.userAcceleration加速度方向
    二、多功能提示框——MBProgressHUD
    一、初识CocoaPods——XCode的依赖库管理工具
    摄像机的控制
    DOTween 使用方法
    Unity依赖注入使用详解
    Unity中对SQL数据库的操作
    unity导弹算法 预计目标点
  • 原文地址:https://www.cnblogs.com/XuChengNotes/p/11722941.html
Copyright © 2011-2022 走看看