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

news2024/11/20 6:27:56

目录

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, include

from app02 import views

urlpatterns = [
    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 = None
    if isinstance(arg, tuple):
        # Callable returning a namespace hint.
        try:
            urlconf_module, app_name = arg
        except 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 = arg

    if 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 = None
    urlconf_module = arg

    if 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_name
    return (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 View

    if isinstance(view, (list, tuple)):
        # For include(...) processing.
        pattern = Pattern(route, is_endpoint=False)
        urlconf_module, app_name, namespace = view
        return 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 View

    pattern = RoutePattern('blog/', is_endpoint=False)
    urlconf_module, app_name, namespace = importlib.import_module('app02.urls'), None, None
    return 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 object
        tried = []
        match = self.pattern.match(path)
        if match:
            new_path, args, kwargs = match
            for 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 submatch
                        sub_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.args
                        if not sub_match_dict:
                            sub_match_args = args + sub_match.args
                        current_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 views

from 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 URLResolver

urlpatterns = [
    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 urls
        None,
        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.coloradmin.cn/o/1566890.html

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

相关文章

Kimi精选提示词,总结PPT内容

大家好&#xff0c;我是子云&#xff0c;最近真是觉得Kimi这个大模型&#xff0c;产品体验很棒&#xff0c;能力也是不错&#xff0c;感觉产品经理用心了。 发现一个Kimi 一个小技巧&#xff0c;可以学习到很多高级提示词。 Kimi输入框可以配置常用提示词&#xff0c;同时也可…

内存和网卡压力测试

1.内存压力测试 1.1测试目的 内存压力测试的目的是评估开发板中的内存子系统性能和稳定性&#xff0c;以确保它能够满足特定的应用需求。开发板通常用于嵌入式系统、物联网设备、嵌入式智能家居等场景&#xff0c;这些场景对内存的要求通常比较高。 其内存压力测试的主要目的…

C++设计模式:TemplateMethod模式(一)

1、概念定义 定义一个操作中的算法的骨架结构&#xff08;稳定&#xff09;&#xff0c;而将一些步骤延迟&#xff08;变化&#xff09;到子类中。Template Method使得子类可以不改变&#xff08;复用&#xff09;一个算法的骨架结构即可重定义&#xff08;override重写&#x…

AMRT3D数字孪生引擎

产品概述 AMRT3D引擎是由眸瑞网络科技自主研发、拥有完全自主知识产权的一款全球首款轻量化3D图形引擎&#xff0c;引擎以核心的轻量化技术及AMRT轻量格式为支柱&#xff0c;专为数字孪生项目开发打造。 AMRT3D引擎提供一整套完善的数字孪生解决方案&#xff0c;在数据处理方…

基于ssm的轻型卡车零部件销售平台(java项目+文档+源码)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的轻型卡车零部件销售平台。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 轻型卡车零部件销售平台…

easyExcel 模版导出 中间数据纵向延伸,并且对指定列进行合并

想要达到的效果 引入maven引用 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.2.1</version></dependency> 按照要求创建模版 备注 : 模板注意 用{} 来表示你要用的变量 如果本…

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

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

RT-Thread下使用NTP服务器获取时间并同步到硬件RTC

单片机:STM32F407VET6 实现功能:通过ntp服务器获取时间并同步到硬件RTC上 1.配置NTP相关参数 1.1打开netutils相关软件包 1.2 关闭软件RTC相关配置 参考资料:RT-Thread中使用NTP自动更新时间_rtthread ntp-CSDN博客 2.配置硬件RTC 2.1 在ENV里面使能硬件RTC 2.2使用STM32C…

医疗器械5G智能制造工厂数字孪生可视化平台,推进行业数字化转型

医疗设备5G智能制造工厂数字孪生可视化平台&#xff0c;推进行业数字化转型。在数字化浪潮的推动下&#xff0c;医疗设备行业正迎来一场深刻的变革。5G技术的崛起&#xff0c;智能制造工厂的兴起&#xff0c;以及数字孪生可视化平台的出现&#xff0c;正在共同推动医疗设备行业…

【数据分析实战】印尼雅加达咖啡市场分析:品牌排名与市场趋势解读

目录 背景介绍数据展示数据分析可视化1. 各市咖啡店占比&#xff1a;1.1 可视化代码1.2 可视化结果1.3 浅薄解读 2. 品牌市场份额排名&#xff1a;2.1 可视化结果1.2 浅薄解读 3. 品牌消费者满意指数&#xff1a;3.1 可视化代码3.2 可视化结果3.3 浅薄解读 写在最后 背景介绍 …

数据结构——二叉树(堆)

大家好我是小峰&#xff0c;今天我们开始学习二叉树。 首先我们来学习什么是树&#xff1f; 树概念及结构 树是一种 非线性 的数据结构&#xff0c;它是由 n &#xff08; n>0 &#xff09;个有限结点组成一个具有层次关系的集合。 把它叫做树是因 为它看起来像一棵倒挂的…

canal部署

定义 canal组件是一个基于mysql数据库增量日志解析&#xff0c;提供增量数据订阅和消费&#xff0c;支持将增量数据投递到下游消费者&#xff08;kafka&#xff0c;rocketmq等&#xff09;或者存储&#xff08;elasticearch,hbase等&#xff09;canal感知到mysql数据变动&…

AI学习-线性回归推导

线性回归 1.简单线性回归2.多元线性回归3.相关概念熟悉4.损失函数推导5.MSE损失函数 1.简单线性回归 ​ 线性回归&#xff1a;有监督机器学习下一种算法思想。用于预测一个或多个连续型目标变量y与数值型自变量x之间的关系,自变量x可以是连续、离散&#xff0c;但是目标变量y必…

IpcRenderer.invoke Error: An object could not be cloned.

这个错误信息提示“Uncaught (in promise) Error: An object could not be cloned.”通常发生在使用 Electron 的 IPC 通信过程中&#xff0c;尝试通过 ipcRenderer.invoke 或 ipcMain.handle 发送不能被克隆的对象时。JavaScript 中一些特殊对象或包含循环引用的对象无法通过 …

SQL server 查询数据库中所有的表名及行数

SQL server 查询数据库中所有的表名及行数 select a.name,b.rows from sysobjects as ainner join sysindexes as bon a.id b.id where (a.type u)and (b.indid in (0, 1)) and b.rows<50 and b.rows>20 order by a.name, b.rows desc;

虚拟机 ubuntu 20.04 git 设置代理的方法

前言 ubuntu 20.04 虚拟机中 Git 访问 github 或者其他的 git 仓库&#xff0c;大部分情况下速度很慢&#xff0c;并且容易掉线 如果 主机上使用了代理软件&#xff0c;但是虚拟机 ubuntu 中 Git 访问 git 仓库依旧是很慢 【问题】如何设置 虚拟机 ubuntu 的 Git 代理&#x…

C# 批量删除Excel重复项

当从不同来源导入Excel数据时&#xff0c;可能存在重复的记录。为了确保数据的准确性&#xff0c;通常需要删除这些重复的行。 手动查找并删除可能会非常耗费时间&#xff0c;而通过编程脚本则可以实现在短时间内处理大量数据。本文将提供一个使用C# 快速查找并删除Excel重复项…

Beaver Builder Pro v2.8.0.6:最佳的WordPress页面构建器插件

如果你正在寻找一个能帮助你轻松创建具有专业外观的网站的工具&#xff0c;那么Beaver Builder Pro v2.8.0.6就是你的最佳选择。这个高级WordPress插件提供了一个直观的前端可视化页面构建器&#xff0c;让你可以通过拖放元素来快速构建无限的自定义帖子和页面。 Beaver Buil…

JAVAEE之IoCDI

Spring 是⼀个 IoC&#xff08;控制反转&#xff09;容器&#xff0c;作为容器, 那么它就具备两个最基础的功能&#xff1a; • 存 • 取 Spring 容器管理的主要是对象, 这些对象, 我们称之为"Bean". 我们把这些对象交由Spring管理, 由 Spring来负责对象的创建…

Spring Boot--文件上传和下载

文件上传和下载 前言文件上传1、以MultipartFile 接口流文件&#xff0c;流的名称需要和前台传过来的名称对应上2、获取到文件名称截取后缀3、为了放置文件名重复使用uuid来随机生成id后缀4、判断转存路径中是否有这个文件夹如果没有就创建5、将文件存储到转存的目录中 文件下载…