Django 系列博客(六)

前言

本篇博客介绍 Django 中的路由控制部分,一个网络请求首先到达的就是路由这部分,经过路由与视图层的映射关系再执行相应的代码逻辑并将结果返回给客户端。

Django 中路由的作用

URL 配置(URLconf)可以比作是 Django 支撑网站的目录。它的本质是 URL 要为该 URL 滴啊用的视图函数之间的映射表。以这种方式告诉 Django,对于客户端发来的 URL 要具体调用视图层的哪段代码。

from django.urls import url
from app import views urlpatterns = [
url(r'^$', views.home),
]
# ^$这个路由对应视图函数中的 home 方法,只要浏览器往该网址发送请求,就会响应到这个函数执行。

简单的路由配置

from django.conf.urls import url

urlpatterns = [
url(正则表达式, views视图函数, 默认参数, 路由别名),
]
  • 正则表达式:一个正则表达式字符串;
  • views 视图函数:一个可调用对象,通常为一个视图函数或一个指定视图路径的字符串;
  • 默认参数:可选的要传递给视图函数的默认参数(字典形式);
  • 路由别名:一个可选的name 参数。
from django.conf.urls import url
from app import views urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^article/(\d{4})/(\d{2})', views.article),
url(r'^user/(?P<name>[a-z]{3})', views.user_havename),
url(r'^user/$', views.user),
]

注意:

  • 若要从 URL 中捕获一个值,只需要在它周围放置一对圆括号;
  • 不需要添加一个前导的反斜杠,因为每个 url 都有。例如,应该是^articles而不是^/articles
  • 每个正则表达式前面的r是可选的,但是建议加上,表示这是个原生字符串,字符串中的任何字符都不应该转义。
  • urlpatterns 中的元素按照书写顺序从上往下逐一匹配正则表达式,一旦匹配成功则不再继续。
一些请求的例子:

/articles/2005/03/ 请求将匹配列表中的第三个模式。Django 将调用函数views.month_archive(request, '2005', '03')。
/articles/2005/3/ 不匹配任何URL 模式,因为列表中的第三个模式要求月份应该是两个数字。
/articles/2003/ 将匹配列表中的第一个模式不是第二个,因为模式按顺序匹配,第一个会首先测试是否匹配。请像这样自由插入一些特殊的情况来探测匹配的次序。
/articles/2003 不匹配任何一个模式,因为每个模式要求URL 以一个反斜线结尾。
/articles/2003/03/03/ 将匹配最后一个模式。Django 将调用函数views.article_detail(request, '2003', '03', '03')。

APPEND_SLASH

该参数的作用是会在访问连接的时候,如果连接没有加斜杠,会自动加上。可以在global_settings.py中修改。

项目的 settings 中没有改配置的选项,一个项目会有两个 settings。

效果:

from django.conf.urls import url
from app_01 import views urlpatterns = [
url(r'^blog/$', views.blog),
]

当我们访问http://www.example.com/blog时,默认将网址自动替换成http://www.example.com/blog/。如果将该配置值设为false,此时在向该网址发送请求时就会提示找不到页面。

分组

当我们需要捕获 url 中的参数并要传递给视图函数时,有两种捕获方式:无名分组和有名分组。无名分组就是该值没有变量标识,传值的时候采用位置参数传递;有名分组就是给捕获的值赋值一个变量,这样就可以通过关键字参数传值了。

无名分组

# urls.py文件
from django.conf.urls import url
from app_01 import views urlpatterns = [
url(r'^article/(\d{4})/(\d{2})', views.article),
]
# views.py文件
from django.shortcuts import HttpResponse def article(reques, year, month):
return HttpResponse('您要查看%s 年%s 月的文章' %(year, month))

通过圆括号捕获年份和月份,然后通过位置参数传递给 year 和 month 变量,接着在 views 函数中使用。

有名分组

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 正则表达式中,命名有名分组的语法时(P<变量名>pattern),其中变量名是分组的标识符,pattern 是要匹配的正则表达式。下面是上面 URLconf 使用有名分组的重写:

from django.urls import path, re_path
from app_01 import views urlpatterns = [
re_path(r'^articles/2003/$', views.special_case_2003),
re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
re_path(r'^articles/(?P<year[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
re_path(r'^articles/(?P<year[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.archive_detail),
]
# 捕获到的数据都是 str 类型
# 视图函数里可以指定默认值
url('blog/$', views.blog),
url('blog/?(?P<num>[0-9]{1})', views.blog),
def blog(request, num=1):
print(num)
return HttpResponse('ok')

这个实现与前面的示例完全相同,只有一个细微的差别:捕获的值作为关键字参数而不是位置参数传递给视图函数。例如:

/articles/2005/03/ 请求将调用views.month_archive(request, year='2005', month='03')函数,而不是views.month_archive(request, '2005', '03')。
/articles/2003/03/03/ 请求将调用函数views.article_detail(request, year='2003', month='03', day='03')。

在实际应用中,这意味你的 URLconf 会更加清晰且不容易产生参数顺序问题的错误,可以在视图函数定义中重新安排参数的顺序。

路由分发

其实和项目名相同的文件夹下面的urls.py文件是整个项目的根路由:

这是整个项目的根路由,所有向该项目发送的连接请求,首先需要从该路由配置里面过滤,如果只有一个应用,或者路由配置不多一个根路由就足够了,但当 app 多起来之后还是使用一个根路由配置会造成路由混乱,所以有了路由分发,比如这是发往app_01应用的连接,那么在根路由中进行路由分发,把连接转向app_01中的路由中进行处理,这就是路由分发。

# 根路由配置
from django.conf.urls import url, include
from app_01 import views
from app_01 import urls urlpatterns = [
url(r'^app_01/', include('app_01.urls')), # 注意前面的正则表达式后面不能加$
url(r'^app_01/', include(urls)),
]
# 应用路由配置
from django.conf.urls import url
from app_01 import views urlpatterns = [
url(r'^test/(?P<year>[0-9]{2})/$', views.url_test),
]

反向解析

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

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

  • 在模板中:使用 url 模板标签;
  • 在 python 代码中:使用from django.urls import reverse
# urls.py文件
from django.urls import path, re_path
from app_01 import views urlpatterns = [
re_path(r'^test/(?P<year>[0-9]{2})/(P?<month>[0-9]{2})/$', views.url_test, name='test'),
]
<!-- html文件 -->
<a href="{% url 'test' 10 23 %}">哈哈</a>
# views.py文件
from django.shortcuts import render, HttpResponse, redirect, reverse def url_test(request, year, month):
url = reverse('test', args=(10, 20))
return HttpResponse('ok')

总结:

  • 在 html 代码里面使用{% url '别名' 参数 参数... %}
  • 在视图函数中:
    • url = reverse('test')
    • url = reverse('test', args=(10, 20))

当命名 URL 模式时,要确保使用的名称不会与其他应用中的名称产生冲突。如果你的 URL 模式叫做 comment,而另外一个应用中也有一个同样的名字,当你在模板中使用这个名称的时候不能保证将插入哪个 URL。在 URL 名称加上一个前缀,比如应用的名称,将会减少冲突的可能。建议使用myapp-comment

名称空间

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

由于name没有作用域,Django 在返解 URL 时,会在项目的全局路径中按顺序搜索,当查找到第一个name指定的 URL 时,立即返回。

在开发项目时,会经常使用name属性反解出 URL,当不小心在不同的 app 的 urls 中定义相同的name时,可能会导致 URL 反解错误,为了避免发生引入了命名空间。

创建 app01和 app02

python manage.py startapp app01

python manage.py startapp app02

根路由

from django.urls import path,re_path,include

urlpatterns = [
path('app01/', include('app01.urls')),
path('app02/', include('app02.urls'))
]

app01的 urls.py

from django.urls import path,re_path
from app01 import views urlpatterns = [
re_path(r'index/',views.index,name='index'),
]

app02的 urls.py

from django.urls import path, re_path, include
from app02 import views urlpatterns = [
re_path(r'index/', views.index,name='index'),
]

app01的视图

def index(request):
url=reverse('index')
print(url)
return HttpResponse('index app01')

app02的视图

def index(request):
url=reverse('index')
print(url)
return HttpResponse('index app02')

这样都找index.html,app01和 app02找到的都是app02的 index。如何处理?在路由分发的时候指定名称空间。根路由在路由分发是,指定名称空间。

# 一
path('app01/', include(('app01.urls','app01'))),
path('app02/', include(('app02.urls','app02'))) # 二
url(r'app01/',include('app01.urls',namespace='app01')),
url(r'app02/',include('app02.urls',namespace='app02')) # 三
url(r'app01/',include(('app01.urls','app01'))),
url(r'app02/',include(('app02.urls','app02')))

在视图函数反向解析的时候,指定名称空间:

url=reverse('app02:index')
print(url)
url2=reverse('app01:index')
print(url2)

在模板里面也是用相应的名称空间名:

<a href="{% url 'app02:index'%}">哈哈</a>

Django2.0版的 path

Django2.0的re_path和1.0的url一样。2.0多了个path

思考情况如下:

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 解决以上的两个问题。

基本示例

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>捕获一个整数变量。如果没有转化器,将匹配任何字符串,也包含/字符。
  • 无需添加前导斜杠

以上是示例,分别和上面的基本示例对应:

path 转换器

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

注册自定义转化器

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

  • regex类属性,字符串类型;
  • to_python(self, value)方法,value 是由类属性 regex 所匹配到的字符串,返回具体的 python 变量值,以供 Django 传递到对应的视图函数中;
  • to_url(self, value)方法,和to_python相反,value 是一个具体的 python 变量值,返回其字符串,通常用于 url 反向引用。

示例

class FourDigitYearConverter:
regex = '[0-9]{4}' def to_python(self, value):
return int(value) def to_url(self, value):
return '%04d' % value

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

from django.urls import register_converter, path
from . import converters, views register_converter(converters.FourDigitYearConverter, 'yyyy') urlpatterns = [
path('articles/2003/', views.spwcial_case_2003),
path('articles/<yyyy:year>/', views.year_archive),
]

Django 系列博客(六)的更多相关文章

  1. Django 系列博客(十六)

    Django 系列博客(十六) 前言 本篇博客介绍 Django 的 forms 组件. 基本属性介绍 创建 forms 类时,主要涉及到字段和插件,字段用于对用户请求数据的验证,插件用于自动生成 h ...

  2. Django 系列博客(十四)

    Django 系列博客(十四) 前言 本篇博客介绍在 html 中使用 ajax 与后台进行数据交互. 什么是 ajax ajax(Asynchronous Javascript And XML)翻译 ...

  3. Django 系列博客(十三)

    Django 系列博客(十三) 前言 本篇博客介绍 Django 中的常用字段和参数. ORM 字段 AutoField int 自增列,必须填入参数 primary_key=True.当 model ...

  4. Django 系列博客(十二)

    Django 系列博客(十二) 前言 本篇博客继续介绍 Django 中的查询,分别为聚合查询和分组查询,以及 F 和 Q 查询. 聚合查询 语法:aggregate(*args, **kwargs) ...

  5. Django 系列博客(十一)

    Django 系列博客(十一) 前言 本篇博客介绍使用 ORM 来进行多表的操作,当然重点在查询方面. 创建表 实例: 作者模型:一个作者有姓名和年龄. 作者详细模型:把作者的详情放到详情表,包含生日 ...

  6. Django 系列博客(十)

    Django 系列博客(十) 前言 本篇博客介绍在 Django 中如何对数据库进行增删查改,主要为对单表进行操作. ORM简介 查询数据层次图解:如果操作 mysql,ORM 是在 pymysql ...

  7. Django 系列博客(九)

    Django 系列博客(九) 前言 本篇博客介绍 Django 模板的导入与继承以及导入导入静态文件的几种方式. 模板导入 模板导入 语法:``{% include '模板名称' %} 如下: < ...

  8. Django 系列博客(八)

    Django 系列博客(八) 前言 本篇博客介绍 Django 中的模板层,模板都是Django 使用相关函数渲染后传输给前端在显式的,为了想要渲染出我们想要的数据,需要学习模板语法,相关过滤器.标签 ...

  9. Django 系列博客(七)

    Django 系列博客(七) 前言 本篇博客介绍 Django 中的视图层中的相关参数,HttpRequest 对象.HttpResponse 对象.JsonResponse,以及视图层的两种响应方式 ...

随机推荐

  1. 使用tcpdump探测TCP/IP三次握手

    读计算机应该就同说过TCP/IP三次握手,但是都没有去验证过,今天心血来潮,去验证了一下,于是乎写下了这篇博客,可能写的可能有问题,还请多多指教 包括我学习,还有从很多资料来看资料,第三次握手,应该会 ...

  2. opencv2.4.13+python2.7学习笔记--OpenCV中的图像处理--图像轮廓

    阅读对象:无要求. 1.代码 ''' OpenCV中的轮廓 轮廓可以简单认为成将连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度.为了更加准确,要使用二值化图像.在寻找轮廓之前,要进行阈值化 ...

  3. scrapy的基础概念和流程

    1. 什么是scrapy Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,我们只需要实现少量的代码,就能够快速的抓取. Scrapy 使用了Twisted['twɪstɪd]异步网 ...

  4. sqoop错误集锦2

    1.使用sqoop技术将mysql的数据导入到Hive出现的错误如下所示: 第一次使用命令如下所示: 1 [hadoop@slaver1 sqoop-1.4.5-cdh5.3.6]$ bin/sqoo ...

  5. scrapy爬虫之断点续爬和多个spider同时爬取

    from scrapy.commands import ScrapyCommand from scrapy.utils.project import get_project_settings #断点续 ...

  6. 使用Spring+MySql实现读写分离(二)spring整合多数据库

    紧接着上一章,因为现在做的项目还是以spring为主要的容器管理框架,所以写以下spring如何整合多个数据源. 1. 背景 我们一般应用对数据库而言都是“读多写少”,也就说对数据库读取数据的压力比较 ...

  7. Redis-06.Cluster

    Redis Cluster是一个高性能高可用的分布式系统.由多个Redis实例组成的整体,数据按照一致性哈希算法存储分布在多个Redis实例上,并对使用虚拟槽(Slot)对一致性哈希算法进行改进,通过 ...

  8. Python selenium webdriver设置加载页面超时

    1.  pageLoadTimeout: pageLoadTimeout方法用来设置页面完全加载的超时时间,完全加载即页面全部渲染,异步同步脚本都执行完成.没有设置超时时间默认是等待页面全部加载完成才 ...

  9. Javascript高级编程学习笔记(38)—— DOM(4)Text

    Text类型 html页面中的纯文本内容就属于Text类型 纯文本内容可以包含转义后的html字符,但不能包括 html 代码 text类型具有以下属性.方法 nodeType:3 nodeName: ...

  10. 快速入门node.js

    运行node node ./1.js let不存在变量提升 /* const fs = require('fs') const path = require('path') fs.readFile(p ...