首页 > 其他分享 >Django 路由层 —— 基础用法,以及底层如何实现的

Django 路由层 —— 基础用法,以及底层如何实现的

时间:2024-09-22 23:22:46浏览次数:12  
标签:None name self request 用法 path login Django 路由

路由层

  • 负责网页 URL函数/类 的对应关系

用法

基本用法

urls.py:

urlpatterns = [
    # 基本用法
    path('login/', views.login),

    # 动态输入参数
    path('info/<int:v1>', views.info),

    # 可以同时传多个参数, path:v3 会记录 v1-v2/ 后面的所有路径
    path('other/<int:v1>-<str:v2>/<path:v3>', views.other),

    # 通过 re_path 应用正则表达式,通过 () 对参数进行分组。
    # r 的作用:告诉编译器需要进行转义
    # re_path 由 django2 引入,在此之前使用的是 url 进行正则
    # 输入 date/2014-02-11  ->  输出 ('2014', '02-11')
    re_path(r'date/(\d{4})-(\d{2}-\d{2})', views.date),
]

views.py:

def login(request):
    return HttpResponse("login")


def info(request, v1):  # 需要设置形参来接受
    return HttpResponse(f"{v1}")


def other(request, v1, v2, v3):
    return HttpResponse(f"{v1, v2, v3}")


def date(request, r1, r2):
    # 输出 ('2014', '02-11')
    return HttpResponse(f"{r1, r2}")

别名

  • 方便后续使用别名反向生产 URL ,以及使用 redirect 重定向
  • 有时还用于分配权限
path('login/', views.login, name="login"),
path('info/<int:v1>', view.info, name="info"),
print(reverse("login"))  # '/login/'
print(reverse("info", kwargs={"v1": 123}))  # '/info/123/'

路由分发

  • 项目较大 -> 一个 urls.py 内容太多 -> 拆分成多个 urls.py

urls.py:

urlpatterns = [
    # 根路由
    path('login/', views.login),  # /login/

    # 使用 include 进行路由分发
    path('another/', include("app.another_urls"))
]

app.another_urls:

urlpatterns = [
    path('login/', views.login)  # /another/login/
]
  • 另一种写法:

urls.py:

urlpatterns = [
    path('login/', views.login),

    # 元组里面放 urlpatterns 列表
    path('another/', ([
        path('login/', views.login)  # /another/login/
    ], None, None))
]

namespace

  • include 函数
def include(arg, namespace=None):
    app_name = None
    if isinstance(arg, tuple):
        try:
            urlconf_module, app_name = arg
        except ValueError:
            # ......
    else:
        # No namespace
        urlconf_module = arg
        
	if isinstance(urlconf_module, str):
        urlconf_module = import_module(urlconf_module)
        
    # ......
    
    # 只提供 namespace 不提供 app_name 会报错
    if namespace and not app_name:
        raise # ......
        
    # ......
    return (urlconf_module, app_name, namespace)

使用 namespace:

# urls.py:
path('another/', include(("app.another_urls", "app"), "ns"))

# another_urls.py:
path('ns/', views.ns, name="n")

# views:
def login(request):
    # print(reverse("n"))  # 在有 namespace 的情况下会报错
    print(reverse("ns:n"))  # namespace 的作用:防止多路由状态下的别名冲突
    return HttpResponse("login")

源码

路由的本质

path('login/', views.login),

#   ↓   path = partial(_path, Pattern=RoutePattern)
#   ↓   partial 是 python 自带的方法,称为偏函数。此处,用于将 Pattern=RoutePattern 参数传递到函数 _path 中
#   ↓   在函数 _path 中会创建一个 URLPattern 对象:URLPattern(pattern, view, kwargs, name)
#   ↓   pattern = Pattern(route, name=name, is_endpoint=True)

URLPattern(RoutePattern('login/', name=None, is_endpoint=True), views.login, None, None)


path('another/', include("app.another_urls"))
#   ↓   
URLResolver(RoutePattern('another/', name=None, is_endpoint=False), "app.another_urls", None, None, None)
  • _path 创建 URLPattern 或 URLResolver 对象部分如下:
def _path(route, view, kwargs=None, name=None, Pattern=None):
    # path('another/', include("app.another_urls"))
    if isinstance(view, (list, tuple)):
        pattern = Pattern(route, is_endpoint=False)
        urlconf_module, app_name, namespace = view
        return URLResolver(
            pattern,
            urlconf_module,
            kwargs,
            app_name=app_name,
            namespace=namespace,
        )
        
    # path('login/', views.login)
    elif callable(view):  # callable 用于判定函数是否可执行。简单来说就是 能不能在后面加括号。如:views.login()
        pattern = Pattern(route, name=name, is_endpoint=True)
        return URLPattern(pattern, view, kwargs, name)
  • 关于 URLPattern 和 RoutePattern
# 传入:
URLPattern(RoutePattern('login/', name=None, is_endpoint=True), views.login, None, None)


# URLPattern 
class URLPattern:
    def __init__(self, pattern, callback, default_args=None, name=None):
        self.pattern = pattern  # RoutePattern('login/', name=None, is_endpoint=True)
        self.callback = callback  # views.login
        self.default_args = default_args or {}  # None
        self.name = name  # None
        
# urls.py:
# print("callback: ", urlpatterns[0].callback)  ->  callback:  <function login at 0x0000023A5DBDBA30>


# RoutePattern
class RoutePattern(CheckURLMixin):
    regex = LocaleRegexRouteDescriptor()

    def __init__(self, route, name=None, is_endpoint=False):
        self._route = route  # 'login/', 
        
        # _route_to_regex 的作用:
        # 将路径模式转换为正则表达式。返回正则表达式和一个将捕获名称映射到转换器的字典。
        # 例如,“foo/<int:pk>”将返回“^foo\\/(?P<pk>[0-9]+)” 和 {'pk': <django.urls.converters.IntConverter>}
        self._regex, self.converters = _route_to_regex(str(route), is_endpoint)
        
        self._regex_dict = {}
        self._is_endpoint = is_endpoint  # is_endpoint=True
        self.name = name  # name=None
        
        
# URLPattern 中嵌入 RoutePattern 的一种用法:
class URLPattern:
    def resolve(self, path):
        # ......
        self.pattern.match  # -> 此处就可以调用 RoutePattern 中的 match 方法
  • 关于 is_endpoint的作用

is_endpoint=True 的作用是标识该路由是一个终点(endpoint)。具体来说:

  1. 路由终点:当 is_endpoint=True 时,Django 知道这个路由条目代表的是一个终点视图函数(比如 views.login),而不是一个子路由的中间层。这表示如果请求路径与该路由匹配,那么这个路径应该由 views.login 这个视图来处理。
  2. 区分子路由和终点:在 Django 路由中,有时会使用 include() 来包含其他的路由配置。对于这些包含的路由,is_endpoint 会被设置为 False,因为它们并不代表一个具体的视图,而是指向另一个路由配置文件。

例如:

urlpatterns = [
    path('login/', views.login),  # 这是一个终点,is_endpoint=True
    path('another/', include("app.another_urls")),  # 这不是一个终点,is_endpoint=False
]

路由匹配的流程

  1. 启动程序,用户访问 URL
  2. 程序执行到 wsgi.py
# Django 项目的 WSGI 配置。
# 它将 WSGI 可调用程序公开为一个名为 ``application`` 的模块级变量。

application = get_wsgi_application()
  1. get_wsgi_application 返回 WSGIHandler对象
def get_wsgi_application():
    """
    Django WSGI 支持的公共接口。返回一个 WSGI 可调用程序。

	避免将 django.core.handlers.WSGIHandler 作为公共 API,以防将来内部 WSGI 实现发生变化或移动。
    """
    django.setup(set_prefix=False)
    return WSGIHandler()
  1. WSGIHandler会调用 __call__
class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.load_middleware()

    def __call__(self, environ, start_response):
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        
        # environ 是用户浏览器发送请求时,携带的所有请求相关的数据
        # request = self.request_class(environ)  =>  request = WSGIRequest(environ)
        # environ 内部的数据杂乱无章,使用 WSGIRequest 进行封装,方便使用
        # request.method
        # request.POST
        # request.path_info
        request = self.request_class(environ)
        
        # 路由的匹配 request.path_info  /login/  ->  读取项目中 urlpatterns 所有的路由
        # 找到相应的函数  views.login
        # 执行函数并返回值  HttpResponse("login")
        response = self.get_response(request)

        # ......
        # 此处完善了 response 的响应报文
        
        return response
  1. response = self.get_response(request)进行路由匹配
def get_response(self, request):
    """根据给出的 HttpRequest 返回一个 HttpResponse 对象."""
    set_urlconf(settings.ROOT_URLCONF)
    
    # self._middleware_chain
    # ->  self._middleware_chain = handler
    # ->  handler = self.adapt_method_mode(is_async, handler, handler_is_async)  # 判断是同步还是异步(目前是同步)
    # ->  handler = convert_exception_to_response(get_response)  # convert_exception_to_response 中间件,负责异常响应
    # ->  get_response = self._get_response_async if is_async else self._get_response  # 判断是同步还是异步(目前是同步)
    # ->  self._get_response:解析并调用视图,然后应用视图、异常和模板响应中间件。该方法就是请求/响应中间件内部发生的一切。
    # 综上:self._middleware_chain(request)  =>  self._get_response(request)
    response = self._middleware_chain(request)
    
    # ......
    return response
  1. self._get_response(request)进行路由匹配
def _get_response(self, request):
    """
    解析并调用视图,然后应用视图、异常和模板响应中间件。该方法就是请求/响应中间件内部发生的一切。
    """
    response = None
    
    # self.resolve_request: 返回已解析的视图及其参数 args 和 kwargs。
    # callback:视图函数
    # callback_args, callback_kwargs:参数,例如 info/<int:v1>/ 里面的 v1
    callback, callback_args, callback_kwargs = self.resolve_request(request)

    # Apply view middleware
    for middleware_method in self._view_middleware:
        # ......

    if response is None:
        # ......

    # Complain if the view returned None (a common error).
    self.check_response(response, callback)

    # If the response supports deferred rendering, apply template
    # 如果响应支持延迟渲染,则应用模板
    # response middleware and then render the response
    # 执行响应中间件并渲染响应
    if hasattr(response, "render") and callable(response.render):
        # ......

    return response
  1. self.resolve_request(request) 返回 ResolverMatch() 对象
def _get_cached_resolver(urlconf=None):
    return URLResolver(RegexPattern(r"^/"), urlconf)  # RegexPattern(r"^/"): 正则匹配,匹配以 '/' 开头的字符串


def get_resolver(urlconf=None):
    if urlconf is None:
        urlconf = settings.ROOT_URLCONF  # ROOT_URLCONF = 'Django.urls'  ->  此处的 Django 是项目名称,'Django.urls' 指向 urls.py
    return _get_cached_resolver(urlconf)


# 注意,此处的 resolve 属于 URLResolver 类,后续会有递归的情况
def resolve(self, path):
    # path = '/login/'
    # path('another/', include("app.another_urls"))
    path = str(path)
    tried = []
    
    # 判断目前访问的 URL 是否与创建的 URLPattern 记录的 URL 相同
    # match = ('login/', (), {})
    # match = ('another/ns/', (), {})
    match = self.pattern.match(path)
    
    if match:
        # new_path = 'login/'
        # new_path = 'another/ns/'
        new_path, args, kwargs = match
        
        
        # self.url_patterns
        # 使用 getattr 反射
        # ->  getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
        # =>  self.urlconf_module.urlpatterns(如果没有 urlpatterns 就返回 self.urlconf_module)
        
        # self.urlconf_name 在上面 URLResolver(RegexPattern(r"^/"), urlconf) 中获得,值为 urlconf 也就是 'Django.urls'
        # ->  import_module(self.urlconf_name)
        # =>  from Django import urls
        
        # 故上方的 self.urlconf_module.urlpatterns 即为 urls.urlpatterns, 也就是我们所有的定义的路由
        # 开始循环校验
        for pattern in self.url_patterns:
            try:
                
                # pattern.resolve(new_path)
                # 如果是 new_path = 'login/' 的情况:
                    # => URLPattern(RoutePattern('login/', ...), views.login).resolve('login/')
                    # -> match = self.pattern.match(path) => RoutePattern.match('login/')
                    # -> 进行匹配,判断目前访问的 URL 是否与创建的 URLPattern 记录的 URL 相同
                    # 匹配成功,就返回 path[match.end() :], (), kwargs
                    # match = ('login/', (), {})
                    # 再进一步进行封装
                    # 返回 ResolverMatch 对象
                    # ResolverMatch(func=app.views.login, args=(), kwargs={}, url_name=None, app_names=[],namespaces=[], route='login/')
                
                # 如果是 new_path = 'another/ns/' 的情况:
                	# => URLResolver(RoutePattern('another/', ...), "app.another_urls", ...).resolve('another/ns/')
                    # 此时就会递归当前的 resolve 函数
                    # match = self.pattern.match(path) 匹配时,会从 'another/ns/' 匹配出 new_path = 'ns/',此时为 URLPattern ,同上
                sub_match = pattern.resolve(new_path)
                
            except Resolver404 as e:
                # ......
            else:
                # 匹配成功
                if sub_match:
                    # ......
                    # 对一些参数进行处理
                    # 返回 ResolverMatch 对象
                   # ResolverMatch(func=app.views.login, args=(), kwargs={}, url_name=None, app_names=[], namespaces=[], route='login/')
                    return ResolverMatch(
                        # ......
                    )


def resolve_request(self, request):
    if hasattr(request, "urlconf"):  # 判断是否有 urlconf ,一般默认没有
        # ......
    else:
        resolver = get_resolver()  # resolver = URLResolver(RegexPattern(r"^/"), 'Django.urls')
        
    # 解析视图,并将匹配对象重新分配给请求。
    # resolver.resolve(request.path_info)  ->  resolver.resolve('/login/')
    # resolver_match = ResolverMatch(func=app.views.login, ......, route='login/')
    resolver_match = resolver.resolve(request.path_info)
    
    # 赋值给 request ,request.resolver_match = ResolverMatch(func=app.views.login, ......, route='login/')
    request.resolver_match = resolver_match
    return resolver_match
  • WSGI

WSGI(Python Web Server Gateway Interface,即Web服务器网关接口) 是 Python 定义的 Web 服服务器和 Web 应用程序或框架之间的一种简单而通用的接口。

它是 Python 为了解决 Web 服务器端与客户端之间的通信问题而产生的,它基于现存的 CGI 标准而设计。其定义了 Web 服务器如何与 Python 应用程序进行交互,让 Python 写的 Web 应用程序可以和 Web 服务器对接起来。

  • __call__:

使一个类可以像函数一样调用。

使用时,x() 等价于 x.__call__()

class A(object):
    def __init__(self, name, age, male):
        self.name = name
        self.age = age
        self.male = male
 
    def __call__(self, name, age):
        self.name, self.age = name, age
 
 
if __name__ == '__main__':
    a = A('dgw', 25, 'man')
    print(a.age, a.name)  # 25 dgw
    a('zhangsan', 52)
    print(a.name, a.age)  # zhangsan 52
  • 7 到 1:

self.resolve_request(request) 返回 resolver_match -> ResolverMatch() 对象ResolverMatch(func=app.views.login, args=(), kwargs={}, url_name=None, app_names=[], namespaces=[], route='login/')

self._get_response(request) 返回 response -> <HttpResponse status_code=200, "text/html; charset=utf-8">

然后一直往上,返回响应报文

源码的应用(用的不多)

自定义拓展:

Class MYURLPattern(URLPattern):
    def ... :
        pass

MYURLPattern(RoutePattern('login/', name=None, is_endpoint=True), views.login, None, None)

补充:关于网址后面的"/"

  • Django 的默认设置 地址
...\envs\Lib\site-packages\django\conf\global_settings.py
  • Django 的默认设置中,有一个 APPEND_SLASH = True,他的功能是:
# 在访问 "/login" 时,自动重定向到 "/login/"
path('login/', views.login)

注意: 重定向后,访问的方法是 GET 方法。如果使用 POST 访问 “/login”,重定向后会出问题。

  • 可以在 setting.py 中,添加 APPEND_SLASH = False

标签:None,name,self,request,用法,path,login,Django,路由
From: https://blog.csdn.net/2302_76314178/article/details/142389987

相关文章

  • vue如何挂载路由
            在Vue中,挂载路由并不是指一个直接的API调用或方法,而是一个过程,它涉及到将VueRouter实例与Vue应用的根实例进行关联。这个过程通常是在Vue应用的入口文件中完成的,比如main.js或app.js。1、安装VueRouter:        使用npm或yarn将VueRouter添加到你......
  • 交换机+防火墙+路由器
    1、网络拓扑图2、基础网络配置1)路由nat2)防火墙策略3)交换机vlan3、详细配置路由器:<r2>displaycurrent-configuration [V200R003C00]#sysnamer2#snmp-agentlocal-engineid800007DB03000000000000snmp-agent #clocktimezoneChina-Standard-Timeminus08:00:00#......
  • 基于django+vue基于B_S的驾校在线学习考试系统【开题报告+程序+论文】-计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着信息技术的飞速发展和互联网的普及,传统教育模式正经历着深刻的变革。驾校培训作为机动车驾驶员获取驾驶资格的重要环节,其教学模式亦需......
  • 基于django+vue基于b_s的婚恋平台管理系统【开题报告+程序+论文】-计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着互联网技术的飞速发展,人们的社交方式发生了深刻变革,线上婚恋交友平台作为新兴社交形式,逐渐成为现代人寻找伴侣的重要途径。然而,面对日......
  • 路由器的工作原理及基本设置详解
    路由器是现代网络架构中的关键设备,负责在不同网络之间转发数据包。理解路由器的工作原理及其基本设置,对于构建和维护高效、安全的网络环境至关重要。本文将详细讲解路由器的基本原理和常见设置步骤。一、路由器的工作原理1.数据包转发路由器的核心功能是转发数据包。当数据......
  • struct的精确用法
    目录我终于回来啦!1,可以创造根据结构体格式的成员或数组。普通成员 数组成员2,可以用指针遍历成员3,使用typedef---------------------------------------------------------------------------------------------------------------------------------今天我整理了一......
  • 关于`django-auth-ldap` 和 `ldap3`如何处理与 LDAP 服务器的连接和身份验证的问题
    在使用Django进行ActiveDirectory(AD)身份验证时,django-auth-ldap和ldap3都涉及到与LDAP服务器的交互。一个关键的差异在于如何处理与LDAP服务器的连接和身份验证。具体来说,django-auth-ldap通常使用一个绑定用户(BindDN)来执行搜索和验证操作,而使用ldap3时,你......
  • 基于python+django的电影推荐系统设计与实现大数据分析系统
    前言......
  • 计算机汽车销售数据分析—django基于python的汽车销量数据分析
    标题:django基于python的汽车销量数据分析设计并实现一个基于Python的Django汽车销量数据分析系统,旨在为企业提供深入的数据洞察,以支持市场策略制定与产品优化。系统的主要功能模块:1.数据导入与清洗•多源数据集成:支持从Excel、CSV、数据库等多种来源导入汽车销售数据。•......
  • 计算机美食推荐系统—django基于python的美食推荐系统
    标题:django基于python的美食推荐系统设计并实现一个基于Python的Django美食推荐系统,旨在为用户提供个性化且多样化的美食探索体验。系统的核心功能模块:1.用户与美食信息管理•用户注册与资料维护:收集用户基本信息、饮食偏好(如口味、饮食限制、喜爱的菜系)等。•美食数据库......