003 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关键参数介绍:

  • regex:正则表达式,用来匹配客户端访问url地址的路径部分
    • 例如:url的地址为http: //127.0.0.1:8000/index/,正则表达式要匹配的部分是index/
  • view:通常是一个url对应的视图函数,用来处理业务逻辑
  • 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),# 这里是新建一个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:匹配任何非空字符串,包含路径分隔符(/)(不能用?)    

猜你喜欢

转载自www.cnblogs.com/XuChengNotes/p/11722941.html
003