一. URL构成

正常情况下的url

url(r'^article/(?P<year>\d{4})/(?P<month>\d{1,2})/$','article.viws.month',name="archive_month"),#按月归档

url的构成

r'^{prefix}/{base_regex}'
分为prefix前缀和base_regex正则两部分

base_regex构成

base_regex = '(?P<{lookup_prefix}{lookup_url_kwarg}>{lookup_value})'
lookup_prefix默认为空
lookup_url_kwarg为正则分组的关键字
lookup_value为正则表达式允许的类型,默认为[^/.]+

二.Router用法

1
2
3
4
5
6
7
8
9
from rest_framework import routers
router = routers.DefaultRouter()
# viewset注册到路由
router.register('users', UserViewSet)

# 路由帮我们生成合适的 url
urlpatterns = [
	   url(r'', include(router.urls)),
]

如果自己写: url最终效果:

1
2
3
4
5
6
urlpatterns = [
	url(r'users'UserViewSet.as_view({'get': 'list', 'post': create}), name='user-list'),
	url(r'users/(?P<pk>[^/.]+)/, UserViewSet.as_view({'get': 'retrieve', 'put': 'update'}), name='user-detail'),
	url(r'users/me/', UserViewSet.as_view('get': 'me'), name='user-me'),
	url(r'users/(?P<pk>[^/.]+)/info/', UserViewSet.as_view('get': 'info'), name='user-info')
]

核心原理

DRF SimpleRouter提供了一些url路由模版

 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
routes = [
        # List route.
        Route(
            url=r'^{prefix}{trailing_slash}$',
            mapping={
                'get': 'list',
                'post': 'create'
            },
            name='{basename}-list',
            initkwargs={'suffix': 'List'}
        ),
        # Dynamically generated list routes.
        # Generated using @list_route decorator
        # on methods of the viewset.
        DynamicListRoute(
            url=r'^{prefix}/{methodname}{trailing_slash}$',
            name='{basename}-{methodnamehyphen}',
            initkwargs={}
        ),
        # Detail route.
        Route(
            url=r'^{prefix}/{lookup}{trailing_slash}$',
            mapping={
                'get': 'retrieve',
                'put': 'update',
                'patch': 'partial_update',
                'delete': 'destroy'
            },
            name='{basename}-detail',
            initkwargs={'suffix': 'Instance'}
        ),
        # Dynamically generated detail routes.
        # Generated using @detail_route decorator on methods of the viewset.
        DynamicDetailRoute(
            url=r'^{prefix}/{lookup}/{methodname}{trailing_slash}$',
            name='{basename}-{methodnamehyphen}',
            initkwargs={}
        ),
    ]

Router核心:

比较url路由模板属性 mapping={httpmethod, action}
查看在viewset是否实现了对应的action方法
如果存在,就实例化url模板,否则忽略

三.DRF核心源代码

 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
def get_urls(self):
    """
    Use the registered viewsets to generate a list of URL patterns.
    """
    ret = []
    for prefix, viewset, basename in self.registry:
        # 构建url的正则表达式
        lookup = self.get_lookup_regex(viewset)
        # 处理 @list_route() 
        #     @detail_route() 
        #     返回所有的router
        routes = self.get_routes(viewset)

        for route in routes:

            # Only actions which actually exist on the viewset will be bound
                
            # 判断viewset是否实现url路由模板mapping={httpmethod, action}对应的action方法
            mapping = self.get_method_map(viewset, route.mapping)
            # 如果没有,则忽略
            if not mapping:
                continue

            #如果存在,则实例化该url路由模版
            # Build the url pattern
            regex = route.url.format(
                prefix=prefix,
                lookup=lookup,
                trailing_slash=self.trailing_slash
            )

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

    return ret  

这里需要的注意的是: get_lookup_regex()方法

路由正则表达式分组的名称默认是pk

 url(r'users/(?P<pk>[^/.]+)/, UserViewSet.as_view({'get': 'retrieve', 'put': 'update'}), name='user-detail'),

如需修改,示例如下:

1
2
3
4
5
 class UserViewSet(viewsets.GenericViewSet):
    # 用于 get_object()
    lookup_field = 'pk'
    # 用于 url正则分组关键字
    lookup_url_kwarg = None

四. @list_route() @detail_route() 装饰器 4.1用法

1
2
3
4
5
6
@list_route():
def me():
    pass
@detail_route(methods=['get', 'patch'], permission_classes=[IsAdminOrIsSelf])
def info():
    pass

可以在装饰器中指定http方法,权限,认证 methods默认为get

4.2源代码:

 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
def list_route(methods=None, **kwargs):
	"""
	Used to mark a method on a ViewSet that should be routed for list requests.
	"""
	methods = ['get'] if (methods is None) else methods

	def decorator(func):
	    func.bind_to_methods = methods
	    func.detail = False
	    func.kwargs = kwargs
	    return func
	return decorator

def detail_route(methods=None, **kwargs):
	"""
	Used to mark a method on a ViewSet that should be routed for detail requests.
	"""
	methods = ['get'] if (methods is None) else methods

	def decorator(func):
	    func.bind_to_methods = methods
	    func.detail = True
	    func.kwargs = kwargs
	    return func
	return decorator

五.返回所有的route

 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

def get_routes(self, viewset):
    """
    Augment `self.routes` with any dynamically generated routes.
    Returns a list of the Route namedtuple.
    """
    # converting to list as iterables are good for one pass, known host needs to be checked again and again for
    # different functions.
    known_actions = list(flatten([route.mapping.values() for route in self.routes if isinstance(route, Route)]))

    # Determine any `@detail_route` or `@list_route` decorated methods on the viewset
    detail_routes = []
    list_routes = []
    for methodname in dir(viewset):
        attr = getattr(viewset, methodname)
        httpmethods = getattr(attr, 'bind_to_methods', None)
        detail = getattr(attr, 'detail', True)
        if httpmethods:
            # checking method names against the known actions list
            if methodname in known_actions:
                raise ImproperlyConfigured('Cannot use @detail_route or @list_route '
                                           'decorators on method "%s" '
                                           'as it is an existing route' % methodname)
            httpmethods = [method.lower() for method in httpmethods]
            if detail:
                detail_routes.append((httpmethods, methodname))
            else:
                list_routes.append((httpmethods, methodname))

        def _get_dynamic_routes(route, dynamic_routes):
            ret = []
            for httpmethods, methodname in dynamic_routes:
               method_kwargs = getattr(viewset, methodname).kwargs
               initkwargs = route.initkwargs.copy()
               initkwargs.update(method_kwargs)
               url_path = initkwargs.pop("url_path", None) or methodname
               ret.append(Route(
                   url=replace_methodname(route.url, url_path),
                   mapping={httpmethod: methodname for httpmethod in httpmethods},
                   name=replace_methodname(route.name, url_path),
                   initkwargs=initkwargs,
                ))

            return ret

    ret = []
    for route in self.routes:
        if isinstance(route, DynamicDetailRoute):
           # Dynamic detail routes (@detail_route decorator)
           ret += _get_dynamic_routes(route, detail_routes)
        elif isinstance(route, DynamicListRoute):
            # Dynamic list routes (@list_route decorator)
            ret += _get_dynamic_routes(route, list_routes)
        else:
            # Standard route
            ret.append(route)

    return ret

可以看出

attr = getattr(viewset, methodname)
httpmethods = getattr(attr, 'bind_to_methods', None)
detail = getattr(attr, 'detail', True)

通过判断,viewset中method是否有bind_to_methods,和detail属性 来区分DynamicListRoute和DynamicDetailRoute 并且method的名称不能和List route,Detail route中的action重名