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从资 ...
随机推荐
- jrebel的安装配置
1,在线安装jrebel[也可以离线,不过在线可以直接支持maven] 2,然后按照如下步骤 http://idea.goxz.gq/ilanyu 242367666@qq.com 随意邮箱 然 ...
- centos7 ssh免密码登录
单机测试 1,设置/etc/hosts 添加主机及ip 2,修改/etc/sysconfig/network NETWORKING=yesNETWORING_IPV6=yesHOSTNAME=mast ...
- tensorflow elu函数应用
1.elu函数 图像: 2.tensorflow elu应用 import tensorflow as tf input=tf.constant([0,-1,2,-3],dtype=tf.float3 ...
- 【SignalR学习系列】5. SignalR WPF程序
首先创建 WPF Server 端,新建一个 WPF 项目 安装 Nuget 包 替换 MainWindows 的Xaml代码 <Window x:Class="WPFServer.M ...
- [补档][HNOI 2008]GT考试
[HNOI 2008]GT考试 题目 阿申准备报名参加GT考试,准考证号为N位数X1X2....Xn(0<=Xi<=9),他不希望准考证号上出现不吉利的数字. 他的不吉利数学A1A2... ...
- 双向循环链表(C语言描述)(四)
下面以一个电子英汉词典程序(以下简称电子词典)为例,应用双向循环链表.分离数据结构,可以使逻辑代码独立于数据结构操作代码,程序结构更清晰,代码更简洁:电子词典的增.删.查.改操作分别对应于链表的插入. ...
- Spring源码解析——如何阅读源码
最近没什么实质性的工作,正好有点时间,就想学学别人的代码.也看过一点源码,算是有了点阅读的经验,于是下定决心看下spring这种大型的项目的源码,学学它的设计思想. 手码不易,转载请注明:xingoo ...
- 脱壳练习之bitarts 5.0
运行界面 一开始不是PUSHAD,这里我们跟到PUSHAD指令处,按F7执行该指令,接着在寄存器窗口中定位到ESP寄存器的值,在其上面单击鼠标右键选择-Follow in Dump. 仅允许非商业转载 ...
- 逻辑回归的实现(LogicalRegression)
1.背景知识 在刚刚结束的天猫大数据s1比赛中,逻辑回归是大家都普遍使用且效果不错的一种算法. (1)回归 先来说说什么是回归,比如说我们有两类数据,各有50十个点 ...
- angular或者js如何确定选中ul中的哪几个li
刚来新公司接到新的需求做一个知识库页面 红色的是单选 蓝色的是多选 这些都是需要传递到后台的 开始不知道如何解决 下班后在家想到一个很巧妙的办法 不多说上代码 箭头所指就是在li里写 ...