乐趣区

关于python:Django-url-路由匹配过程

1 Django 如何解决一个申请

当一个用户申请 Django 站点的一个页面,上面是 Django 零碎决定执行哪个 Python 代码应用的算法:

  1. Django 确定应用根 URLconf 模块。通常,这是 ROOT_URLCONF 设置的值(即 settings 中的 ROOT_URLCONF),但如果传入 HttpRequest 对象领有 urlconf 属性(通过中间件设置),它的值将被用来代替 ROOT_URLCONF 设置。能够在 django/core/handlers/base.py 发现该逻辑。

    class BaseHandler:
        ...
        def _get_response(self, request):
            ...
            if hasattr(request, 'urlconf'):
                urlconf = request.urlconf
                set_urlconf(urlconf)
                resolver = get_resolver(urlconf)
            else:
                resolver = get_resolver()
  2. Django 加载该 Python 模块并寻找可用的 urlpatterns。它是 django.urls.path() 和(或) django.urls.re_path() 实例的序列(sequence)。其实就是咱们写的 url.py
  3. Django 会按程序遍历每个 URL 模式,而后会在所申请的 URL 匹配到第一个模式后进行,并与 path_info 匹配。这个是路由匹配的要害,相干逻辑均在 django/urls/resolvers.py。其中有几个比拟重要的概念,如RegexPatternRoutePatternURLPatternURLResolver。其中URLResolver 有嵌套的逻辑,下文详述。
  4. 一旦有 URL 匹配胜利,Django 导入并调用相干的视图,这个视图是一个 Python 函数(或基于类的视图 class-based view)。匹配胜利会返回一个 ResolverMatch 对象。
  5. 如果没有 URL 被匹配,或者匹配过程中呈现了异样,Django 会调用一个适当的错误处理视图。

本文详述 2、3,即 urlpatterns 相干概念和路由匹配的过程。

2 URL 配置文件

在 Django 2 之后通常会应用 path/re_path 来设置路由,还要一个比拟非凡的办法 include。

  • path: 用于一般门路
  • re_path:用于正则门路
  • include:将一个子 url 配置文件导入

如下示例:

urlpatterns = [path('index/', views.index), # 一般门路
    re_path(r'^articles/([0-9]{4})/$', views.articles), # 正则门路
    path("app01/", include("app01.urls")),
]

下面的配置文件,设置了 3 条 urlpattern,别离是一般门路 index/ 与 视图函数 views.index,正则门路 ^articles/([0-9]{4})/$ 与视图函数 views.articles 绑定。app01/app01.urls 绑定,app01.urls 不是一个视图函数,而是一个子模块的 urlpatterns。
能够看到 urlpattern 能够把一个 url 和视图函数绑定,也能够和一个子 urlpattern 进行绑定。

2.1 path、re_path

设置路由的几个函数均定义在 django/urls/conf.py 中。

def include(arg, namespace=None):
    ...
    return (urlconf_module, app_name, namespace)


def _path(route, view, kwargs=None, name=None, Pattern=None):
    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)
    else:
        raise TypeError('view must be a callable or a list/tuple in the case of include().')


path = partial(_path, Pattern=RoutePattern)
re_path = partial(_path, Pattern=RegexPattern)

首先先来看下 path 和 re_path,这两个函数别离被 functools 上面的 partial 封装了一下。partial 的作用简略来说就是将一个函数的某些参数给固定住,返回一个新的函数。具体文档能够查看 partial 文档。
这样就不难理解 path 和 re_path,他们就是就是绑定了不同的 Pattern 参数的 _path 函数。进一步查看 _path 外部的逻辑,

  • 第一个分支 如果绑定的是一个 list 或者 tuple,应用 URLResolver 去解析,其实此时就是应用了 include 来定义 urlpattern。
  • 另外一种状况如果绑定的 view 是能够调用的,那就应用 URLPattern 去解析。URLPattern 中的 pattern 参数就是依据是采纳 path/re_path 办法别离对应 RoutePattern/RegexPattern。

2.2 include

def include(arg, namespace=None):
    ...
    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)
    ...
    return (urlconf_module, app_name, namespace)

include 办法所做的工作就是通过 import_module 将定义的 url 模块导入。返回一个由子 urlconf 模块、app_name、命名空间 namespace 组成的元组。回到刚刚下面的 _path 中第一个分支。将这个元组外面参数代入 URLResolver 并返回。

3 URLPattern 与 URLResolver

3.1 URLPattern

下面提到如果 url 定义中绑定是一个能够间接调用的 view。那就是应用 URLPattern 间接去解析。

class URLPattern:
    def __init__(self, pattern, callback, default_args=None, name=None):
        # 须要匹配的 urlpattern,这里依据是 path 还是 re_path 别离是 RoutePattern 或 RegexPattern 的实例
        self.pattern = pattern
        self.callback = callback  # the view
        self.default_args = default_args or {}
        self.name = name
    ...
    def resolve(self, path):
        调用 RoutePattern 或 RegexPattern 的实例中的 match 办法进行匹配(留神这里不是 re 模块外面的 match)match = self.pattern.match(path)
        if match:
            new_path, args, kwargs = match
            # Pass any extra_kwargs as **kwargs.
            kwargs.update(self.default_args)
            # 匹配胜利返回 `ResolverMatch`
            return ResolverMatch(self.callback, args, kwargs, self.pattern.name, route=str(self.pattern))
    ...

URLPattern 初始化时其中的 pattern 就是依据是应用 path/re_path 别离对应 RoutePattern 或 RegexPattern。其实就是指定匹配的模式是一般路由还是正则的路由。

3.2 URLResolver

URLResolver 源码中比拟外围的是 resolve 函数,就是传入一个 path,进行匹配。

class URLResolver:
    def resolve(self, path):
        path = str(path)  # path may be a reverse_lazy object
        tried = []
        # 匹配 path
        match = self.pattern.match(path)
        if match:
            new_path, args, kwargs = match
            # 如果匹配胜利,则持续匹配它的 url_patterns
            for pattern in self.url_patterns:
                try:
                    # 这个 pattern 可能是 URLPattern,也可能是 URLResolver;如果是 URLPattern,匹配胜利则返回 ResolverMatch;如果是 URLResolver,则会递归调用上来。sub_match = pattern.resolve(new_path)
                ...
                else:
                    if sub_match:
                        ...
                        # 匹配胜利返回 ResolverMatch
                        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.append([pattern])
            raise Resolver404({'tried': tried, 'path': new_path})
        raise Resolver404({'path': path})

URLResolver 比拟要害的逻辑在 循环匹配 pattern 过程,如果 pattern 是 URLPattern 匹配胜利间接返回 ResolverMatch,如果是另一个 URLResolver,则实现了递归调用。

Django 就是通过这个 URLResolver 实现了多级 URL 配置。

4 总结

Django 路由匹配的有几个比拟外围的概念 path/re_path/include、RegexPattern/RoutePattern、URLPattern/URLResolver。
首先用 partial 封装 _path,绑定了一个 pattern 匹配模式(RegexPattern/RoutePattern),前面屡次用到了这个 pattern。而后就是依据 view 是元组还是可调用视图函数,别离应用 URLResolver 和 URLPattern 去解析,这两个类解析之后都会返回给 ResolverMatch,由它去回调匹配胜利后的后果(view 和 args 等)。
本文从全局的角度大抵阐明了 Django 路由的匹配流程,后续将从细节局部阐明其中的一些关键点。

退出移动版