欢迎访问我的个人网站: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. C#获取当前时间详解

    [转]C#获取当前日期时间(转)  http://blog.163.com/ljq086@126/blog/static/549639712010112921658843/ 我们可以通过使用DataT ...

  2. SpringMVC源码情操陶冶-View视图渲染

    本节简单分析View视图对象的render方法 View接口 最重要的就是render()方法,具体源码如下 /** * Render the view given the specified mod ...

  3. Unity编辑器重写Inspector面板,面板中编辑的数据不触发场景发生变化的问题。

    今天开始协助主程一起制作新框架.主程让我写关于新版UI框架注册UI预制体用的快捷编辑器. 现学现写,总算完成了. 可以直接把选中的预制体添加到UIController的数组中,期间涉及到改变大小.所以 ...

  4. JavaScript获取鼠标位置的三种方法

    在一些DOM操作中我们经常会跟元素的位置打交道,鼠标交互式一个经常用到的方面,令人失望的是不同的游览器下会有不同的结果甚至是有的游览器下没结果,这篇文章就鼠标点击位置坐标获取做一些简单的总结. 获取鼠 ...

  5. STS 配置tomcat以后,无法访问

    问题 今天在新环境下安装开发环境STS,使用的的3.9,如下图 安装完成之后配置Tomcat,在STS启动Tocat后,在浏览器输入:http://localhost:8080/ 发现无法访问... ...

  6. (转)WebSphere MQ基础命令

    --查看MQ版本-- dspmqver --查看队列状态--dspmq --创建队列管理器--crtmqm -q ECIS_QM --删除队列管理器--dltmqm ECIS_QM --启动队列管理器 ...

  7. 初探JavaScript的截图实现

    最近参与了网易炉石盒子的相关页面开发,在做卡组分享页(地址:炉石盒子卡组分享),有个需求:用户可以把这个卡组以图片的形式分享给好友.最初的的做法是使用服务器把该页面转换成图片,然后把图片地址返回给前端 ...

  8. 关于Oracle连接超时的问题

    测试环境ORACLE 11.2.0. 如果连接池设置单个连接闲置时间大于数据库连接超时时间,则连接池中的连接发出数据请求时会出现Connect timeout occurred错误, 这是由于连接超时 ...

  9. mk框架,一个基于react、nodejs全栈框架

    在这个前端技术爆炸的时代,不自己写套开源框架出门都不好意思跟别人打招呼,作为一个前端领域的小学生,去年年初接触了react,之后一发不可收拾爱上了它,近期重构了自己去年开源的一个项目,废话到此结束句号 ...

  10. HDU1002——大整数加法

    题目: I have a very simple problem for you. Given two integers A and B, your job is to calculate the S ...