在这一篇文章中,要介绍的是另一种基于类的视图的写法,它的抽象程度更高,也可以说是代码量又减少了。
使用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 |
这里面的queryset和serializer_class的值还是和原来一样。因为关于User的API都是只读的,所以我们继承了一个ReadOnlyModelViewSet类,这样就把原先的两个视图类集合起来了。原本类里面的:
queryset = User.objects.all() |
这部分属于重复代码,所以通过视图集来实现视图类我们的代码量确实减少了,更加简洁。
ViewSet类与View类其实几乎是相同的,但提供的是read或update这些操作,而不是get或put 等HTTP动作。同时,ViewSet为我们提供了默认的URL结构, 使得我们能更专注于API本身。
上面这段话呢,是官方文档里面说的,想就这样看看就算了来理解也行,不过如果我们看一下源码也许能理解的更好。因为我用的是PyCharm,所以查看源码很方便,按住CTRL键然后鼠标点击一下就会自动跳转了,首先查看一下ReadOnlyModelViewSet,发现它是这样的:
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin, |
发现原来有用到之前说的mixins,所以刚才才说ViewSet类与View类其实几乎是相同的。但是这里多了一个GenericViewSet类是新的内容,继续CTRL点击查看其代码,发现它内部只是一个pass然后就没有其他的操作了,但是可以继续查看其父类ViewSetMixin的源码来了解ViewSets,然后就可以看到这个ViewSetMixin其实重写了as_view方法:
@classonlymethod |
我们平时使用视图类的时候,编写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 |
因为查看highlight不像其他动作那样,django-rest-framework并没有替我们封装好,所以我们需要自己添加这个额外的动作,要记得在方法前面加上装饰器@detail_route,这个装饰器就是用来创建自定义的动作,当然我们的自定义动作不可以是create/update/delete这些标准的,否则会有冲突。
还有一点,用@detail_route装饰器定义的动作默认是GET请求,需要其他的请求方式可以传入methods参数给这个装饰器。同样的,默认情况下,自定义操作的URL取决于方法名称本身。如果要更改url应该构造的方式,可以将url_path作为decorator的关键字参数。
最后还要注意继承的类是ModelViewSet和刚才的也有点不同,为什么换成这个,也可以看看源码能略知一二:
class ModelViewSet(mixins.CreateModelMixin, |
将ViewSets明确的绑定到URL
根据上面所说的,每个视图集的url模式都需要我们在as_view中传入参数,把snippets/urls.py
的代码换成下面的:
from django.conf.urls import url,include |
OK,到了这里对视图的改造已经完成了,可以启动服务器测试一下,我们的项目功能还是和之前的一样的。
使用Routers
不过看到urls.py
的代码,我们可能会发现一个问题,就是我们的视图类代码简洁了变少了,但是urls.py
的代码量好像多了啊,要绑定那么多动作,这样算起来好像也没多大提升?
确实是这样。但是我们这可是在用python开发啊,当然是能短则短了,没错,django-rest-framework的作者也是这么想的,所以我们又有现成的轮子可以使用了。这个轮子就是本文的另一个主角——Routers。用起来也是简单粗暴,重写urls.py
:
from django.conf.urls import url, include |
这样就搞定了,代码少了很多,连原来用来设置后缀的下面这行代码都不需要了。
urlpatterns = format_suffix_patterns(urlpatterns) |
而且这个DefaultRouter 类还会自动帮我们创建API根视图,也就是说view.py
中的api_root方法也可以删除掉了。
额…这个Routers帮我们做的事情真是有点多啊。。不过这也就是我为什么在文章的前言里面说使用ViewSets会比原本的视图更抽象的原因。
拿过来用是会了,但是这里面发生了什么我们完全不知道啊,比如说API后缀去哪了?上面我们写的:
snippet_list = SnippetViewSet.as_view({ |
这些绑定全都自动生成了?这些确实都是DefaultRouter 帮我们做好了,怎么做的,我们还是可以看一下源码了解一下大概的过程。首先就是register方法,我们绑定了那么多动作它两行就搞定了,查看它的源码,发现它是BaseRouter类下的一个方法:
class BaseRouter(object): |
改方法根据传进来的参数生成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'), |
而DefaultRouter中的get_urls()中则生成了api_root的url模式,同时还为这些url模式加了格式后缀,所以我们自己不会用到format_suffix_patterns这个东西。
当然了,并不一定要使用ViewSets的视图代替View,两者各有好处ViewSets节省了很多代码并且url模式也不用我们自己设置了,但是也会带来一些不确定性,自动化的效果有时候可能和你预想的不太一样,所以想要选择哪种方法看你自己喜欢。
声明
本篇文章来自ziv
的博客,仅作整理学习分享。
点击这里访问原文。