在Django中使用基于类的视图(ClassView),类中所定义的方法名称与Http的请求方法相对应,才能基于路由将请求分发(dispatch)到ClassView中的方法进行处理,而Django REST framework中可以突破这一点,通过ViewSets可以实现自定义路由。

创建一个ViewSets

为get_stocks方法添加list_route装饰器,url_path参数是暴露在外的接口名称

class StockViewSet(viewsets.ModelViewSet):
queryset = AppStock.objects.all() @list_route(url_path='getstocklist')
def get_stocks(self, request, *args, **kwargs):
'''获取股票列表''' return Response({'succss':True,'msg':'操作成功'})

来看一下list_route的定义:

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

对于接口,一般有获取列表页和获取详情两种形式。同样的,还有detail_route装饰器。list_route、detail_route的作用都是为方法添加了bind_to_methods、detail、kwargs属性,唯一的区别是detail属性值的不同

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

注册路由

router=DefaultRouter()
router.register(r'stock',StockViewSet) urlpatterns = [
url(r'',include(router.urls)), url(r'^admin/', admin.site.urls),
]

自定义路由实现过程

DefaultRouter是BaseRouter的子类,register方法内部将其注册的prefix与之对应的viewset保存在registry列表中

class BaseRouter(object):
def __init__(self):
self.registry = [] def register(self, prefix, viewset, base_name=None):
if base_name is None:
base_name = self.get_default_base_name(viewset)
self.registry.append((prefix, viewset, base_name))

其urls属性是一个描述符,内部调用了get_urls方法

从get_routes中可以看出些眉目了,遍历ViewSet中定义的方法,获取到方法的bind_to_method和detail属性(list_route、detail_route的功劳),根据detial属性将它们分别保存到detail_routes和list_routes列表中,保存的是httpmethod与methodname的元祖对象

    def get_routes(self, viewset):
"""
省略若干...
"""
# 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)
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
url_name = initkwargs.pop("url_name", None) or url_path
ret.append(Route(
url=replace_methodname(route.url, url_path),
mapping={httpmethod: methodname for httpmethod in httpmethods},
name=replace_methodname(route.name, url_name),
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

接着,遍历routes列表,看到这个代码,我也是看了挺久才看懂这用意,routes列表包含固定的四个Route对象

routes = [
# List route.
Route(
url=r'^{prefix}{trailing_slash}$',
mapping={
'get': 'list',
'post': 'create'
},
name='{basename}-list',
initkwargs={'suffix': 'List'}
),
# Dynamically generated list routes.
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.
DynamicDetailRoute(
url=r'^{prefix}/{lookup}/{methodname}{trailing_slash}$',
name='{basename}-{methodnamehyphen}',
initkwargs={}
),
]

其用意是通过调用_get_dynamic_routes内嵌方法,把routes列表中项作为模板,将list_routes和detail_routes中的项依次进行替换,最终得到一个Route对象的列表(Route是一个namedtuple,包含如url、mapping、name等项)

[
Route(url='^{prefix}{trailing_slash}$', mapping={'get': 'list', 'post': 'create'}, name='{basename}-list', initkwargs={'suffix': 'List'}), Route(url='^{prefix}/getstocklist{trailing_slash}$', mapping={'get': 'get_stocks'}, name='{basename}-getstocklist', initkwargs={}), Route(url='^{prefix}/{lookup}{trailing_slash}$', mapping={'get': 'retrieve', 'patch': 'partial_update', 'put': 'update', 'delete': 'destroy'}, name='{basename}-detail', initkwargs={'suffix': 'Instance'})
]

get_route方法的功能到此结束了,回到get_urls方法中

    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:] view = viewset.as_view(mapping, **route.initkwargs)
name = route.name.format(basename=basename)
ret.append(url(regex, view, name=name)) return ret

这里的核心点是viewset的as_view方法,是不是很熟悉,Django中基于类的视图注册路由时也是调用的ClassView的as_view方法。as_view方法是在父类ViewSetMixin中定义的,传入的action参数是httpmethod与methodname的映射一个字典,如 {'get': 'get_stocks'}

    def as_view(cls, actions=None, **initkwargs):
"""
省略若干...
""" def view(request, *args, **kwargs):
self = cls(**initkwargs)
# We also store the mapping of request methods to actions,
# so that we can later set the action attribute.
# eg. `self.action = 'list'` on an incoming GET request.
self.action_map = actions # Bind methods to actions
# This is the bit that's different to a standard view
for method, action in actions.items():
handler = getattr(self, action)
setattr(self, method, handler) # And continue as usual
return self.dispatch(request, *args, **kwargs) view.cls = cls
view.initkwargs = initkwargs
view.suffix = initkwargs.get('suffix', None)
view.actions = actions
return csrf_exempt(view)

核心点是这个view方法以及dispatch方法,view方法中遍历anctions字典,通过setattr设置名称为httpmethod的属性,属性值为methodname所对应的方法。在dispathch方法中,就可通过getattr获取到httpmethod所对应的handler

 def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate? try:
self.initial(request, *args, **kwargs) # Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed response = handler(request, *args, **kwargs) except Exception as exc:
response = self.handle_exception(exc) self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response

get_urls方法最终返回的结果是url(regex, view, name=name)的列表,这也就是ViewSet帮我们创建的自定义路由,其实现与我们在urls.py注册路由是一样的。url方法得到的是RegexURLPattern对象

[
<RegexURLPattern appstock-list ^stock/$>, <RegexURLPattern appstock-getstocklist ^stock/getstocklist/$>, <RegexURLPattern appstock-detail ^stock/(?P<pk>[^/.]+)/$>
]

最后

访问 http://127.0.0.1:8000/stock/getstocklist/,请求就会交由StockViewSet中的get_stocks方法进行处理了。

整个过程大致就是这样了。

Django REST framework使用ViewSets的自定义路由实现过程的更多相关文章

  1. Django Rest Framework(分页、视图、路由、渲染器)

    一.分页 试问如果当数据量特别大的时候,你是怎么解决分页的? 方式a.记录当前访问页数的数据id 方式b.最多显示120页等 方式c.只显示上一页,下一页,不让选择页码,对页码进行加密 1.基于lim ...

  2. Django REST framework基础:视图和路由

    DRF中的Request 在Django REST Framework中内置的Request类扩展了Django中的Request类,实现了很多方便的功能--如请求数据解析和认证等. 比如,区别于Dj ...

  3. Django Rest Framework源码剖析(八)-----视图与路由

    一.简介 django rest framework 给我们带来了很多组件,除了认证.权限.序列化...其中一个重要组件就是视图,一般视图是和路由配合使用,这种方式给我们提供了更灵活的使用方法,对于使 ...

  4. Django Rest Framework 视图和路由

    Django Rest Framework 视图和路由   DRF的视图 APIView 我们django中写CBV的时候继承的是View,rest_framework继承的是APIView,那么他们 ...

  5. 03 Django REST Framework 视图和路由

    01-DRF中的request 在Django REST Framework中内置的Request类扩展了Django中的Request类,实现了很多方便的功能--如请求数据解析和认证等. 比如,区别 ...

  6. Django REST framework 自定义(认证、权限、访问频率)组件

    本篇随笔在 "Django REST framework 初识" 基础上扩展 一.认证组件 # models.py class Account(models.Model): &qu ...

  7. Django(6)自定义路由转换器

    自定义路径转换器 有时候上面的内置的url转换器并不能满足我们的需求,因此django给我们提供了一个接口可以让我们自己定义自己的url转换器 django内置的路径转换器源码解析 在我们自定义路由转 ...

  8. python 全栈开发,Day96(Django REST framework 视图,django logging配置,django-debug-toolbar使用指南)

    昨日内容回顾 1. Serializer(序列化) 1. ORM对应的query_set和ORM对象转换成JSON格式的数据 1. 在序列化类中定义自定义的字段:SerializerMethodFie ...

  9. django rest framework restful 规范

    内容回顾: . django请求生命周期 -> 执行遵循wsgi协议的模块(socket服务端) -> 中间件(路由匹配) -> 视图函数(业务处理:ORM.模板渲染) -> ...

随机推荐

  1. 导入礼包时,遇到file_get_contents读取的文本,去除空格、换行等的方法

    解决方法:正则替换: 代码:$gift_code_str = preg_replace('/((\s)*(\n)+(\s)*) /i ',',',file_get_contents($gift_cod ...

  2. 小谈JavaScript中this的用法

    "this"关键字是JavaScript中广泛应用的一种特性,但它经常也是这门语言中最容易混淆和误解的特性.那么"this"的实际意义是什么?它是如何求值的? ...

  3. 《JAVASCRIPT高级程序设计》表单基础知识和文本框脚本

    在HTML中,表单是由<form>元素来表示,在javascript中,表单对应的是HTMLFormElement类型,它具有一些独有的属性和方法: 一.表单基础知识 1.取得表单的方式 ...

  4. [html5] 学习笔记-Canvas标签的使用

    Canvas通过JavaScript来绘制2D图形.Canvas是逐像素渲染的.在Canvas中,一旦图形被绘制完成,它就不会继续得到浏览器的关注.如果其位置发生变化,那么整个场景也需要重新绘制,包括 ...

  5. Exiting the Matrix: Introducing Metasploit's Hardware Bridge

    Metasploit is an amazing tool. You can use it to maneuver through vast networks, pivoting through se ...

  6. C++编程练习(16)----“排序算法 之 快速排序“

    快速排序 基本思想: 通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的. 算法介绍: 设要排序的 ...

  7. gevent拾遗

      在前文已经介绍过了gevent的调度流程,本文介绍gevent一些重要的模块,包括Timeout,Event\AsynResult, Semphore, socket patch,这些模块都涉及当 ...

  8. matlab中小技巧

    关于matlab中可能遇到的小知识点 一.字符串的比较 不能使用“==”,需要使用函数strcmp() %matlab中字符串的比较 %字符串比较要用strcmp.相同则返回1,不相同则返回0. cl ...

  9. enote笔记语言(3)(ver0.2)

    what&why(why not)&how&when&where&which:紫色,象征着神秘而又潜蕴着强大的力量,故取紫色. key&keyword: ...

  10. 从头开始搭建一个Spring boot+RabbitMQ环境

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...