Django路由分发的三种方式以及命名空间namespce——附带源码解析

目录

1. 前言

2. include常规路由分发

3. include源码解析

4. 路由分发的第二种写法

5. 路由分发的第三种写法

6. 小结

7. 有关namespace

8. 最后


1. 前言

本篇文章主要是讲解路由分发的三种方式。当然,你可能在想,一般做路由分发只需要一个include就能搞定,为什么还有另外三种方式呢。

这个就要追溯到Django的底层源码了, 通过研究Django中include的底层源码了,从而发现另外两种作路由分发的方式。

2. include常规路由分发

在了解include方法之前,我们应该了解什么是路由分发

路由分发:传入的HTTP请求映射到相应的视图函数或处理程序的过程 。通过前缀匹配,将url分发到相应的app里面,在对相应的app里面的url路由进行再一次匹配。

通俗来讲,就是我们在app里面单独定义url,通过全局的前缀匹配之后,再去相应的app里面进行路由匹配

inlcude方法,常用于作路由分发 , 可以将剩下的路由交给app去匹配:

urls.py

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

app02.urls.py

from django.contrib import admin
from django.urls import path, re_path, includefrom app02 import viewsurlpatterns = [path('', views.test, name='index'),path('<int:post_id>/', views.test, name='detail'),
]

然后我们在进行匹配的时候,就需要先匹配前缀,再匹配后面的:

我们在访问具体url的时候,就需要先进行前缀blog匹配,然后再进入到app02里面进行匹配。

3. include源码解析

path('blog/', include('app02.urls')),

我们对当前这条代码进行解析 ,以下是inlcude的源码:

def include(arg, namespace=None):app_name = Noneif isinstance(arg, tuple):# Callable returning a namespace hint.try:urlconf_module, app_name = argexcept ValueError:if namespace:raise ImproperlyConfigured("Cannot override the namespace for a dynamic module that ""provides a namespace.")raise ImproperlyConfigured("Passing a %d-tuple to include() is not supported. Pass a ""2-tuple containing the list of patterns and app_name, and ""provide the namespace argument to include() instead." % len(arg))else:# No namespace hint - use manually provided namespace.urlconf_module = argif isinstance(urlconf_module, str):urlconf_module = import_module(urlconf_module)patterns = getattr(urlconf_module, "urlpatterns", urlconf_module)app_name = getattr(urlconf_module, "app_name", app_name)if namespace and not app_name:raise ImproperlyConfigured("Specifying a namespace in include() without providing an app_name ""is not supported. Set the app_name attribute in the included ""module, or pass a 2-tuple containing the list of patterns and ""app_name instead.",)namespace = namespace or app_name# Make sure the patterns can be iterated through (without this, some# testcases will break).if isinstance(patterns, (list, tuple)):for url_pattern in patterns:pattern = getattr(url_pattern, "pattern", None)if isinstance(pattern, LocalePrefixPattern):raise ImproperlyConfigured("Using i18n_patterns in an included URLconf is not allowed.")return (urlconf_module, app_name, namespace)

其中ags就是我们传递进来的路径字符串

这里,主要是判断是否是元组,我们传递进来的是字符串,所以可以先忽略这里

其中,最重要的就是patternsapp_name

这都是通过反射getattr从上面导入进来的模块里面拿的数据,也就是app02里面的urls里面的urlpatterns


这里,主要是跟命名空间相关的 :

定义namespace后,就需要定义好app_name

命名空间在后面聊

其实最后,总结下来,也就这些内容:

def include(arg = 'app02.urls', namespace=None):app_name = Noneurlconf_module = argif isinstance(urlconf_module, str):urlconf_module = import_module(urlconf_module)patterns = getattr(urlconf_module, "urlpatterns", urlconf_module)app_name = getattr(urlconf_module, "app_name", app_name)namespace = namespace or app_namereturn (urlconf_module, app_name, namespace)

返回的是一个元组:

  • urlconf_module:导入的模块(app02.urls)
  • app_name:app02下的app名字,与命名空间相挂钩
  • namespace:命名空间

4. 路由分发的第二种写法

所以,通过源码,我们得出了路由分发的第二种写法:

path('blog/', (importlib.import_module('app02.urls'), None, None))

其中第一个就是以上的通过动态导入的字符串路径,第二个和第三个都是与命名空间相关的

5. 路由分发的第三种写法

我们先看一下_path的源码:

def _path(route, view, kwargs=None, name=None, Pattern=None):from django.views import Viewif isinstance(view, (list, tuple)):# For include(...) processing.pattern = Pattern(route, is_endpoint=False)urlconf_module, app_name, namespace = viewreturn URLResolver(pattern,urlconf_module,kwargs,app_name=app_name,namespace=namespace,)elif callable(view):pattern = Pattern(route, name=name, is_endpoint=True)return URLPattern(pattern, view, kwargs, name)elif isinstance(view, View):view_cls_name = view.__class__.__name__raise TypeError(f"view must be a callable, pass {view_cls_name}.as_view(), not "f"{view_cls_name}().")else:raise TypeError("view must be a callable or a list/tuple in the case of include().")

很显然,我们第二个view参数传递的是一个元组,因此走的就是第一条路:

最后返回的就是一个URLResolver对象 , 常规的是返回一个URLPattern对象

 我们稍微做一下整理:

def _path('blog/', (importlib.import_module('app02.urls'), None, None) ):from django.views import Viewpattern = RoutePattern('blog/', is_endpoint=False)urlconf_module, app_name, namespace = importlib.import_module('app02.urls'), None, Nonereturn URLResolver(RoutePattern('blog/', is_endpoint=False),importlib.import_module('app02.urls'),None,app_name=None,namespace=None,)

实际上,最后就是一个URLResolver的对象。

现在,我们来看URLResolverreslove函数(如果不清楚为什么看这个的,我们看我以前的一篇文章,就是关于路由匹配源码的解析):

    def resolve(self, path):path = str(path)  # path may be a reverse_lazy objecttried = []match = self.pattern.match(path)if match:new_path, args, kwargs = matchfor pattern in self.url_patterns:try:sub_match = pattern.resolve(new_path)except Resolver404 as e:self._extend_tried(tried, pattern, e.args[0].get("tried"))else:if sub_match:# Merge captured arguments in match with submatchsub_match_dict = {**kwargs, **self.default_kwargs}# Update the sub_match_dict with the kwargs from the sub_match.sub_match_dict.update(sub_match.kwargs)# If there are *any* named groups, ignore all non-named groups.# Otherwise, pass all non-named arguments as positional# arguments.sub_match_args = sub_match.argsif not sub_match_dict:sub_match_args = args + sub_match.argscurrent_route = (""if isinstance(pattern, URLPattern)else str(pattern.pattern))self._extend_tried(tried, pattern, sub_match.tried)return ResolverMatch(sub_match.func,sub_match_args,sub_match_dict,sub_match.url_name,[self.app_name] + sub_match.app_names,[self.namespace] + sub_match.namespaces,self._join_route(current_route, sub_match.route),tried,captured_kwargs=sub_match.captured_kwargs,extra_kwargs={**self.default_kwargs,**sub_match.extra_kwargs,},)tried.append([pattern])raise Resolver404({"tried": tried, "path": new_path})raise Resolver404({"path": path})

我们看到这一句:

这就相当于遍历了urls里面的整个url_patterns

这里的self.url_patterns其实是一个方法,它被赋予@cached_property装饰器,代表不需要加上括号,就能执行:

@cached_property:这个函数与property()类似,但增加了缓存,可以直接通过方法名来访问方法,不需要在方法名后添加一对“()”小括号。

重点看以下两句,返回的其实就是app02.urls下面的url_patterns

由此,我们引出来第三种编写路由分发的方法:

# 最初
path('web/', ([path('v1/', www_views.login, name='v1'),path('v2/', www_views.login, name='v2'),], None, None)
)# 解析之后
URLResolver(RoutePattern('api/',name=None,is_endpoint=False),[path('v1/', www_views.login, name='v1'),path('v2/', www_views.login, name='v2'),], None,app_name=None,namespace=None
)

6. 小结

对于常规的path对象,它的底层是URLPattern类,但是对于路由分发,它的底层就是URLResolver对象了。

from django.urls import path, re_path, include
from apps.www import viewsfrom django.urls import URLPattern, ResolverMatch
from django.urls.resolvers import RoutePattern
from importlib import import_module
from apps.www import views as www_views
from django.urls.resolvers import URLResolverurlpatterns = [URLPattern(RoutePattern("login/", name=None, is_endpoint=True),views.login,None,None),URLResolver(RoutePattern('api/', name=None, is_endpoint=False),import_module("apps.base.urls"),  # 模块对象 from app.base import urlsNone,app_name=None,namespace=None),URLResolver(RoutePattern('web/', name=None, is_endpoint=False),[path('v1/', www_views.login, name='v1'),path('v2/', www_views.login, name='v2'),],None,app_name=None,namespace=None)
]

7. 有关namespace

平常做路由的时候,我们有一个参数为name , 主要就是为URL取的别名,在后续操作中更加方便引入该路径:

path('user/', views.User.as_view() , name='user')

但是在做路由分发的时候,涉及到一个命名空间,也就是涉及到相同的name,但是无法去做解析识别。

比如我现在有两个app:app01app02

以下是两个单独的urls

 

可以看到,两个里面都有name,并且name都是一样的。

如果,这个时候我要来做反向解析:通过name生成url:

可以看到,最后解析出来的是blog2 ,为什么是blog2呢, 这是因为相同的name,第二个把第一个给覆盖了。

这个时候,命名空间的作用就来了,可以很好的做分割:

  • 设置namespace
path('blog/', include('app02.urls', namespace='v1')),
path('blog2/', include('app01.urls', namespace='v2'))
  • 设置app_name ,使namespace能够找到对应的app

 

ok,设置好之后,我们再一次进行匹配:

这个时候,我们需要在解析之前加上一个命名空间:

 

ok,现在就能找到相应的URL啦

8. 最后

路由分发,常规用法是会比较简单的,但是要搞清楚源码的执行流程,还需要花费一定时间和精力,不懂的小伙伴,可以去看看我之前写过的路由定义路由匹配的源码分析,本篇文章只做了一些简单的介绍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/790508.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

尚硅谷2024最新Git企业实战教程 | Git与GitLab的企业实战

这篇博客是尚硅谷2024最新Git企业实战教程&#xff0c;全方位学习git与gitlab的完整笔记。 这不仅仅是一套Git的入门教程&#xff0c;更是全方位的极狐GitLab企业任务流开发实战&#xff01;作为一应俱全的一站式DevOps平台&#xff0c;极狐GitLab的高阶功能全面覆盖&#xff0…

2024-04-03 NO.4 Quest3 手势追踪抓取物体

文章目录 1 手势抓取方式1.1 Hand Grab1.2 Touch Hand Grab1.3 Distance Hand Grab 2 HandGrabExamples 示例场景2.1 Interactor 对象2.2 Interactable 对象2.2.1 父子结构2.2.2 “Hand Grab lnteractable” 脚本2.2.3 “Move Towards Target Provider” 脚本2.2.4 其他 Moveme…

5.5G,只比6G少0.5G

5.5G成为通信行业2024年开年的一大焦点。提到5.5G&#xff0c;多出来的0.5G又是啥&#xff1f;为什么不直接迈向6G时代&#xff1f;今天我们一探究竟&#xff01; “0.5G”&#xff0c;现在与未来的桥梁 2021年&#xff0c;国际标准组织3GPP为通信技术的进一步发展定义了新的里…

AI绘图:Stable Diffusion WEB UI 详细操作介绍:进阶-面部修复和调参

结合两篇文章完成了本地部署和基础操作,现在我们来介绍下进阶内容:面部修复,高清修复和调参区。 一:脸部修复 面部修复的适用在画真人、三次元的场景,特别是在画全身的时候 一般在画全身,由于脸部占比的空间比较小,那么绘制出来的效果就会比较差 1.面部修复 SD 支持…

利用sqoop实现sql表数据导入到Hadoop

1.在开发这创建好sql表后&#xff0c;开始执行下面步骤 2.sqoop的安装路径&#xff0c;我这里放在以下位置 3. 进入到option2脚本中&#xff0c;下面是脚本里的内容 下面四点要根据情况随时更改&#xff1a; 1>jdbc:mysql://node00:3306/数据库名 2>sid,sname->前…

BGP-(as-path-filter)

BGP-as-path-filter&#xff0c;缺省 as-path-filter&#xff0c;正则表达式&#xff0c;as-path过滤器&#xff0c;对于BGP的as-path属性实际上可以看成是一个包含空格的字符串。 特点&#xff1a;1、通过对BGP路由的as-path属性进行匹配达到对BGP路由的过滤。 2、在route-…

鸿蒙分布式音乐播放-如何完成播放、暂停、上一曲、下一曲功能

介绍 本示例使用fileIo获取指定音频文件&#xff0c;并通过AudioPlayer完成了音乐的播放完成了基本的音乐播放、暂停、上一曲、下一曲功能&#xff1b;并使用DeviceManager完成了分布式设备列表的显示和分布式能力完成了音乐播放状态的跨设备分享。 本示例用到了与用户进行交…

【VUE+ElementUI】el-table表格固定列el-table__fixed导致滚动条无法拖动

【VUEElementUI】el-table表格固定列el-table__fixed导致滚动条无法拖动 背景 当设置了几个固定列之后&#xff0c;表格无数据时&#xff0c;点击左侧滚动条却被遮挡&#xff0c;原因是el-table__fixed过高导致的 解决 在index.scss中直接加入以下代码即可 /* 设置默认高…

音频转换工具 Bigasoft FLAC Converter for Mac

Bigasoft FLAC Converter for Mac是一款专为Mac用户设计的音频转换工具&#xff0c;它能够将FLAC音频文件高效、高质量地转换为其他常见的音频格式&#xff0c;如MP3、AAC等。这款软件具有直观易用的界面&#xff0c;使用户能够轻松上手&#xff0c;无需复杂的操作步骤即可完成…

Redis底层数据结构-Dict

1. Dict基本结构 Redis的键与值的映射关系是通过Dict来实现的。 Dict是由三部分组成&#xff0c;分别是哈希表&#xff08;DictHashTable&#xff09;&#xff0c;哈希节点&#xff08;DictEntry&#xff09;&#xff0c;字典&#xff08;Dict&#xff09; 哈希表结构如下图所…

阿里云服务器购买租用价格多少钱一年?61元、99元、165元、199元

阿里云服务器租用价格表2024年最新&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元&#xff0c;ECS u1服务器2核4G5M固定带宽199元一年&#xff0c;2核4G4M带宽轻量服务器一年165元12个月&#xff0c;2核…

Delphi 是一种内存安全的语言吗?

上个月&#xff0c;美国政府发布了 "回到基石 "报告&#xff1a; 通往安全和可衡量软件之路 "的报告。该报告是美国网络安全战略的一部分&#xff0c;重点关注多个领域&#xff0c;包括内存安全漏洞和质量指标。 许多在线杂志都对这份报告进行了评论&#xff0…

C语言内存函数,让内存管理更高效!

1. memcpy使⽤和模拟实现 2. memmove使⽤和模拟实现 3. memset函数的使⽤ 4. memcmp函数的使⽤ 正文开始&#xff1a; 1. memcpy 使⽤和模拟实现 void * memcpy ( void * destination, const void * source, size_t num ); • 函数memcpy从source的位置开始向后复…

区间概率预测python|QR-CNN-BiLSTM+KDE分位数-卷积-双向长短期记忆神经网络-时间序列区间概率预测+核密度估计

区间预测python|QR-CNN-BiLSTMKDE分位数-卷积-双向长短期记忆神经网络-核密度估计-回归时间序列区间预测 模型输出展示&#xff1a; (图中是只设置了20次迭代的预测结果&#xff0c;宽度较宽&#xff0c;可自行修改迭代参数&#xff0c;获取更窄的预测区间&#xff09; 注&am…

类似微信的以文搜图功能实现

通过PaddleOCR识别图片中的文字&#xff0c;将识别结果报存到es中&#xff0c;利用es查询语句返回结果图片。 技术逻辑 PaddleOCR部署、es部署创建mapping将PaddleOCR识别结果保存至es通过查询&#xff0c;返回结果 前期准备 PaddleOCR、es部署请参考https://blog.csdn.net…

stm32之基本定时器的使用

在上文我们使用到了HAL库的自带的延时函数&#xff0c;HAL_Delay&#xff08;&#xff09;&#xff1b;我们来看一下函数的原型 __weak void HAL_Delay(uint32_t Delay) {uint32_t tickstart HAL_GetTick();uint32_t wait Delay;/* Add a freq to guarantee minimum wait */…

【SQL】1587. 银行账户概要 II

题目描述 leetcode题目&#xff1a;1587. 银行账户概要 II Code 写法一 select name, sum(amount) as balance from Users U left join Transactions T on U.account T.account group by U.account having sum(amount) > 10000写法二 select Users.name, balance from…

Unity自定义icon

Unity自定义icon 1. 新建文件夹 OfficeFabricIconSet2. 新建Iconset3. 新建子文件夹Textures并添加icon图片4. 向iconset添加Quad Icons5. 最终效果 教程来源处&#xff1a; https://365xr.blog/build-your-own-button-icon-set-for-microsoft-hololens-2-apps-with-mrtk-using…

前视声呐目标识别定位(三)-部署至机器人

前视声呐目标识别定位&#xff08;一&#xff09;-基础知识 前视声呐目标识别定位&#xff08;二&#xff09;-目标识别定位模块 开发了多波束前视声呐目标识别定位模块后&#xff0c;自然期待能将声呐部署至AUV&#xff0c;实现AUV对目标的抵近观测。原本规划着定位模块不…

C++算法——二分法查找

一、二分查找算法思想和模版 1.算法思想 2.细节处理 3.模板 二、二分查找 1.链接 704. 二分查找 - 力扣&#xff08;LeetCode&#xff09; 2.描述 3.思路 先从最经典的题目去切入&#xff0c;思路就是二分查找&#xff0c;这里我们认为&#xff0c;目标值既可以看作为左部…