Django 路由系统

1.常见路由操作

在这里插入图片描述

2. name 别名

别名主要是用于方便后续使用特别长的路由,不写全全部路由,而起了一个简短的名字
例如:一个api路径为api/auth/user/login/

  #定义了一个路由api/auth/user/login/,取了一个n1的别名
  path('api/auth/user/login/', views.login, name='n1') 
  #也等价于下面这种
  URLPattern(RoutePattern("login/", name="n1", is_endpoint=True), views.login, None, "n1"),
 def login(request):
        return HttpResponse("欢迎登陆")
    from django.urls import reverse
    
    result = reverse("n1")
    print(result) # "api/auth/user/login/"

如果路径中带有参数的,可以使用kwargs加入

path('api/auth/login/<int:v1>/', views.login, name='n1'),
from django.urls import reverse

result = reverse("n1",kwargs={"v1":123})
print(result) # "/api/auth/login/123/"

在实际运用中,假设有2个函数

path('api/auth/login/',  views.login,  name='n1'), #登录
path('api/user/account/', views.account, name='n2'),#账号信息
def login(request):
    # 当用户登录成功之后,需要让用户跳转到 /api/user/account/ 页面
    # return redirect("/api/user/account/")
    
	# url = reverse("n2")   # "/api/user/account/"
    # return redirect(url)

	return redirect("n2")
    
def account(request):
    return HttpResponse("用户信息")

ps: redirect后可以直接带别名,内部会默认先reverse生成url再重定向

3. 路由分发

假设存在多个app,不同的处理逻辑分布在不同的app里面,就可以使用include路由分发
在这里插入图片描述

4. 路由本质*

4.1 include 源码

# django.urls.include
def include(arg, namespace=None):
    app_name = None
    if isinstance(arg, tuple):
        # Callable returning a namespace hint.
        try:
            urlconf_module, app_name = arg
			...
    else:
        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)
path('api/',include('apps.base.urls'))
----------->等价于
path('api/',(import_module('apps.base.urls'),None,None))

在这里插入图片描述
通过这种方法同样可以成功访问。

4.2 path源码

通过源码可以查看

path = partial(_path, Pattern=RoutePattern)


def _path(route, view, kwargs=None, name=None, Pattern=None):
    ...
    elif callable(view):
        pattern = Pattern(route, name=name, is_endpoint=True)
        return URLPattern(pattern, view, kwargs, name)
   ...

最后返回的是一个URLPattern类,

URLPattern(Pattern(route, name=name, is_endpoint=True), view, kwargs, name)

path('login/', views.login),
所以通过path()这个函数,本质上是创建了一个URLPattern对象
----->
URLPattern(
    RoutePattern("login/", name=None, is_endpoint=True),
    views.login,
    None,
    None
),

在执行path的时候,传入参数种类可以有

  • 网址+函数 ,返回的是URLPattern对象
path('login/', views.login),
-------------------->
URLPattern(
    RoutePattern("login/", name=None, is_endpoint=True),
    views.login,
    None,
    None
),
  • 网址+(模块对象,None,None),返回的是URLResolver对象
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,
        )
        ....
 path('api/', include("apps.base.urls")),
 path('api/', (
     import_module("apps.base.urls"),              # 模块对象 from app.base import urls
     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
)
  • 网址+(列表,None,None)
 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
)

总结,所有URL用类和对象嵌套的形式实现:

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
    )
]

5. 匹配的流程*

核心就是执行URLPattern的resolve方法,而resolve调用的是RoutePattern的match方法.遍历定义的urlpatterns ,执行每一个独享的resolve方法,

urlpatterns = [
    URLPattern(RoutePattern("login/", name=None, is_endpoint=True) , views.login, None, None),  # resolve -> RoutePattern.match
    URLPattern(RoutePattern("login2/", name=None, is_endpoint=True) , views.login, None, None),  # resolve -> RoutePattern.match
    URLPattern(RoutePattern("login3/", name=None, is_endpoint=True) , views.login, None, None),  # resolve -> RoutePattern.match
    URLPattern(RoutePattern("login4/", name=None, is_endpoint=True) , views.login, None, None),  # resolve -> RoutePattern.match
    path('login/', views.login),  # resolve
]

后续再进行路由匹配时:
1.源码流程
    ....
    django.urls.resolves.URLResolver.resolve

2.开始路由匹配
    for pattern in urlpatterns:
        sub_match = pattern.resolve("用户请求的URL")  # RoutePattern.match("用户请求的URL")
        if sub_match:
        #匹配成功,就返回了一个ResolverMatch对象
            reutrn 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,
                    },
                )
	# 如果匹配成功,会把这个 ResolverMatch对象赋值给request
    request.resolver_match = ResolverMatch对象

3.执行视图函数

  1. 请求开始
    请添加图片描述
  2. 路由匹配请添加图片描述
    请添加图片描述
    请添加图片描述
  3. 匹配过程
def resolve_request(self, request):
		...
        resolver = get_resolver()

匹配从这里开始,会创建URLResover对象

def get_resolver(urlconf=None):
    if urlconf is None:
        urlconf = settings.ROOT_URLCONF
    return _get_cached_resolver(urlconf)

将路由的地址传递进来,给 _get_cached_resolver(),这里urlconf实际就是settings里的创建的'day006.urls'
在这里插入图片描述

@functools.lru_cache(maxsize=None)
def _get_cached_resolver(urlconf=None):
    return URLResolver(RegexPattern(r'^/'), urlconf)

假设用户请求一个网址 /info/v1
最开始就返回一个URLResolver对象,RegexPattern(r’^/')是一个正则表达式

class URLResolver:
    def __init__(self, pattern, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
        self.pattern = pattern
        # self.pattern = RegexPattern(r'^/')
        self.urlconf_name = urlconf_name
        # self.urlconf_name = 'day006.urls'
  		...

得到URLResover对象后,执行对象里面的resolve方法

    def resolve_request(self, request):
    	...
  		resolver = get_resolver()
  		...
        resolver_match = resolver.resolve(request.path_info)
        ...
def resolve(self, path):
   path = str(path)  # path may be a reverse_lazy object
   tried = []
   # 1. 首先执行pattern.match
   match = self.pattern.match(path)
   if match:
       new_path, args, kwargs = match
       for pattern in self.url_patterns:
...


@cached_property
def urlconf_module(self):
    if isinstance(self.urlconf_name, str):
        return import_module(self.urlconf_name)
    else:
        return self.urlconf_name
        
  @cached_property
def url_patterns(self):
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
 ...

根据urlconf_name 导入根路由‘day006.urls’,然后再getattr,获取根路由里面的urlpatterns

def resolve(self, path):
		...
       new_path, args, kwargs = match
       for pattern in self.url_patterns:

这里就循环获取到的根路由里所有的路由

路由本质底层又是 URLPattern对象,URLResover对象

```python
    def resolve(self, path):
		...
        if match:
            new_path, args, kwargs = match
            for pattern in self.url_patterns:
                try:
                    sub_match = pattern.resolve(new_path)

这里循环这些URLPattern/URLResover对象,然后执行对象的resolve方法,new_path 等于匹配到的新路由 info/v1/
URLPattern(
    RoutePattern("login/", name=None, is_endpoint=True),
    views.login,
    None,
    None
)


class URLPattern:
    def __init__(self, pattern, callback, default_args=None, name=None):
        self.pattern = pattern
        # self.pattern = RoutePattern("login/", name=None, is_endpoint=True)
        self.callback = callback  # the view
        # self.callback = views.login  视图函数
        ....
    def resolve(self, path):
        match = self.pattern.match(path)
        ...

这里就调用pattern的match进行匹配,也就是RoutePattern("login/", name=None, is_endpoint=True)对象的match方法

class RoutePattern(CheckURLMixin):
    regex = LocaleRegexDescriptor('_route')

    def __init__(self, route, name=None, is_endpoint=False):
        self._route = route
        # self._route = ‘login/’
        self._is_endpoint = is_endpoint
        # self._is_endpoint = True
        ...

    def match(self, path):
      	...
        return path[match.end():], (), kwargs
        ...

match本质上就是拿着这个'login/'和用户请求的URL进行匹配,最后返回
	return path[match.end():], (), kwargs
	匹配成功的切片

如果是路由分发,会先匹配自己的前缀,再去分发的路由逐一进行匹配

请添加图片描述

6. 关于网址后面 ‘/’

# 如果有一个路由是这样的
path('login/', views.login, name='login')
#那么直接请求’.../login/‘   是肯定能请求成功的
	http://127.0.0.1:8000/login/  成功
# 如果连接是'.../login',那么会执行一次重定向,重新自动请求'.../login/'	
	http://127.0.0.1:8000/login   重定向,GET
	http://127.0.0.1:8000/login/   

当直接访问或者get请求访问,会成功,但是如果POST请求就会出错。
在这里插入图片描述
如果在settings里设置了APPEND_SLASH = False,那么就会是严格模式,第一什么就以什么方式访问。此时如果再访问'.../login'就会报错。

7. Django和Flask对于request处理的区别

  • Django将request对象逐一传递,最后到视图函数的是经过层层封装的request对象,可以调用里面各种属性和方法
  • Flasks将request对象独立出去,哪里需要用到,再去进行用和修改。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值