Jiang's blog

DRF源码解析3--routers对路由的封装

Word count: 855Reading time: 4 min
2020/02/20 Share

当我们想最方便的使用drf的路由的时候,只需要用DefaultRoute实例化一个路由实例,然后注册,再讲注册后的组件加入urlpatterns即可

1
2
3
4
5
6
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
# 其中BookModelView继承了viewsets.ModelViewSet
router.register(r'^book', views.BookModelView)

urlpatterns += router.urls

到底routers底层是怎么样实现的呢

routers

DefaultRoute是routers组件里的一个类,相当于帮助我们在路由里构建好了如{‘list’:”create”}的对应关系,它继承了SimpleRouter,截取源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
class SimpleRouter(BaseRouter):
routes = [
# List route.
Route(
url=r'^{prefix}{trailing_slash}$',
# 这里构建好了对应关系
mapping={
'get': 'list',
'post': 'create'
},
name='{basename}-list',
detail=False,
initkwargs={'suffix': 'List'}
),

def __init__(self, trailing_slash=True):
self.trailing_slash = '/' if trailing_slash else ''
super().__init__()

def get_default_basename(self, viewset):
"""
If `basename` is not specified, attempt to automatically determine
it from the viewset.
"""
queryset = getattr(viewset, 'queryset', None)

assert queryset is not None, '`basename` argument not specified, and could ' \
'not automatically determine the name from the viewset, as ' \
'it does not have a `.queryset` attribute.'

return queryset.model._meta.object_name.lower()

def get_routes(self, viewset):
# 获取对应关系
known_actions = list(flatten([route.mapping.values() for route in self.routes if isinstance(route, Route)]))
# 找出viewsets里所有的方法
extra_actions = viewset.get_extra_actions()

# checking action names against the known actions list
# 判断路由的方法对应的viewsets的方法
not_allowed = [
action.__name__ for action in extra_actions
if action.__name__ in known_actions
]
if not_allowed:
msg = ('Cannot use the @action decorator on the following '
'methods, as they are existing routes: %s')
raise ImproperlyConfigured(msg % ', '.join(not_allowed))

# partition detail and list actions
detail_actions = [action for action in extra_actions if action.detail]
list_actions = [action for action in extra_actions if not action.detail]

routes = []
for route in self.routes:
if isinstance(route, DynamicRoute) and route.detail:
routes += [self._get_dynamic_route(route, action) for action in detail_actions]
elif isinstance(route, DynamicRoute) and not route.detail:
routes += [self._get_dynamic_route(route, action) for action in list_actions]
else:
routes.append(route)

return routes

def get_urls(self):
"""
Use the registered viewsets to generate a list of URL patterns.
"""
ret = []
# 这里的self.registry是父类BaseRouter的一个属性
for prefix, viewset, basename in self.registry:
lookup = self.get_lookup_regex(viewset)
routes = self.get_routes(viewset)
# 这里是对路径进行了不同形式的封装
for route in routes:

# Only actions which actually exist on the viewset will be bound
mapping = self.get_method_map(viewset, route.mapping)
if not mapping:
continue

# Build the url pattern
regex = route.url.format(
prefix=prefix,
lookup=lookup,
trailing_slash=self.trailing_slash
)

if not prefix and regex[:2] == '^/':
regex = '^' + regex[2:]

initkwargs = route.initkwargs.copy()
initkwargs.update({
'basename': basename,
'detail': route.detail,
})

view = viewset.as_view(mapping, **initkwargs)
name = route.name.format(basename=basename)
ret.append(url(regex, view, name=name))

return ret

进入BaseRouter中,截取源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class BaseRouter:
def __init__(self):
# SimpleRouter的get_urls中的需要的属性
self.registry = []

def register(self, prefix, viewset, basename=None):
# 如果没有basename,则self.get_default_basename()方法
# get_default_basename()方法就是报错要求basename必须有,
# 从而看得出来要想使用router组件必须执行register()方法
if basename is None:
basename = self.get_default_basename(viewset)
self.registry.append((prefix, viewset, basename))

# invalidate the urls cache
if hasattr(self, '_urls'):
del self._urls

def get_default_basename(self, viewset):
"""
If `basename` is not specified, attempt to automatically determine
it from the viewset.
"""
raise NotImplementedError('get_default_basename must be overridden')

def get_urls(self):
"""
Return a list of URL patterns, given the registered viewsets.
"""
raise NotImplementedError('get_urls must be overridden')

@property
def urls(self):
if not hasattr(self, '_urls'):
self._urls = self.get_urls()
return self._urls

DefaultRoute类进行了更好的优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class DefaultRouter(SimpleRouter):
"""
The default router extends the SimpleRouter, but also adds in a default
API root view, and adds format suffix patterns to the URLs.
"""
include_root_view = True
include_format_suffixes = True
root_view_name = 'api-root'
default_schema_renderers = None
APIRootView = APIRootView
APISchemaView = SchemaView
SchemaGenerator = SchemaGenerator

def __init__(self, *args, **kwargs):
if 'root_renderers' in kwargs:
self.root_renderers = kwargs.pop('root_renderers')
else:
self.root_renderers = list(api_settings.DEFAULT_RENDERER_CLASSES)
super().__init__(*args, **kwargs)

def get_api_root_view(self, api_urls=None):
"""
Return a basic root view.
"""
api_root_dict = OrderedDict()
list_name = self.routes[0].name
for prefix, viewset, basename in self.registry:
api_root_dict[prefix] = list_name.format(basename=basename)

return self.APIRootView.as_view(api_root_dict=api_root_dict)

def get_urls(self):
urls = super().get_urls()
if self.include_root_view:
view = self.get_api_root_view(api_urls=urls)
root_url = url(r'^$', view, name=self.root_view_name)
urls.append(root_url)

if self.include_format_suffixes:
urls = format_suffix_patterns(urls)

return urls

最后路由组件创建了怎么样的url?
3mDRXt.png

CATALOG
  1. 1. routers