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.执行视图函数
- 请求开始
- 路由匹配
- 匹配过程
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对象独立出去,哪里需要用到,再去进行用和修改。