路由和视图

HTTP服务端框架对不同功能模块的请求通常是按“路由”规划和拆分的,浏览器或HTTP客户端发起请求时在请求信息中设置不同的URL,服务端则根据URL找到对应的代码进行处理。Django中也是类似的,路由负责将URL请求映射到相应的视图函数(FBV)或类视图组件(CBV),这篇笔记我们介绍Django的路由系统。

路由配置

Django的路由是通过配置文件集中配置的。前面我们提到过,Django项目的代码目录结构是Project下划分多个APP,路由划分也是类似的,Project下有一个根路由配置,每个APP下有一个子路由配置,子路由配置由根路由配置集中引用。那么根路由配置又是如何被加载的呢?其实在settings.py中有配置项ROOT_URLCONF,这个属性指定根路由配置文件的位置。例如配置ROOT_URLCONF = 'mysite.urls'表示根路由配置文件在mysite模块下,文件名是urls.py

下面例子演示了Django中路由的配置结构,工程目录如下,其中mysite是根模块,其中包含项目配置文件、根路由配置文件等;demo01是一个APP,其中包含具体业务视图代码和子路由配置文件。

|_ mysite # 根模块
    |_ urls.py # 根路由配置文件
    |_ settings.py # 项目配置文件
|_ demo01
    |_ urls.py # 子路由配置文件
    |_ views.py # 视图函数

settings.py

ROOT_URLCONF = 'mysite.urls'

项目配置文件中指定了根路由配置文件路径。

mysite/urls.py

from django.urls import path, include

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

上面代码中,根模块中引入了子模块的路由配置。具体来说,path()函数用于配置Django路由,参数指定demo01子模块的路由具有demo01/前缀;此外Django的路由系统提供了include()函数实现了引入子路由配置这一目的,参数和写法和ROOT_URLCONF相同。

demo01/urls.py

from django.urls import path
from demo01.views import demo

urlpatterns = [
    path('demo/', demo),
]

子模块中,路由同样使用path()函数配置,其中指定demo函数作为视图函数(FBV),该路由的匹配路径为demo/

demo01/views.py

from django.shortcuts import render


def demo(request):
    return render(request, "success.html")

demo01/views.py中,我们定义了一个视图函数demo(),它简单的返回了一个HTML模板。

如上配置完成后,我们就可以启动项目了,测试服务器默认监听8000端口,我们使用浏览器访问http://127.0.0.1:8000/demo01/demo/并查看返回的页面。

精确匹配

Django的路由系统中,path()函数能够创建精确匹配路由,下面例子代码创建了精确匹配/demo/的路由配置。

from django.urls import path
from demo01.views import demo

urlpatterns = [
    path('demo/', demo, name='demo'),
]

path()函数的参数中,第1个参数是匹配路径,第2个参数是视图函数,第3个参数是路由名称(该参数是可选的)。路由名称通常用于反向解析,即根据name获取对应的URL,它应该设置的尽量具有描述性且简短和唯一。下面代码演示了这一用法,其中demo_url会输出/demo01/demo/

from django.shortcuts import render
from django.urls import reverse


def demo(request):
    demo_url = reverse('demo')
    print(demo_url)
    return render(request, "success.html")

正则匹配

下面例子中演示了Django中使用正则匹配方式配置路由,正则匹配需要使用re_path()函数。

from django.urls import re_path
from demo01.views import blog

urlpatterns = [
    re_path(r'^blogs/(?P<blog_id>[0-9]+)/$', blog, name="blog"),
]

上面代码中,re_path()函数的第1个参数是匹配路由的正则表达式,第2个参数是视图函数,第3个参数是路由名称(该参数是可选的)。正则表达式中的(?P<blog_id>[0-9]+)表示匹配一个或多个数字,并将其命名为blog_id,其中(?P<...>)是捕获组。在这种匹配模式下,我们需要访问形如/blogs/1/的URL,如果访问/blogs//blogs/a/是无法成功匹配的。

from django.shortcuts import render


def blog(request, blog_id):
    print('Blog ID ', type(blog_id), blog_id)
    return render(request, "success.html")

视图函数中,我们直接以blog_id为参数名接收匹配到的值即可,注意此处正则表达式匹配的blog_id是字符串类型。

转换器匹配

正则匹配实际上写起来比较复杂,Django还支持一种转换器匹配,它能替代大部分正则匹配路由配置,省去写正则表达式的麻烦。下面例子我们依然是匹配形如/blogs/1/的URL,但使用转换器匹配方式。

from django.urls import path
from demo01.views import blog

urlpatterns = [
    path('blogs/<int:blog_id>/', blog, name="blog"),
]

上述配置和前面正则匹配的路由完全相同,而且blog_id传入视图函数时还会自动转换为整数类型。除了<int: ... >转换器,Django还支持其他几种转换器,如下表所示。

转换器 描述
str 匹配除了路径分隔符/之外的非空字符串
int 匹配整数
slug 匹配包含字母、数字、下划线和破折号的字符串
uuid 匹配UUID字符串
path 匹配包含斜杠的任意字符串

实际开发中,推荐优先考虑精确匹配和转换器匹配,它们更简洁高效,这两种匹配模式无法实现再考虑使用正则匹配。

嵌套路由

嵌套路由主要用于根路由配置包含子路由配置的情况,前面我们已经使用过了。在大型项目中,子模块也可能进一步细分,这对于组织大型项目非常有用。

from django.urls import path, include

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

上面代码中,根模块中引入了demo01子模块的路由配置。

路由斜线结尾问题

前面例子中,我们最终访问demo()视图函数的URL是http://127.0.0.1:8000/demo01/demo/,可以看到它是以斜线/结尾的,这其实是Django的一种约定而非强制性的,不过使用Django框架时我们应尽量遵守约定。Django约定URL以斜线结尾有以下几个理由:

URL规范化:URL规范化是Django的核心设计哲学之一。技术上来说,foo.com/barfoo.com/bar/看似相同,但其实是两个不同的URL。为了避免搜索引擎和某些Web流量分析工具将它们视为不同的页面,Django约定通过统一添加斜线来规范化URL。

APPEND_SLASH:基于前一条原因,Django默认启用APPEND_SLASH设置。如果请求的URL不匹配任何路由且不以斜线结尾,Django会自动重定向到结尾带斜线的URL。也就是说,前面代码中如果访问/demo01/demo,Django也会自动重定向到/demo01/demo/。但如果路由配置是path('demo', demo),则不会发生任何自动的重定向行为,你必须且只能通过/demo01/demo访问这一路由。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap