一、路由组件的使用

1、使用实例

在视图中继承GenericViewSet类来完成功能时,需要自己对路由的写法有所改变,需要在as_view中传入actions字典参数:

   re_path('books/$', views.BookView.as_view({'get': 'list','post':'create'}), name="books"),

但是rest framework中的路由组件完全可以自动生成对应的路由这样的路由。

from rest_framework import routers

router=routers.DefaultRouter()
router.register('books',views.BookView) urlpatterns = [ ... re_path('',include(router.urls)), ... ]

这样就会生成下面的url形式:

URL pattern: ^books/$ Name: 'books-list'
URL pattern: ^books/{pk}/$ Name: 'books-detail'

2、参数

  • register() 方法有两个强制参数:

  (1)prefix用于路由url前缀

  (2)viewset处理请求的viewset类

3、额外连接和操作

@detail_route@list_route装饰的视图集上的任何方法也将被路由,比如在BookView中又自定义了一个方法,那么可以加上装饰器生成对应的路由:

class BookView(GenericViewSet):

    queryset = models.Book.objects.all()
serializer_class = BookModelSerializer def list(self,request):
pass @detail_route(methods=['get'],url_path='set-book')
def set_bookname(self, request, pk=None):
return HttpResponse('...')

此时会多生成这样一条路由规则:

^books/(?P<pk>[^/.]+)/set-book/$ [name='book-set-book']

二、内置API

1、SimpleRouter

该路由器包括标准集合listcreateretrieveupdatepartial_update 和 destroy动作的路由。视图集中还可以使用@ detail_route@ list_route装饰器标记要被路由的其他方法。

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'}
),
# Dynamically generated list routes. Generated using
# @action(detail=False) decorator on methods of the viewset.
DynamicRoute(
url=r'^{prefix}/{url_path}{trailing_slash}$',
name='{basename}-{url_name}',
detail=False,
initkwargs={}
),
# Detail route.
Route(
url=r'^{prefix}/{lookup}{trailing_slash}$',
mapping={
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
},
name='{basename}-detail',
detail=True,
initkwargs={'suffix': 'Instance'}
),
# Dynamically generated detail routes. Generated using
# @action(detail=True) decorator on methods of the viewset.
DynamicRoute(
url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$',
name='{basename}-{url_name}',
detail=True,
initkwargs={}
),
] def __init__(self, trailing_slash=True):
self.trailing_slash = '/' if trailing_slash else ''
super(SimpleRouter, self).__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):
"""
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)]))
extra_actions = viewset.get_extra_actions() # checking action names against the known actions list
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_dynamic_route(self, route, action):
initkwargs = route.initkwargs.copy()
initkwargs.update(action.kwargs) url_path = escape_curly_brackets(action.url_path) return Route(
url=route.url.replace('{url_path}', url_path),
mapping=action.mapping,
name=route.name.replace('{url_name}', action.url_name),
detail=route.detail,
initkwargs=initkwargs,
) def get_method_map(self, viewset, method_map):
"""
Given a viewset, and a mapping of http methods to actions,
return a new mapping which only includes any mappings that
are actually implemented by the viewset.
"""
bound_methods = {}
for method, action in method_map.items():
if hasattr(viewset, action):
bound_methods[method] = action
return bound_methods def get_lookup_regex(self, viewset, lookup_prefix=''):
"""
Given a viewset, return the portion of URL regex that is used
to match against a single instance. Note that lookup_prefix is not used directly inside REST rest_framework
itself, but is required in order to nicely support nested router
implementations, such as drf-nested-routers. https://github.com/alanjds/drf-nested-routers
"""
base_regex = '(?P<{lookup_prefix}{lookup_url_kwarg}>{lookup_value})'
# Use `pk` as default field, unset set. Default regex should not
# consume `.json` style suffixes and should break at '/' boundaries.
lookup_field = getattr(viewset, 'lookup_field', 'pk')
lookup_url_kwarg = getattr(viewset, 'lookup_url_kwarg', None) or lookup_field
lookup_value = getattr(viewset, 'lookup_value_regex', '[^/.]+')
return base_regex.format(
lookup_prefix=lookup_prefix,
lookup_url_kwarg=lookup_url_kwarg,
lookup_value=lookup_value
) def get_urls(self):
"""
Use the registered viewsets to generate a list of URL patterns.
"""
ret = [] 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 there is no prefix, the first part of the url is probably
# controlled by project's urls.py and the router is in an app,
# so a slash in the beginning will (A) cause Django to give
# warnings and (B) generate URLS that will require using '//'.
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

SimpleRouter

2、DefaultRouter

这个路由器类似于上面的SimpleRouter,但是还包括一个默认返回所有列表视图的超链接的API根视图。它还生成可选的.json样式格式后缀的路由。

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(DefaultRouter, self).__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):
"""
Generate the list of URL patterns, including a default root view
for the API, and appending `.json` style format suffixes.
"""
urls = super(DefaultRouter, self).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

DefaultRouter

三、源码

按照使用自动生成路由的规则一步步的探索源码流程,以SimpleRouter为例:

  • 生成SimpleRouter实例router
from rest_framework.routers import SimpleRouter

router=SimpleRouter()

显然会执行SimpleRouter类的__init__方法:

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

SimpleRouter创建的URL将附加尾部斜杠。 在实例化路由器时,可以通过将trailing_slash参数设置为`False'来修改此行为:

router = SimpleRouter(trailing_slash=False)

其次,执行父类BaseRouter的__init__方法:

class BaseRouter(six.with_metaclass(RenameRouterMethods)):
def __init__(self):
self.registry = []
...

所以,在实例化SimpleRouter后会判断生成的url尾部是否加‘/’以及生成registry字典。

  • 执行register方法
router.register('books',views.BookView)

在SimpleRouter类中没有register方法,所以会执行父类BaseRouter中的register方法:

class BaseRouter(six.with_metaclass(RenameRouterMethods)):

    ...

    def register(self, prefix, viewset, basename=None, base_name=None):
if base_name is not None:
msg = "The `base_name` argument is pending deprecation in favor of `basename`."
warnings.warn(msg, RemovedInDRF311Warning, 2) assert not (basename and base_name), (
"Do not provide both the `basename` and `base_name` arguments.") if basename is None:
basename = base_name 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
....

  将传入的url前缀、视图viewset和base_name以元祖的形式 添加到实例化生成的字典registry中,值得注意的是如果base_name没有传值得话就会生成默认的basename(模型表的小写),获取默认的basename方法是在SimpleRouter类中:

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

get_default_basename

此时,就会在registry字典中生成这样的数据:

registry = {
('books','BookView','book'),
}
  • 生成url
urlpatterns = router.urls

接下来就是执行SimpleRouter类的urls属性,显然它没有这个属性,就会会执行父类BaseRouter中的urls属性:

class BaseRouter(six.with_metaclass(RenameRouterMethods)):

    ...

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

紧接着执行get_urls方法,会先去SimpleRouter中执行这个方法:

class SimpleRouter(BaseRouter):
...
def get_urls(self):
"""
Use the registered viewsets to generate a list of URL patterns.
"""
ret = [] 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 there is no prefix, the first part of the url is probably
# controlled by project's urls.py and the router is in an app,
# so a slash in the beginning will (A) cause Django to give
# warnings and (B) generate URLS that will require using '//'.
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
...

在这个方法中会循环得到的registry字典:

(1)生成lookup构建url的正则表达式

    def get_lookup_regex(self, viewset, lookup_prefix=''):
"""
Given a viewset, return the portion of URL regex that is used
to match against a single instance. Note that lookup_prefix is not used directly inside REST rest_framework
itself, but is required in order to nicely support nested router
implementations, such as drf-nested-routers. https://github.com/alanjds/drf-nested-routers
"""
base_regex = '(?P<{lookup_prefix}{lookup_url_kwarg}>{lookup_value})'
# Use `pk` as default field, unset set. Default regex should not
# consume `.json` style suffixes and should break at '/' boundaries.
lookup_field = getattr(viewset, 'lookup_field', 'pk')
lookup_url_kwarg = getattr(viewset, 'lookup_url_kwarg', None) or lookup_field
lookup_value = getattr(viewset, 'lookup_value_regex', '[^/.]+')
return base_regex.format(
lookup_prefix=lookup_prefix,
lookup_url_kwarg=lookup_url_kwarg,
lookup_value=lookup_value
)

get_lookup_regex

基于:

base_regex = '(?P<{lookup_prefix}{lookup_url_kwarg}>{lookup_value})'

lookup_prefix默认为空,lookup_url_kwargs为正则分组的关键字默认为lookup_field(lookup_field默认为pk),lookup_value_regex默认为[^/.]+

所以最后生成:

base_regex = '(?P<pk>[^/.]+)'

(2)生成routes

生成所有的路由

 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)]))
extra_actions = viewset.get_extra_actions() # checking action names against the known actions list
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

get_routes

在get_routes方法处理的是所有的@list_route和@detail_route,返回的是所有的route。

内部给予的url模板,router列表:

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'}
),
# Dynamically generated list routes. Generated using
# @action(detail=False) decorator on methods of the viewset.
DynamicRoute(
url=r'^{prefix}/{url_path}{trailing_slash}$',
name='{basename}-{url_name}',
detail=False,
initkwargs={}
),
# Detail route.
Route(
url=r'^{prefix}/{lookup}{trailing_slash}$',
mapping={
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
},
name='{basename}-detail',
detail=True,
initkwargs={'suffix': 'Instance'}
),
# Dynamically generated detail routes. Generated using
# @action(detail=True) decorator on methods of the viewset.
DynamicRoute(
url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$',
name='{basename}-{url_name}',
detail=True,
initkwargs={}
),
]

routes列表

get_routes方法中known_actions得到的是循环模板routers列表得到的所有的action列表,而get_routes方法中extra_actions得到的是经过viewset视图类中的action列表

经过判断确认在视图类中使用的action是模板本身存在的,而不是自己随意添加的action

        # checking action names against the known actions list
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))

紧接着生成list和detail两类路由,根据detail是True还是False来进行判断的:

def detail_route(methods=None, **kwargs):
"""
Used to mark a method on a ViewSet that should be routed for detail requests.
"""
warnings.warn(
"`detail_route` is deprecated and will be removed in 3.10 in favor of "
"`action`, which accepts a `detail` bool. Use `@action(detail=True)` instead.",
RemovedInDRF310Warning, stacklevel=2
) def decorator(func):
func = action(methods, detail=True, **kwargs)(func)
if 'url_name' not in kwargs:
func.url_name = func.url_path.replace('_', '-')
return func
return decorator

detail_route装饰器

def list_route(methods=None, **kwargs):
"""
Used to mark a method on a ViewSet that should be routed for list requests.
"""
warnings.warn(
"`list_route` is deprecated and will be removed in 3.10 in favor of "
"`action`, which accepts a `detail` bool. Use `@action(detail=False)` instead.",
RemovedInDRF310Warning, stacklevel=2
) def decorator(func):
func = action(methods, detail=False, **kwargs)(func)
if 'url_name' not in kwargs:
func.url_name = func.url_path.replace('_', '-')
return func
return decorator

list_route装饰器

        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)

上面就是动态生成路由,利用的就是_get_dynamic_route方法,而这个方法返回的就是Route实例(url模板中定义好的route)

    def _get_dynamic_route(self, route, action):
initkwargs = route.initkwargs.copy()
initkwargs.update(action.kwargs) url_path = escape_curly_brackets(action.url_path) return Route(
url=route.url.replace('{url_path}', url_path),
mapping=action.mapping,
name=route.name.replace('{url_name}', action.url_name),
detail=route.detail,
initkwargs=initkwargs,
)

_get_dynamic_route

(3)生成re_path

循环得到的routers列表,进行当前viewset中method与action的映射:

 mapping = self.get_method_map(viewset, route.mapping)
    {‘get’:'list','post':'create'}

构建url模式:

               regex = route.url.format(
prefix=prefix,
lookup=lookup,
trailing_slash=self.trailing_slash
)

这样就会生成这样的url:

url=r'^books/(?P<pk>[^/.]+)$',

从源码的这里可以知道,url的构成主要是由前缀prefix以及正则base_regex

r'^{prefix}/{base_regex}'

然后生成对应的re_path,并将所有的re_path加入到对应的列表中

url(regex, view, name=name)
def url(regex, view, kwargs=None, name=None):
return re_path(regex, view, kwargs, name)

url

参考:https://q1mi.github.io/Django-REST-framework-documentation/api-guide/routers_zh/#routers

rest framework之路由组件的更多相关文章

  1. DRF 视图组件,路由组件

    视图组件  -- 第一次封装   -- GenericAPIView(APIView):    queryset = None    serializer_class = None    def ge ...

  2. REST framework之分页组件

    REST framework之分页组件 一 简单分页 查看第n页,每页显示n条 from rest_framework.pagination import PageNumberPagination # ...

  3. drf路由组件(4星)

    路由组件(4星) 路由Routers 对于视图集ViewSet, 我们除了可以自己手动指明请求方式与动作action之间的对应关系外,还可以使用Routers来帮助我们快速实现路由信息. REST f ...

  4. vue_VueRouter 路由_路由器管理n个路由_并向路由组件传递数据_新标签路由_编程式路由导航

    路由:就是一个 key 与 value 的映射关系.key 就是 pathh 前台路由的 value 是 Component 组件对象 后台路由的 value 是一个 回调函数 普通链接: 会发送请求 ...

  5. Vue-admin工作整理(四):路由组件传参

    路由组件传参:如果在一个页面中,需要根据路由去获得参数,去对页面进行一些逻辑处理,首先可以通过this.$router来获取路由实例的参数,这样页面组件和路由就进行了耦合,为了进行分离,更大程度复用, ...

  6. VueJs(11)---vue-router(命名路由,命名视图,重定向别名,路由组件传参)

    vue-router 上篇文章讲了第一篇vue-router相关文章,文章地址:VueJs(10)---vue-router(进阶1) 一.命名路由 有时候,通过一个名称来标识一个路由显得更方便一些, ...

  7. vue-router之路由钩子(组件内路由钩子必须在路由组件调用,子组件没用)

    模式 vue-router中的模式选项主要在router实例化的时候进行定义的,如下 const router = new VueRouter({ mode: 'history', // 两种类型hi ...

  8. ARouter 路由 组件 跳转 MD

    目录 简介 支持的功能 典型应用 简单使用 进阶使用 更多功能 其他 Q&A Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs bai ...

  9. 067——VUE中vue-router之使用transition设置酷炫的路由组件过渡动画效果

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

随机推荐

  1. go语言从例子开始之Example24.通道同步

    我们可以使用通道来同步 Go 协程间的执行状态.这里是一个使用阻塞的接受方式来等待一个 Go 协程的运行结束. Example: package main import "fmt" ...

  2. 网页head头部meta和link标签使用大全

    <!-- 声明文档使用的字符编码 --> <meta charset="utf-8"> <!-- 声明文档的兼容模式 --> <meta ...

  3. dubbo-monitor安装

    dubbo-monitor安装 cd /opt/tools/ #包目录 tar -C /opt/ -xf dubbo-monitor-simple--assembly.tar.gz cd dubbo- ...

  4. 【Java架构:基础技术】一篇文章搞掂:Spring Boot 官方文档解读

    本文篇幅较长,建议合理利用右上角目录进行查看(如果没有目录请刷新). 本文内容大部分是翻译和总结官方文档,可以到https://docs.spring.io/spring-boot/docs查看(此地 ...

  5. (5)C++ 循环和判断

    循环 一.for循环 ; i < ; i++) { cout << "abc"<< endl; } 或 ; i; i--) { cout <&l ...

  6. table td 溢出隐藏

    需要给table加一个属性:table-layout:fixed;

  7. 简单gui

    import java.awt.Button; import java.awt.Frame; import java.awt.event.WindowAdapter; import java.awt. ...

  8. 转 jmeter 关联

    jmeter(十二)关联之正则表达式提取器   如果有这样的情况:一个完整的操作流程,需要先完成某个操作,获得某个值或数据信息,然后才能进行下一步的操作(也就是常说的关联/将上一个请求的响应结果作为下 ...

  9. shell编程:向函数中传递参数

    cal.sh sh cal.sh 20 + 10 实现这样传参的函数(shell不是一个严谨的编程语言,参数这种是不用定义的,函数中直接引用,shell执行中直接写) #!/bin/bash # ca ...

  10. docker对容器进行资源限制

    对内存进行资源限制 docker run --memory=200m soymilk/stress 对cpu进行资源限制 终端1 docker run --name=test1 --cpu-share ...