Django编写RESTful API(六):ViewSets和Routers
欢迎访问我的个人网站:www.comingnext.cn
前言
在本系列的文章中,我在第一篇和第二篇文章中写的编写Django视图时,使用的都是基于函数的方法,并且每个视图函数之前都会加一个django-rest-framework带的装饰器@api_view。然后在第三篇文章,我们就开始把基于函数的视图改成了基于类的视图,然后发现这样做视图部分减少了很多代码量。
在这一篇文章中,我要介绍的是另一种基于类的视图的写法,它的抽象程度更高,也可以说是代码量又减少了。OK,废话不多说,先进入主题~
使用ViewSets重构视图
先介绍一下这个ViewSets。ViewSets,翻译过来可以说是视图集,也就是几个视图的集合。
拿本项目为例子,我们之前查看所有用户列表就要写一个视图类UserList,并在urls.py中为其设置一个模式然后as_view使用它,然后要看单个用户的详情页就要再写一个UserDetail视图类并再在添加一个url模式。同时注意到这两个视图类都是继承的generics.XXXAPIView。而使用ViewSets我们就可以把UserList和UserDetail合并成UserViewSet视图类,并且继承的类改为viewsets.ReadOnlyModelViewSet,这样就是一个视图集了。
还是有点懵逼?没事,下面看看代码。编辑snippets/view.py,导入viewsets并使用UserViewSet来替换掉UserList和UserDetail:
- from rest_framework import viewsets
- class UserViewSet(viewsets.ReadOnlyModelViewSet):
- """
- viewset自动提供了list和detail动作
- """
- queryset = User.objects.all()
- serializer_class = UserSerializer
这里面的queryset和serializer_class的值还是和原来一样。因为关于User的API都是只读的,所以我们继承了一个ReadOnlyModelViewSet类,这样就把原先的两个视图类集合起来了。原本类里面的:
- queryset = User.objects.all()
- serializer_class = UserSerializer
这部分属于重复代码,所以通过视图集来实现视图类我们的代码量确实减少了,更加简洁。
ViewSet类与View类其实几乎是相同的,但提供的是read或update这些操作,而不是get或put 等HTTP动作。同时,ViewSet为我们提供了默认的URL结构, 使得我们能更专注于API本身。
上面这段话呢,是官方文档里面说的,想就这样看看就算了来理解也行,不过如果我们看一下源码也许能理解的更好。因为我用的是PyCharm,所以查看源码很方便,按住CTRL键然后鼠标点击一下就会自动跳转了,首先查看一下ReadOnlyModelViewSet,发现它是这样的:
- class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
- mixins.ListModelMixin,
- GenericViewSet):
- """
- A viewset that provides default `list()` and `retrieve()` actions.
- """
- pass
发现原来有用到之前说的mixins,所以刚才才说ViewSet类与View类其实几乎是相同的。但是这里多了一个GenericViewSet类是新的内容,继续CTRL点击查看其代码,发现它内部只是一个pass然后就没有其他的操作了,但是可以继续查看其父类ViewSetMixin的源码来了解ViewSets,然后就可以看到这个ViewSetMixin其实重写了as_view方法:
- @classonlymethod
- def as_view(cls, actions=None, **initkwargs):
- """
- Because of the way class based views create a closure around the
- instantiated view, we need to totally reimplement `.as_view`,
- and slightly modify the view function that is created and returned.
- """
- ...
我们平时使用视图类的时候,编写urls.py时,就一个XXX.as_view(),现在使用ViewSets,需要传入参数,大概像下面这样的:
- UserViewSet.as_view({'get': 'list'})
之后url就配置好了,也就是上面说的ViewSet为我们提供了默认的URL结构。当然了,这个还不是完整的url模式,稍后补全。
刚才把User的两个视图类合并成视图集了,那么Snippet的几个视图类操作上也是差不多的。用视图集SnippetViewSet代替SnippetList, SnippetDetail 和 SnippetHighlight这三个视图类:
- from rest_framework.decorators import detail_route
- class SnippetViewSet(viewsets.ModelViewSet):
- """
- viewset自动提供了`list`, `create`, `retrieve`,
- `update` 和 `destroy` 动作.
- 同时我们手动增加一个额外的'highlight'动作用于查看高亮的代码段
- """
- queryset = Snippet.objects.all()
- serializer_class = SnippetSerializer
- permission_classes = (permissions.IsAuthenticatedOrReadOnly,
- IsOwnerOrReadOnly,)
- @detail_route(renderer_classes=[renderers.StaticHTMLRenderer])
- def highlight(self, request, *args, **kwargs):
- snippet = self.get_object()
- return Response(snippet.highlighted)
- def perform_create(self, serializer):
- serializer.save(owner=self.request.user)
因为查看highlight不像其他动作那样,django-rest-framework并没有替我们封装好,所以我们需要自己添加这个额外的动作,要记得在方法前面加上装饰器@detail_route,这个装饰器就是用来创建自定义的动作,当然我们的自定义动作不可以是create/update/delete这些标准的,否则会有冲突。
还有一点,用@detail_route装饰器定义的动作默认是GET请求,需要其他的请求方式可以传入methods参数给这个装饰器。同样的,默认情况下,自定义操作的URL取决于方法名称本身。如果要更改url应该构造的方式,可以将url_path作为decorator的关键字参数。
最后还要注意继承的类是ModelViewSet和刚才的也有点不同,为什么换成这个,也可以看看源码能略知一二:
- class ModelViewSet(mixins.CreateModelMixin,
- mixins.RetrieveModelMixin,
- mixins.UpdateModelMixin,
- mixins.DestroyModelMixin,
- mixins.ListModelMixin,
- GenericViewSet):
将ViewSets明确的绑定到URL
根据上面所说的,每个视图集的url模式都需要我们在as_view中传入参数,把snippets/urls.py的代码换成下面的:
- from django.conf.urls import url,include
- from snippets.views import SnippetViewSet, UserViewSet, api_root
- from rest_framework import renderers
- from rest_framework.urlpatterns import format_suffix_patterns
- snippet_list = SnippetViewSet.as_view({
- 'get': 'list',
- 'post': 'create'
- })
- snippet_detail = SnippetViewSet.as_view({
- 'get': 'retrieve',
- 'put': 'update',
- 'patch': 'partial_update',
- 'delete': 'destroy'
- })
- snippet_highlight = SnippetViewSet.as_view({
- 'get': 'highlight'
- }, renderer_classes=[renderers.StaticHTMLRenderer])
- user_list = UserViewSet.as_view({
- 'get': 'list'
- })
- user_detail = UserViewSet.as_view({
- 'get': 'retrieve'
- })
- urlpatterns = format_suffix_patterns([
- url(r'^$', api_root),
- url(r'^snippets/$', snippet_list, name='snippet-list'),
- url(r'^snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'),
- url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
- url(r'^users/$', user_list, name='user-list'),
- url(r'^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail')
- ])
OK,到了这里对视图的改造已经完成了,可以启动服务器测试一下,我们的项目功能还是和之前的一样的。
使用Routers
不过看到urls.py的代码,我们可能会发现一个问题,就是我们的视图类代码简洁了变少了,但是url.py的代码量好像多了啊,要绑定那么多动作,这样算起来好像也没多大提升?
确实是这样。但是我们这可是在用python开发啊,当然是能短则短了,没错,django-rest-framework的作者也是这么想的,所以我们又有现成的轮子可以使用了。这个轮子就是本文的另一个主角——Routers。用起来也是简单粗暴,重写urls.py:
- from django.conf.urls import url, include
- from snippets import views
- from rest_framework.routers import DefaultRouter
- # Create a router and register our viewsets with it.
- router = DefaultRouter()
- router.register(r'snippets', views.SnippetViewSet)
- router.register(r'users', views.UserViewSet)
- # The API URLs are now determined automatically by the router.
- # Additionally, we include the login URLs for the browsable API.
- urlpatterns = [
- url(r'^', include(router.urls)),
- ]
这样就搞定了,代码少了很多,连原来用来设置后缀的下面这行代码都不需要了。
- urlpatterns = format_suffix_patterns(urlpatterns)
- 而且这个DefaultRouter 类还会自动帮我们创建API根视图,也就是说view.py中的api_root方法也可以删除掉了。
额...这个Routers帮我们做的事情真是有点多啊。。不过这也就是我为什么在文章的前言里面说使用ViewSets会比原本的视图更抽象的原因。
拿过来用是会了,但是这里面发生了什么我们完全不知道啊,比如说API后缀去哪了?上面我们写的:
- snippet_list = SnippetViewSet.as_view({
- 'get': 'list',
- 'post': 'create'
- })
这些绑定全都自动生成了?这些确实都是DefaultRouter 帮我们做好了,怎么做的,我们还是可以看一下源码了解一下大概的过程。首先就是register方法,我们绑定了那么多动作它两行就搞定了,查看它的源码,发现它是BaseRouter类下的一个方法:
- 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))
- ...
- @property
- def urls(self):
- if not hasattr(self, '_urls'):
- self._urls = self.get_urls()
- return self._urls
改方法根据传进来的参数生成url端点,也就是/snippets和/users,然后存到registry列表中。并且这个类的最后是一个可以当属性用的方法urls,而这个方法里面又调用了get_urls()来生成所有的url模式,当然这个get_urls()被子类SimpleRouter和子子类DefaultRouter重写了。SimpleRouter中的get_urls()实现了生成是5个url模式,也就是原本的:
- url(r'^snippets/$',snippet_list,name='snippet-list'),
- url(r'^snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'),
- url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
- url(r'^users/$', user_list, name='user-list'),
- url(r'^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail')
而DefaultRouter中的get_urls()中则生成了api_root的url模式,同时还为这些url模式加了格式后缀,所以我们自己不会用到format_suffix_patterns这个东西。
总结
当然了,并不一定要使用ViewSets的视图代替View,两者各有好处ViewSets节省了很多代码并且url模式也不用我们自己设置了,但是也会带来一些不确定性,自动化的效果有时候可能和你预想的不太一样,所以想要选择哪种方法看你自己喜欢。
好了,关于ViewSets和Routers的介绍就到这里了,API的功能和之前的一样,所以这里就不做展示了。
Django编写RESTful API(六):ViewSets和Routers的更多相关文章
- Django编写RESTful API(一):序列化
欢迎访问我的个人网站:www.comingnext.cn 关于RESTful API 现在,在开发的过程中,我们经常会听到前后端分离这个技术名词,顾名思义,就是前台的开发和后台的开发分离开.这个技术方 ...
- Django编写RESTful API(四):认证和权限
欢迎访问我的个人网站:www.comingnext.cn 前言: 按照前面几篇文章里那样做,使用Django编写RESTful API的基本功能已经像模像样了.我们可以通过不同的URL访问到不同的资源 ...
- Spring Boot 2.x 编写 RESTful API (六) 事务
用Spring Boot编写RESTful API 学习笔记 Transactional 判定顺序 propagation isolation 脏读 不可重复读 幻读 不可重复读是指记录不同 (upd ...
- Django编写RESTful API(二):请求和响应
欢迎访问我的个人网站:www.comingnext.cn 前言 在上一篇文章,已经实现了访问指定URL就返回了指定的数据,这也体现了RESTful API的一个理念,每一个URL代表着一个资源.当然我 ...
- Django编写RESTful API(五):添加超链接提高模型间的关联性
前言 在第四篇中,加入了用户模型,以及相关的认证和权限的功能.但是我们在使用的时候,会发现在访问http://127.0.0.1:8000/users/时看到的用户列表,不能够直接点击某个链接然后查看 ...
- Django编写RESTful API(三):基于类的视图
欢迎访问我的个人网站:www.comingnext.cn 前言 在上一篇文章中,主要讲的是请求和响应,项目里面views.py中的视图函数都是基于函数的,并且我们介绍了@api_view这个很有用的装 ...
- 利用 Django REST framework 编写 RESTful API
利用 Django REST framework 编写 RESTful API Updateat 2015/12/3: 增加 filter 最近在玩 Django,不得不说 rest_framewor ...
- python 全栈开发,Day95(RESTful API介绍,基于Django实现RESTful API,DRF 序列化)
昨日内容回顾 1. rest framework serializer(序列化)的简单使用 QuerySet([ obj, obj, obj]) --> JSON格式数据 0. 安装和导入: p ...
- RESTful规范与django编写restful接口
一.什么是RESTful规范 ①REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移” ②REST从资 ...
随机推荐
- Result Maps collection does not contain value for...
出现上述错误 主要是因为你的select标签内部的resultMap属性指向的不正确 在sql文件中只要有一个resultMap或resultType属性指向错误,则在这个文件中其余正确的语句也不能执 ...
- Python 获取当前路径的方法
Python2.7 中获取路径的各种方法 sys.path 模块搜索路径的字符串列表.由环境变量PYTHONPATH初始化得到. sys.path[0]是调用Python解释器的当前脚本所在的目录. ...
- 支付宝分库分表中间件--zdal简介
中间件, 如果仅仅作为一名用户的话, 主要关注一下如何使用即可, 大多数情况下也就是配置. 下面简单的介绍一下支付宝的分库分表中间件--->zdal在web项目中的配置. 1, 在网上查阅相关资 ...
- 关于javascript在OJ系统上编程的注意事项
① 牛客网输入流: var line=readline().split(' '); ② 赛码网输入流: var line=read_line().split(' '); ③ 输出流: print(); ...
- 37. leetcode 108. Convert Sorted Array to Binary Search Tree
108. Convert Sorted Array to Binary Search Tree 思路:利用一个有序数组构建一个平衡二叉排序树.直接递归构建,取中间的元素为根节点,然后分别构建左子树和右 ...
- 虚拟机下克隆3个centos系统并配置IP访问网络(转载)
此文是保证linux系统能够上网 先查看本机的IP 打开虚拟机,更改虚拟机为桥接方式 在第一个虚拟机上打开终端,在命令行输入setup,选择NETWORK CONFIGRATION 回车后, 配置IP ...
- Redis从单机到集群,一步步教你环境部署以及使用
Redis作为缓存系统来说还是很有价值的,在大数据方向里,也是需要有缓存系统的.一般可以考虑tachyon或者redis,由于redis安装以及使用更简单,所以还是优先考虑了它.那么在一些场景下为了保 ...
- spring boot admin
这里记录一个spring cloud的模板,有的模块spring cloud eureka + spring boot admin + spring cloud zuul + 一个普通spring c ...
- jmeter - 关联之正则表达式提取器
如果有这样的情况:一个完整的操作流程,需要先完成某个操作,获得某个值或数据信息,然后才能进行下一步的操作(也就是常说的关联/将上一个请求的响应结果作为下一个请求的参数): 在jmeter中,可以利用正 ...
- shell 编程之 if...else case...esac
shell的条件判断语句有三种 if...fi 语句 if...else...fi 语句 if...elif...fi 语句 例子: a=10; b=20; if [ $a -gt %b ] t ...