rest framework之路由组件
一、路由组件的使用
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
该路由器包括标准集合list
, create
, retrieve
, update
, partial_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之路由组件的更多相关文章
- DRF 视图组件,路由组件
视图组件 -- 第一次封装 -- GenericAPIView(APIView): queryset = None serializer_class = None def ge ...
- REST framework之分页组件
REST framework之分页组件 一 简单分页 查看第n页,每页显示n条 from rest_framework.pagination import PageNumberPagination # ...
- drf路由组件(4星)
路由组件(4星) 路由Routers 对于视图集ViewSet, 我们除了可以自己手动指明请求方式与动作action之间的对应关系外,还可以使用Routers来帮助我们快速实现路由信息. REST f ...
- vue_VueRouter 路由_路由器管理n个路由_并向路由组件传递数据_新标签路由_编程式路由导航
路由:就是一个 key 与 value 的映射关系.key 就是 pathh 前台路由的 value 是 Component 组件对象 后台路由的 value 是一个 回调函数 普通链接: 会发送请求 ...
- Vue-admin工作整理(四):路由组件传参
路由组件传参:如果在一个页面中,需要根据路由去获得参数,去对页面进行一些逻辑处理,首先可以通过this.$router来获取路由实例的参数,这样页面组件和路由就进行了耦合,为了进行分离,更大程度复用, ...
- VueJs(11)---vue-router(命名路由,命名视图,重定向别名,路由组件传参)
vue-router 上篇文章讲了第一篇vue-router相关文章,文章地址:VueJs(10)---vue-router(进阶1) 一.命名路由 有时候,通过一个名称来标识一个路由显得更方便一些, ...
- vue-router之路由钩子(组件内路由钩子必须在路由组件调用,子组件没用)
模式 vue-router中的模式选项主要在router实例化的时候进行定义的,如下 const router = new VueRouter({ mode: 'history', // 两种类型hi ...
- ARouter 路由 组件 跳转 MD
目录 简介 支持的功能 典型应用 简单使用 进阶使用 更多功能 其他 Q&A Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs bai ...
- 067——VUE中vue-router之使用transition设置酷炫的路由组件过渡动画效果
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
随机推荐
- go语言从例子开始之Example24.通道同步
我们可以使用通道来同步 Go 协程间的执行状态.这里是一个使用阻塞的接受方式来等待一个 Go 协程的运行结束. Example: package main import "fmt" ...
- 网页head头部meta和link标签使用大全
<!-- 声明文档使用的字符编码 --> <meta charset="utf-8"> <!-- 声明文档的兼容模式 --> <meta ...
- dubbo-monitor安装
dubbo-monitor安装 cd /opt/tools/ #包目录 tar -C /opt/ -xf dubbo-monitor-simple--assembly.tar.gz cd dubbo- ...
- 【Java架构:基础技术】一篇文章搞掂:Spring Boot 官方文档解读
本文篇幅较长,建议合理利用右上角目录进行查看(如果没有目录请刷新). 本文内容大部分是翻译和总结官方文档,可以到https://docs.spring.io/spring-boot/docs查看(此地 ...
- (5)C++ 循环和判断
循环 一.for循环 ; i < ; i++) { cout << "abc"<< endl; } 或 ; i; i--) { cout <&l ...
- table td 溢出隐藏
需要给table加一个属性:table-layout:fixed;
- 简单gui
import java.awt.Button; import java.awt.Frame; import java.awt.event.WindowAdapter; import java.awt. ...
- 转 jmeter 关联
jmeter(十二)关联之正则表达式提取器 如果有这样的情况:一个完整的操作流程,需要先完成某个操作,获得某个值或数据信息,然后才能进行下一步的操作(也就是常说的关联/将上一个请求的响应结果作为下 ...
- shell编程:向函数中传递参数
cal.sh sh cal.sh 20 + 10 实现这样传参的函数(shell不是一个严谨的编程语言,参数这种是不用定义的,函数中直接引用,shell执行中直接写) #!/bin/bash # ca ...
- docker对容器进行资源限制
对内存进行资源限制 docker run --memory=200m soymilk/stress 对cpu进行资源限制 终端1 docker run --name=test1 --cpu-share ...