欢迎访问我的个人网站: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的更多相关文章

  1. Django编写RESTful API(一):序列化

    欢迎访问我的个人网站:www.comingnext.cn 关于RESTful API 现在,在开发的过程中,我们经常会听到前后端分离这个技术名词,顾名思义,就是前台的开发和后台的开发分离开.这个技术方 ...

  2. Django编写RESTful API(四):认证和权限

    欢迎访问我的个人网站:www.comingnext.cn 前言: 按照前面几篇文章里那样做,使用Django编写RESTful API的基本功能已经像模像样了.我们可以通过不同的URL访问到不同的资源 ...

  3. Spring Boot 2.x 编写 RESTful API (六) 事务

    用Spring Boot编写RESTful API 学习笔记 Transactional 判定顺序 propagation isolation 脏读 不可重复读 幻读 不可重复读是指记录不同 (upd ...

  4. Django编写RESTful API(二):请求和响应

    欢迎访问我的个人网站:www.comingnext.cn 前言 在上一篇文章,已经实现了访问指定URL就返回了指定的数据,这也体现了RESTful API的一个理念,每一个URL代表着一个资源.当然我 ...

  5. Django编写RESTful API(五):添加超链接提高模型间的关联性

    前言 在第四篇中,加入了用户模型,以及相关的认证和权限的功能.但是我们在使用的时候,会发现在访问http://127.0.0.1:8000/users/时看到的用户列表,不能够直接点击某个链接然后查看 ...

  6. Django编写RESTful API(三):基于类的视图

    欢迎访问我的个人网站:www.comingnext.cn 前言 在上一篇文章中,主要讲的是请求和响应,项目里面views.py中的视图函数都是基于函数的,并且我们介绍了@api_view这个很有用的装 ...

  7. 利用 Django REST framework 编写 RESTful API

    利用 Django REST framework 编写 RESTful API Updateat 2015/12/3: 增加 filter 最近在玩 Django,不得不说 rest_framewor ...

  8. python 全栈开发,Day95(RESTful API介绍,基于Django实现RESTful API,DRF 序列化)

    昨日内容回顾 1. rest framework serializer(序列化)的简单使用 QuerySet([ obj, obj, obj]) --> JSON格式数据 0. 安装和导入: p ...

  9. RESTful规范与django编写restful接口

    一.什么是RESTful规范 ①REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移” ②REST从资 ...

随机推荐

  1. 常用PHP函数

    md5_file() 生成md5 $zip = new \ZipArchive(); if($zip->open($savepath.$key) === TRUE){ $zip ->ext ...

  2. 【NOIP模拟】Grid(字符串哈希)

    题目背景 SOURCE:NOIP2016-RZZ-1 T3 题目描述 有一个 2×N 的矩阵,矩阵的每个位置上都是一个英文小写字符. 现在需要从某一个位置开始,每次可以移动到一个没有到过的相邻位置,即 ...

  3. AtCoder Beginner Contest 068

    A - ABCxxx 题意: 给出n,输出“ABCn”就可以了,纯水题. B - Break Number 题意: 给出n,找出从1到n的闭区间内能够被2整除最多次的数. 思路: 直接模拟. 代码: ...

  4. css阴影,边框,渐变,背景

    一.box-shadow: 参数1,参数2 阴影位置偏移量 参数3 模糊半径 参数4 扩展半径 负数 0 默认值 正数 参数5 阴影的颜色 参数6 设置内阴影 inset 默认是外阴影 多个阴影使用, ...

  5. 暑假学习计划:Day_1.JSP&Servlet&Tocat 环境搭建到基础的认识。

    1.了解JSP和Servlet(百度了解即可). 2.了解B/S和C/S.分别是  浏览器/服务器  和  客户端/服务器. 其中 B/S 被称为瘦模式(主流模式). 3.了解并下载Tomcat服务器 ...

  6. Uva 122 树的层次遍历 Trees on the level lrj白书 p149

    是否可以把树上结点的编号,然后把二叉树存储在数组中呢?很遗憾如果结点在一条链上,那将是2^256个结点 所以需要采用动态结构 首先要读取结点,建立二叉树addnode()+read_input()承担 ...

  7. (1)xcode基本设置和控制器等介绍

    1.在IOS应用程序中,如果没有对storyBoard进和设置它的界面是非常大,有时候如果把元素放在右边会出现运行程序时超出显示界面而不显示的问题.为了解决这个问题我们通常会在用模拟器设置调试界面的时 ...

  8. Java 架构师之路(1)

    本人也是coding很多年,虽然很失败,但也总算有点失败的心得,不过我在中国,大多数程序员都是像我一样,在一直走着弯路.如果想成为一个架构师,就必须走正确的路,否则离目标越来越远,正在辛苦工作的程序员 ...

  9. crm踩坑记(二)

    Linux tmux 如何查看 tmux如何进行滚动呢? prefix + [, prefix为tmux的前置动作,默认是ctrl + b. 使用方向键或者pageUp来进行翻页. q可以退出滚动模式 ...

  10. 不安分的this

    不安分的this 前言:关于javascript中的this,上网一搜一大片的文章.惊! 而我个人认为要想分清this,就有必要先搞清楚“对象”. 目录: 一.函数对象的认识 二.this 一.函数对 ...