Python进阶27-Django 路由层

2022-09-26 12:46:22 浏览数 (1)

  • 创建Django项目
  • 路由层介绍
  • 有名分组,无名分组
  • 反向解析
  • 路由分发
  • 名称空间
  • Django配置/和404
  • Django 路由不自动加/(几乎不用)
  • Django2.0 和 Django 1.0路由层区别

-曾老湿, 江湖人称曾老大。


-多年互联网运维工作经验,曾负责过大规模集群架构自动化运维管理工作。 -擅长Web集群架构与自动化运维,曾负责国内某大型金融公司运维工作。 -devops项目经理兼DBA。 -开发过一套自动化运维平台(功能如下): 1)整合了各个公有云API,自主创建云主机。 2)ELK自动化收集日志功能。 3)Saltstack自动化运维统一配置管理工具。 4)Git、Jenkins自动化代码上线及自动化测试平台。 5)堡垒机,连接Linux、Windows平台及日志审计。 6)SQL执行及审批流程。 7)慢查询日志分析web界面。


创建Django项目


图形创建项目

注释settings.py的csrf中间件

代码语言:javascript复制
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

设置静态资源路径settings.py

代码语言:javascript复制
STATIC_URL = '/static/'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR,'static')
]

创建static

路由层介绍


作用

URL配置(URLconf)就像Django 所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表;你就是以这种方式告诉Django,对于客户端发来的某个URL调用哪一段逻辑代码对应执行

代码语言:javascript复制
from django.conf.urls import url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

url是一个函数,传递两个参数,我们从app01项目中,导入views,从而关联视图函数

代码语言:javascript复制
"""
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'publish', views.publish),
    url(r'publishadd', views.publishadd),
]

views.py

代码语言:javascript复制
from django.shortcuts import render,HttpResponse,redirect

# Create your views here.

def publish(request):
    if request.method == 'GET':
        return HttpResponse('publish GET is ok')
    elif request.method == 'POST':
        return HttpResponse('publish POST is ok')

def publishadd(request):
    if request.method == 'GET':
        return HttpResponse('publishadd GET is ok')
    elif request.method == 'POST':
        return HttpResponse('publishadd POST is ok')

卧槽,我明明访问的是publishadd,为啥给我返回的是publish的页面?

因为url那里放的是正则表达式,在url中匹配了publish就匹配成功了,所以返回publish页面

代码语言:javascript复制
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^publish/$', views.publish),
    url(r'^publishadd/$', views.publishadd),
]


匹配年

代码语言:javascript复制
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^publish/[0-9]{4}$', views.publish),
    url(r'^publishadd/$', views.publishadd),
]


匹配任意长度数字

代码语言:javascript复制
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^publish/d $', views.publish),
    url(r'^publishadd/$', views.publishadd),
]

有名分组,无名分组


正则例子

代码语言:javascript复制
import re
ret=re.search('(?P<year>[0-9]{4})/([0-9]{2})','2012/12')
print(ret.group())
print(ret.group(1))
print(ret.group(2))
print(ret.group('year'))

上面的示例使用简单的、没有命名的正则表达式组(通过圆括号)来捕获URL 中的值并以位置 参数传递给视图。在更高级的用法中,可以使用命名的正则表达式组来捕获URL 中的值并以关键字 参数传递给视图。

在Python 正则表达式中,命名正则表达式组的语法是(?Ppattern),其中name 是组的名称,pattern 是要匹配的模式。


无名分组

下面是以上URLconf 使用命名组的重写:

代码语言:javascript复制
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # url(r'^publish/d $', views.publish),
    # url(r'^publishadd/$', views.publishadd),
    url(r'^publish/([0-9]{4})/$', views.publish),
]

访问页面报错,因为在视图函数中,我们没有接收这个分组的参数

代码语言:javascript复制
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # url(r'^publish/d $', views.publish),
    # url(r'^publishadd/$', views.publishadd),
    url(r'^publish/([0-9]{4})/$', views.publish),
]
代码语言:javascript复制
from django.shortcuts import render,HttpResponse,redirect

# Create your views here.

def publish(request,year):
    if request.method == 'GET':
        return HttpResponse('publish GET %s is ok' %year)
    elif request.method == 'POST':
        return HttpResponse('publish POST is ok')

def publishadd(request):
    if request.method == 'GET':
        return HttpResponse('publishadd GET is ok')
    elif request.method == 'POST':
        return HttpResponse('publishadd POST is ok')

但是如果我再加一个正则的分组,这样的话我们需要按照位置传参*args

代码语言:javascript复制
from django.shortcuts import render,HttpResponse,redirect

# Create your views here.

def publish(request,*args):
    if request.method == 'GET':
        return HttpResponse('publish GET %s is ok' %args)
    elif request.method == 'POST':
        return HttpResponse('publish POST is ok')

def publishadd(request):
    if request.method == 'GET':
        return HttpResponse('publishadd GET is ok')
    elif request.method == 'POST':
        return HttpResponse('publishadd POST is ok')

但是一般不会这么传参,工作中一般也就一个两个。


有名分组

代码语言:javascript复制
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # url(r'^publish/d $', views.publish),
    # url(r'^publishadd/$', views.publishadd),
    # url(r'^publish/([0-9]{4})/$', views.publish),
    url(r'^publish/(?P<year>[0-9]{4})/$', views.publish),
]
代码语言:javascript复制
from django.shortcuts import render,HttpResponse,redirect

# Create your views here.

def publish(request,year):
    if request.method == 'GET':
        return HttpResponse('publish GET %s is ok' %year)
    elif request.method == 'POST':
        return HttpResponse('publish POST is ok')

def publishadd(request):
    if request.method == 'GET':
        return HttpResponse('publishadd GET is ok')
    elif request.method == 'POST':
        return HttpResponse('publishadd POST is ok')

总结:

代码语言:javascript复制
## 无名分组
        -按位置传参
        -分组之后,会把分组出来的数据,当位置参数,传到视图函数,所以,视图函数需要定义形参
        -url(r'^publish/([0-9]{4})/([0-9]{2})$', views.publish),
        -def publish(request,*args):   视图函数可以这样接收

## 有名分组
        -按关键字传参
        -有名分组之后,会把分组出来的数据,当关键字参数,传到视图函数,所以,视图函数需要定义形参,形参名字要跟分组的名字对应,与无关
        -url(r'^publish/(?P<year>[0-9]{4})/(?P<mounth>[0-9]{2})/$', views.publish),
        -def publish(request, mounth,year):
        *****有名分组和无名分组,不要混用

反向解析


反向解析介绍

在使用Django 项目时,一个常见的需求是获得URL 的最终形式,以用于嵌入到生成的内容中(视图中和显示给用户的URL等)或者用于处理服务器端的导航(重定向等)。人们强烈希望不要硬编码这些URL(费力、不可扩展且容易产生错误)或者设计一种与URLconf 毫不相关的专门的URL 生成机制,因为这样容易导致一定程度上产生过期的URL。

在需要URL 的地方,对于不同层级,Django 提供不同的工具用于URL 反查:

在模板中:使用url 模板标签。 在Python 代码中:使用from django.urls import reverse()函数


举例

publish.html

代码语言:javascript复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>publish</title>
</head>
<body>
<a href="/publishadd/">点我一下子</a>
</body>
</html>

urls.py

代码语言:javascript复制
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^publish/$', views.publish),
    url(r'^publishadd/$', views.publishadd),
    # url(r'^publish/([0-9]{4})/$', views.publish),
    # url(r'^publish/(?P<year>[0-9]{4})/$', views.publish),
]

views.py

代码语言:javascript复制
from django.shortcuts import render,HttpResponse,redirect

# Create your views here.

def publish(request):
    if request.method == 'GET':
        return render(request,'publish.html')

def publishadd(request):
    return HttpResponse('publishadd is ok')

但是以后项目多了,我们有多个页面的时候,如下,我们该怎么办?

代码语言:javascript复制
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^publish/$', views.publish),
    url(r'^publishadd/$', views.publishadd),
    url(r'^publishadd1/$', views.publishadd),
    url(r'^publishadd2/$', views.publishadd),
    url(r'^publishadd3/$', views.publishadd),
    url(r'^publishadd4/$', views.publishadd),
    url(r'^publishadd5/$', views.publishadd),
    url(r'^publishadd6/$', views.publishadd),
    # url(r'^publish/([0-9]{4})/$', views.publish),
    # url(r'^publish/(?P<year>[0-9]{4})/$', views.publish),
]

我们要在前端添加一堆a标签嘛?当然不是,所以我们需要用到反向解析

代码语言:javascript复制
"""
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^publish/$', views.publish),
    url(r'^publishadd/$', views.publishadd,name='add'),
    # url(r'^publish/([0-9]{4})/$', views.publish),
    # url(r'^publish/(?P<year>[0-9]{4})/$', views.publish),
]
代码语言:javascript复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>publish</title>
</head>
<body>
<a href="{% url 'add' %}">点我一下子</a>
</body>
</html>

后台随意修改。但是我们要在后台的视图层也要做一次反向解析,导入:reverse

代码语言:javascript复制
from django.shortcuts import render,HttpResponse,redirect,reverse

# Create your views here.

def publish(request):
    if request.method == 'GET':
        url=reverse('add')
        return redirect(url)

def publishadd(request):
    return HttpResponse('publishadd is ok')

url使用无名参数如何传参

代码语言:javascript复制
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^publish/$', views.publish),
    url(r'^publishadd/([0-9]{4})$', views.publishadd,name='add'),
    # url(r'^publish/([0-9]{4})/$', views.publish),
    # url(r'^publish/(?P<year>[0-9]{4})/$', views.publish),
]

代码语言:javascript复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>publish</title>
</head>
<body>
<a href="{% url 'add' 2018 %}">点我一下子</a>
</body>
</html>

视图层传递参数

代码语言:javascript复制
from django.shortcuts import render,HttpResponse,redirect,reverse

# Create your views here.

def publish(request):
    if request.method == 'GET':
        url=reverse('add',args=(2018,))
        return redirect(url)

def publishadd(request,year):
    return HttpResponse('publishadd is ok')


url使用有名参数如何传参

模板层反向解析

代码语言:javascript复制
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^publish/$', views.publish),
    url(r'^publishadd/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})$', views.publishadd,name='add'),
    # url(r'^publish/([0-9]{4})/$', views.publish),
    # url(r'^publish/(?P<year>[0-9]{4})/$', views.publish),
]
代码语言:javascript复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>publish</title>
</head>
<body>
<a href="{% url 'add' year=2018 month=12 %}">点我一下子</a>
</body>
</html>
代码语言:javascript复制
from django.shortcuts import render,HttpResponse,redirect,reverse

# Create your views here.

def publish(request):
    if request.method == 'GET':
        # url=reverse('add',args=(2018,))
        # return redirect(url)
        return render(request,'publish.html')

def publishadd(request,year,month):
    return HttpResponse('publishadd is ok')

视图层反向解析

代码语言:javascript复制
## 方法一:
from django.shortcuts import render,HttpResponse,redirect,reverse

# Create your views here.

def publish(request):
    if request.method == 'GET':
        url=reverse('add',args=(2018,12))
        return redirect(url)
        # return render(request,'publish.html')

def publishadd(request,year,month):
    return HttpResponse('publishadd is ok')

代码语言:javascript复制
## 方法二:
from django.shortcuts import render,HttpResponse,redirect,reverse

# Create your views here.

def publish(request):
    if request.method == 'GET':
        url=reverse('add',kwargs={'year':2018,'month':11})
        return redirect(url)
        # return render(request,'publish.html')

def publishadd(request,year,month):
    return HttpResponse('publishadd is ok')

路由分发

当项目越来越大的时候,urls.py中的内容会越来越多,所以我们根据 app 来创建不同的路由


再创建一个APP

代码语言:javascript复制
## 创建一个名为blog的APP
MacBook-pro:route driverzeng$ python3 manage.py startapp blog

注册APP

代码语言:javascript复制
### 方法一:(推荐方法)
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
    'blog.apps.BlogConfig',
]

### 方法二:
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
    'blog',
]

方法一的名字,怎么来的呢?


在各个项目中创建urls.py

其实urls文件中已经告诉我们如何包含其他的URLconf

代码语言:javascript复制
Including another URLconf
    1. Import the include() function: from django.conf.urls import url, include
    2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))

总路由urls.py文件中,导入include方法,然后添加blogapp01的路由。

代码语言:javascript复制
from django.conf.urls import url,include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^blog/', include('blog.urls')),
    url(r'^app01/', include('app01.urls')),
]

app01路由文件urls.py

代码语言:javascript复制
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^publish/$',views.publish),
]

app01视图文件views.py

代码语言:javascript复制
from django.shortcuts import render,HttpResponse,redirect,reverse

# Create your views here.

def publish(request):
    if request.method == 'GET':
        return HttpResponse('app01 publish is OK')

打开浏览器访问:http://127.0.0.1:8000/app01/publish/

blog路由文件urls.py

代码语言:javascript复制
from django.conf.urls import url
from blog import views

urlpatterns = [
    url(r'^article/$',views.article),
]

blog视图文件views.py

代码语言:javascript复制
from django.shortcuts import render,HttpResponse

# Create your views here.

def article(request):
    return HttpResponse('blog article OK')

打开浏览器访问:http://127.0.0.1:8000/blog/article/

名称空间

命名空间(英语:Namespace)是表示标识符的可见范围。一个标识符可在多个命名空间中定义,它在不同命名空间中的含义是互不相干的。这样,在一个新的命名空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其它命名空间中。

由于name没有作用域,Django在反解URL时,会在项目全局顺序搜索,当查找到第一个name指定URL时,立即返回 我们在开发项目时,会经常使用name属性反解出URL,当不小心在不同的app的urls中定义相同的name时,可能会导致URL反解错误,为了避免这种事情发生,引入了命名空间。

总而言之,就是,在不同app中,反向解析名字相同就会混乱。

解决方案,就是在总路由中设置名称空间

代码语言:javascript复制
from django.conf.urls import url,include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^blog/', include('blog.urls',namespace='blog')),
    url(r'^app01/', include('app01.urls',namespace='app01')),
]

app01使用反向解析

代码语言:javascript复制
from django.conf.urls import url
from app01 import views

urlpatterns = [
    url(r'^publish/$',views.publish,name='zls'),
    url(r'^zls/$',views.zls),
]
代码语言:javascript复制
from django.shortcuts import render,HttpResponse,redirect,reverse

# Create your views here.

def publish(request):
    if request.method == 'GET':
        return HttpResponse('app01 publish is OK')

def zls(request):
    url=reverse('app01:zls')  ## 输入zls的时候系统会自动提示app01:zls
    return redirect(url)

blog使用反向解析

代码语言:javascript复制
from django.conf.urls import url
from blog import views

urlpatterns = [
    url(r'^article/$',views.article,name='zls'),
    url(r'^zls/$',views.zls),
]
代码语言:javascript复制
from django.shortcuts import render,HttpResponse,redirect,reverse

# Create your views here.

def article(request):
    return HttpResponse('blog article OK')

def zls(request):
    url=reverse('blog:zls')
    return redirect(url)

但是我们尽量不要用名称空间,如果需要反向解析,尽量就使用项目前缀 name='app01_zls'name=blog_zls

Django配置/和404


/路由

直接访问主页

代码语言:javascript复制
from django.conf.urls import url,include
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r'^$',views.index),
    url(r'^admin/', admin.site.urls),
    url(r'^blog/', include('blog.urls',namespace='blog')),
    url(r'^app01/', include('app01.urls',namespace='app01')),
]
代码语言:javascript复制
from django.shortcuts import render,HttpResponse,redirect,reverse,Http404

# Create your views here.

def publish(request):
    if request.method == 'GET':
        return HttpResponse('app01 publish is OK')

def zls(request):
    url=reverse('app01:zls')
    return redirect(url)

def index(request):
    return HttpResponse('主页')


404页面

代码语言:javascript复制
from django.conf.urls import url,include
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$',views.index),
    url(r'^blog/', include('blog.urls',namespace='blog')),
    url(r'^app01/', include('app01.urls',namespace='app01')),
    url(r'', views.page_not_fount),
]
代码语言:javascript复制
from django.shortcuts import render,HttpResponse,redirect,reverse
from django.http import HttpResponseNotFound

# Create your views here.

def publish(request):
    if request.method == 'GET':
        return HttpResponse('app01 publish is OK')

def zls(request):
    url=reverse('app01:zls')
    return redirect(url)

def index(request):
    return HttpResponse('主页')

def page_not_fount(request):
    return HttpResponseNotFound('页面走丢啦')

Django 路由不自动加/(几乎不用)


APPEND_SLASH

代码语言:javascript复制
# 是否开启URL访问地址后面不为/跳转至带有/的路径的配置项
APPEND_SLASH=True

Django2.0 和 Django 1.0路由层区别


思考问题

django2.0的re_path和1.0的url一样

思考情况如下:

代码语言:javascript复制
urlpatterns = [  
    re_path('articles/(?P<year>[0-9]{4})/', year_archive),  
    re_path('article/(?P<article_id>[a-zA-Z0-9] )/detail/', detail_view),  
    re_path('articles/(?P<article_id>[a-zA-Z0-9] )/edit/', edit_view),  
    re_path('articles/(?P<article_id>[a-zA-Z0-9] )/delete/', delete_view),  
]

考虑下这样的两个问题:

第一个问题,函数 year_archive 中year参数是字符串类型的,因此需要先转化为整数类型的变量值,当然year=int(year) 不会有诸如如TypeError或者ValueError的异常。那么有没有一种方法,在url中,使得这一转化步骤可以由Django自动完成?

第二个问题,三个路由中article_id都是同样的正则表达式,但是你需要写三遍,当之后article_id规则改变后,需要同时修改三处代码,那么有没有一种方法,只需修改一处即可?

在Django2.0中,可以使用 path 解决以上的两个问题。

代码语言:javascript复制
from django.urls import path  
from . import views  
urlpatterns = [  
    path('articles/2003/', views.special_case_2003),  
    path('articles/<int:year>/', views.year_archive),  
    path('articles/<int:year>/<int:month>/', views.month_archive),  
    path('articles/<int:year>/<int:month>/<slug>/', views.article_detail),  
  # path才支持,re_path不支持  path('order/<int:year>',views.order),
]

基本规则

使用尖括号(<>)从url中捕获值。 捕获值中可以包含一个转化器类型(converter type),比如使用 int:name 捕获一个整数变量。若果没有转化器,将匹配任何字符串,当然也包括了 / 字符。 无需添加前导斜杠。 以下是根据 2.0官方文档 而整理的示例分析表:(跟上面url的匹配关系)


path转化器

文档原文是Path converters,暂且翻译为转化器。

代码语言:javascript复制
Django默认支持以下5个转化器:

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

自定义转化器

对于一些复杂或者复用的需要,可以定义自己的转化器。转化器是一个类或接口,它的要求有三点:

1.regex 类属性,字符串类型

2.to_python(self, value) 方法,value是由类属性 regex 所匹配到的字符串,返回具体的Python变量值,以供Django传递到对应的视图函数中。

3.to_url(self, value) 方法,和 to_python 相反,value是一个具体的Python变量值,返回其字符串,通常用于url反向引用。

例子:

代码语言:javascript复制
class FourDigitYearConverter:  
    regex = '[0-9]{4}'  
    def to_python(self, value):  
        return int(value)  
    def to_url(self, value):  
        return 'd' % value  

使用register_converter 将其注册到URL配置中:

代码语言:javascript复制
from django.urls import register_converter, path  
from . import converters, views  
register_converter(converters.FourDigitYearConverter, 'yyyy')  
urlpatterns = [  
    path('articles/2003/', views.special_case_2003),  
    path('articles/<yyyy:year>/', views.year_archive),  
    ...  
]

0 人点赞