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, 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. 最后

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

云计算存在的安全隐患

目录 一、概述 二、ENISA云安全漏洞分析 三、云计算相关系统漏洞 3.1 概述 3.2 漏洞分析 3.2.1 Hypervisor漏洞 3.2.1.1 CVE-2018-16882 3.2.1.2 CVE-2017-17563 3.2.1.3 CVE-2010-1225 3.2.2 虚拟机漏洞 3.2.2.1 CVE-2019-14835 3.2.2.2 CVE-2019-5514 3.2.2.3 CV…

css 属性值计算过程

1.css 属性值计算过程 某个元素从所有CSS属性没有值&#xff0c;到所有CSS属性都有值的过程1.确定声明值 2.层叠 3.继承 4.使用默认值 1.确定声明值 样式表总共有两类&#xff1a;作者样式表&#xff08;自己写的样式&#xff09;和浏览器样式表 html <h1 class"text&…

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

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

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之一 简单视频放大抖动效果

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之一 简单视频放大抖动效果 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之一 简单视频放大抖动效果 一、简单介绍 二、简单视频放大抖动效果实现原理 三、简单视频放大…

面试经典150题【131-140】

文章目录 面试经典150题【131-140】123.买卖股票的最佳时机III188.买卖股票的最佳时机IV二分查找的板子&#xff1a;35.搜索插入位置74.搜索二维矩阵162.寻找峰值33.搜索旋转排序数组34.在排序数组中查找元素的第一个和最后一个位置153.寻找旋转排序数组中的最小值4.寻找两个正…

练习 18 Web [RoarCTF 2019]Easy Calc

表达式注入&#xff0c;被屏蔽字符的处理方式 一开始先看一下前端的源码 有一个calc.php&#xff0c;肯定需要打开 这是calc中的代码 <?php error_reporting(0); if(!isset($_GET[num])){show_source(__FILE__); }else{$str $_GET[num];$blacklist [ , \t, \r, \n,\,…

计算机网络-HTTP相关知识-HTTP的发展

HTTP/1.1 特点&#xff1a; 简单&#xff1a;HTTP/1.1的报文格式包括头部和主体&#xff0c;头部信息是键值对的形式&#xff0c;使得其易于理解和使用。灵活和易于扩展&#xff1a;HTTP/1.1的请求方法、URL、状态码、头字段等都可以自定义和扩展&#xff0c;使得其具有很高的…

【Android、 kotlin】kotlin学习笔记

基本语法 fun main(){val a2var b "Hello"println("$ (a - 1} $b Kotlin!")} Variables 只赋值一次用val read-only variables with val 赋值多次用var mutable variables with var Standard output printin() and print() functions String templ…

蓝桥杯第793题——排水系统

题目描述 对于一个城市来说&#xff0c;排水系统是极其重要的一个部分。 有一天&#xff0c;小 C 拿到了某座城市排水系统的设计图。排水系统由 n 个排水结点&#xff08;它们从 1∼n 编号&#xff09;和若干个单向排水管道构成。每一个排水结点有若干个管道用于汇集其他排水…

分布式链路追踪与云原生可观测性

分布式链路追踪系统历史 Dapper, a Large-Scale Distributed Systems Tracing Infrastructure - Google Dapper&#xff0c;大规模分布式系统的跟踪系统大规模分布式系统的跟踪系统&#xff1a;Dapper设计给我们的启示 阿里巴巴鹰眼技术解密 - 周小帆京东云分布式链路追踪在金…

Node.js环境调用百度智能云(百度云)api鉴权认证三步走

方式一 :Postman脚本的方式生成v1版本的认证字符串 Postman脚本下载 下载Postman pre-request Script 设置 Authorization 示例脚本 方式二&#xff1a;在线签名工具生成 (试用于验证编程字符串签名是否有错误) 签名计算工具 https://cloud.baidu.com/signature/index.html …

Acrel-1000DP光伏监控系统在尚雷仕(湖北)健康科技有限公司5.98MW分布式光伏10KV并网系统的应用

摘 要&#xff1a;分布式光伏发电特指在用户场地附近建设&#xff0c;运行方式多为自发自用&#xff0c;余电上网&#xff0c;部分项目采用全额上网模式。分布式光伏全额上网的优点是可以充分利用分布式光伏发电系统的发电量&#xff0c;提高分布式光伏发电系统的利用率。发展分…

C++ //练习 11.3 编写你自己的单词计数程序。

C Primer&#xff08;第5版&#xff09; 练习 11.3 练习 11.3 编写你自己的单词计数程序。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /*************************************************************************> …

拾光坞N3 ARM 虚拟主机 i茅台项目

拾光坞N3 在Dcoker部署i茅台案例 OS&#xff1a;Ubuntu 22.04.1 LTS aarch64 cpu&#xff1a;RK3566 ram&#xff1a;2G 部署流程——》mysql——》java8——》redis——》nginx mysql # 依赖 apt update apt install -y net-tools apt install -y libaio* # 下载mysql wg…

JavaBean是什么?

Bean的本意为豌豆、子实&#xff0c;在这里引申为一种实体。JavaBean 是一种JAVA语言写成的可重用组件。为写成JavaBean&#xff0c;类必须是具体的和公共的&#xff0c;并且具有无参数的构造器。JavaBean 通过提供符合一致性设计模式的公共方法将内部域暴露成员属性&#xff0…

鸿蒙南向开发实战:【智能窗帘】

样例简介 智能窗帘设备不仅接收数字管家应用下发的指令来控制窗帘开启的时间&#xff0c;而且还可以加入到数字管家的日程管理中。通过日程可以设定窗帘开关的时间段&#xff0c;使其在特定的时间段内&#xff0c;窗帘自动打开或者关闭&#xff1b;通过日程管家还可以实现窗帘…

基础篇3 浅试Python爬虫爬取视频,m3u8标准的切片视频

浅试Python爬取视频 1.页面分析 使用虾米视频在线解析使用方式&#xff1a;https://jx.xmflv.cc/?url目标网站视频链接例如某艺的视频 原视频链接 解析结果: 1.1 F12查看页面结构 我们发现页面内容中什么都没有&#xff0c;video标签中的src路径也不是视频的数据。 1.2 …

VUE运行项目后,只有local地址,没有network地址(添加nerwork地址)

使用前 使用后 解决方案 1.找到build文件夹下的webpack.dev.conf.js文件&#xff0c;更改messages中的内容 devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({compilationSuccessInfo: {messages: [App running: ,Local: http://${devWebpackConfig.devServer.hos…

【前端Vue】社交信息头条项目完整笔记第3篇:三、个人中心,TabBar 处理【附代码文档】

社交媒体-信息头条项目完整开发笔记完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;一、项目初始化使用 Vue CLI 创建项目,加入 Git 版本管理,调整初始目录结构,导入图标素材,引入 Vant 组件库,移动端 REM 适配。二、登录注册准备,实现基本登录功能,登录状…

ObjectiveC-08-OOP面向对象程序设计-类的分离与组合

本节用一简短的文章来说下是ObjectiveC中的类。类其实是OOP中的一个概念&#xff0c;概念上简单来讲类是它是一组关系密切属性的集合&#xff0c;所谓的关系就是对现实事物的抽象。 上面提到的关系包括很多种&#xff0c;比如has a&#xff0c; is a&#xff0c;has some等&…