django路由系统

django路由系统

为了替应用程序设计对应的urls, 我们创建了一个URLConf(url configuration)的模块. URL配置就像一个字典的目录一样, 让我们的应用程序(视图函数)和对应的url(本质是正则表达式)形成映射关系. 以这种方式, 用户访问哪个url, django就能到正确的映射函数里去执行了.

url基本配置

django 1.11版本的路由匹配

from django.conf.urls import url

urlpatterns = [
     url(正则表达式, views视图函数,参数,别名),
]
  • 正则表达式: 正则表达式模式串

  • views视图函数: 一个callable对象, 指向执行具体业务逻辑代码的函数

  • 参数: 可选的要传递给视图函数的默认参数(字典形式)

  • 一个可选的name参数, 反向解析时需要使用

下面是Django 1.11 文档中的典型例子.

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^articles/2003/$', views.special_case_2003),
    url(r'^articles/([0-9]{4})/$', views.year_archive),
    url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
    url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]

注意事项:

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

django 2.0版本的路由配置

Django2.0版本以上的路由配置与1.0+版本的路由配置有了一些变化.

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:slug>/', views.article_detail),
]

注意事项:

  • 使用尖括号(<>)从url中捕获值
  • 捕获值中可以包含一个转化器类型(converter type),比如使用 <int:name> 捕获一个整数变量, 后面的视图函数的参数的名字也要与之对应. 若果没有转化器,将匹配任何字符串,当然也包括了 / 字符.
  • 无需添加前导斜杠
  • 默认的已经是使用path来做路由匹配了.
  • 经过转换器后转换后的类型可能也已经变为相应的数据类型了

默认的path转换器

  • str:非空的字符串类型。默认的转换器。但是不能包含斜杠;
  • int:匹配任意的零或者正数的整形。到视图函数中就是一个int类型;
  • slug:由英文中的横杠 - ,或者下划线 _ 连接英文字符或者数字而成的字符串;
  • uuid:匹配 uuid 字符串;
  • path:匹配非空的英文字符串,可以包含斜杠;

自定义转换器

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

  • regex 类属性,字符串类型

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

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

from django.urls import path, register_converter
class FourDigitYearConverter:
    regex = '[0-9]{4}'

    def to_python(self, value):
        """直接转换, 传递给对应的视图函数中作为参数"""
        return int(value)

    def to_url(self, value):
        return '%04d' % value


# 注册自定义的转换器
register_converter(FourDigitYearConverter, 'yyyy')

urlpatterns = [
    path('books/<yyyy:year>/', views.book_year),
]

以上这些就是2.0以上版本的新的url匹配规则了, 下面的例子仍然以1.0版本的url(正则)来做路由匹配.

路由配置中的斜杠问题

我们自己写的路由函数中通常会在最后添加上/后缀, 然后在settings全局配置中添加上下面一句话

APPEND_SLASH = True

这个配置项为true的含义是当用户输入的url不匹配任何一个url路由的时候, Django会先判断这个url是否是以/结尾的,如果是, 则直接返回404错误, 如果不以/结尾, 会自动加上/生成一个新的url, 然后再去urlConf进行匹配, 如果能访问的话, 便会返回一个301状态码, 并把这个新的url传给浏览器 , 如果还是不能访问, 则还会报404错误.

通常为了方便我们都会默认添加上这个斜杠.

分组

无名分组

下面的示例使用简单的正则表达式分组匹配(通过圆括号)来捕获URL中的值并以位置参数形式传递给视图。

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'books/(\d+)', views.books),
]
# views.py
def books(request, args):
    # 在无名分组中, 括号捕获到的参数都会以位置参数的形式传递到视图函数中, 名字可以随意取
    print(args)
    return HttpResponse(f'无名分组, 接收到参数{args}')

有名分组

在更高级的用法中,可以使用分组命名匹配的正则表达式组来捕获URL中的值并以关键字参数形式传递给视图.

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

urlpatterns = [
    url(r'books2/(?P<book_id>\d+)', views.books2),
]
def books2(request, book_id):
    return HttpResponse(f'收到有名分组{book_id}')

有名分组能使表达式更加清晰, 并且捕获到的参数会以关键字参数的形式传递到视图函数中, 这意味着参数的顺序不那么重要, 也不容易出错.

路由分发

当应用有很多的时候, 且每一个应用都有上百个视图函数的时候, 这时如果都写在总路由里面, 那么显然不合理, 和拆分的模块的思路一样, 路由可以写在多个文件中, 然后使用include函数导入即可.

urls = [
    url(r'^test1/', views.test1),
]
urlpatterns = [
    url(r'test/', include(urls)),
]

def test1(request):
    return HttpResponse('路由分发的test1')

这时候再浏览器里敲地址test/开头的都会被分发到urls这个子路由里面, 进行匹配, 和之前的逻辑并无不同.

命名URL和反向解析

在使用Django 项目时,一个常见的需求是获得URL的最终形式,以用于嵌入到生成的内容中(视图中和显示给用户的URL等)或者用于处理服务器端的导航(重定向等)。

人们强烈希望不要硬编码这些URL(费力、不可扩展且容易产生错误)或者设计一种与URLconf 毫不相关的专门的URL 生成机制,因为这样容易导致一定程度上产生过期的URL。

换句话讲,需要的是一个DRY 机制。除了其它有点,它还允许设计的URL 可以自动更新而不用遍历项目的源代码来搜索并替换过期的URL。

获取一个URL 最开始想到的信息是处理它视图的标识(例如名字),查找正确的URL 的其它必要的信息有视图参数的类型(位置参数、关键字参数)和值。
Django 提供一个办法是让URL 映射是URL 设计唯一的地方。你填充你的URLconf,然后可以双向使用它:

  • 根据用户/浏览器发起的URL 请求,它调用正确的Django 视图,并从URL 中提取它的参数需要的值。
  • 根据Django 视图的标识和将要传递给它的参数的值,获取与之关联的URL。

第一种方式是我们在前面的章节中一直讨论的用法。第二种方式叫做反向解析URL、反向URL 匹配、反向URL 查询或者简单的URL 反查。
在需要URL 的地方,对于不同层级,Django 提供不同的工具用于URL 反查:

  • 在模板中:使用url模板标签。
  • 在Python 代码中:使用django.core.urlresolvers.reverse() 函数。

以上是官方文档的直白翻译, 用大白话讲就是我们不希望在一些需要使用到url的地方把这个url的地址写死, 后期我们可能由于升级等原因需要改动url地址, 那么原来写死的url就可能过期无效, 这时候维护更新就非常麻烦, 所以需要找到一种反映映射关系的技术. 而这种方式就是Django给我们提供的url命名与url反向解析.

在上面我们也提到了url的基本配置还可以填写一个name参数, 这个就是当前url的名字. 使用方式如下所示:

urlpatterns = [
    url(r'books/(?P<book_id>\d+)', views.books, name='book_detail'),
]

from django.urls import reverse

def books(request, book_id):
    print(reverse('book_detail', kwargs={'book_id': 5}))  # out: /books/5
    return HttpResponse('Book')
# 模板中的写法
{% url 'book_detail' book_id=6 %}

正确的反向解析需要正确的传递给路由需要的参数, 如果是无名分组, 需要传递给args参数, 如果是有名分组, 需要将参数传递给kwargs字典中.

名称空间

真实的项目中会存在多个app, 而这些app又分别由许多人共同开发, 这就涉及到可能多个url名称会相同, 这时候想要正确的反向解析到对应app里的视图函数, 就需要利用到名称空间了.

具体用法见下面:

# 总路由, 进行分发, 配置各自的名称空间
urlpatterns = [
    url(r'app01/', include('app01.urls', namespace='app01')),
    url(r'app02/', include('app02.urls', namespace='app02')),
]

# app01的路由和视图
urlpatterns = [
    url(r'books/(?P<book_id>\d+)', views.books, name='book_detail'),
    url(r'test/', views.test),
]
# 视图
def books(request, book_id):
    print(reverse('book_detail', kwargs={'book_id': 5}))
    return HttpResponse('Book')

def test(request):
    print(reverse('app01:book_detail', kwargs={'book_id': 5}))
    print(reverse('app02:book_detail', kwargs={'book_id': 7}))
    return HttpResponse('ok')

# app02的路由和视图
urlpatterns = [
    url(r'books/(?P<book_id>\d+)', views.books, name='book_detail'),
]

# 现在在浏览器敲入http://127.0.0.1:8000/app01/test/进行测试, 最后test函数可以正确的输出
# 结果也如预期正确解析出来
# /app01/books/5
# /app02/books/7

模板中的使用和反向解析一样, 只需要加上名称空间名字: url名字即可.

{% url 'app01:book_detail' book_id=7 %}
posted @ 2019-09-24 19:20  yscl  阅读(180)  评论(0编辑  收藏  举报