在DRF官方教程的学习过程中,一个很明显的感受是框架在不断地进行封装,我们自己写框架/工具/脚本/平台也可以模仿模仿,先完成底层代码,再做多层封装,让使用者很容易就上手操作。本文是教程的最后一篇,介绍ViewSets和Routers。

ViewSets

先看看之前在给User模型创建Endpoint时,我们新增的2个视图:

class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer

DRF提供了rest_framework.viewsets

可以把它们合成一个视图。

set是集合的意思,ViewSets就是视图集合。

ReadOnlyModelViewSet

我们先使用ReadOnlyModelViewSetUserListUserDetail视图合并成一个UserViewSet

from rest_framework import viewsets

class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""
This viewset automatically provides `list` and `retrieve` actions.
"""
queryset = User.objects.all()
serializer_class = UserSerializer

viewsets.ReadOnlyModelViewSet是只读视图集合,源码如下:

class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `list()` and `retrieve()` actions.
"""
pass

它继承了mixins.RetrieveModelMixinmixins.ListModelMixinGenericViewSet

mixins.RetrieveModelMixin

class RetrieveModelMixin:
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)

mixins.ListModelMixin

class ListModelMixin:
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset()) page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

viewsets.GenericViewSet

class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
"""
The GenericViewSet class does not provide any actions by default,
but does include the base set of generic view behavior, such as
the `get_object` and `get_queryset` methods.
"""
pass

从源码可以看出,它提供了list()retrieve()2个方法,正好对应UserListUserDetail

ModelViewSet

再使用ModelViewSetSnippetListSnippetDetailSnippetHighlight视图合并成一个SnippetViewSet

from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import permissions class SnippetViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions. Additionally we also provide an extra `highlight` action.
"""
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly] @action(detail=True, 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)

@action装饰器用来创建除了create/update/delete以外的action,默认为GET请求,如果想改为POST请求,可以添加参数methods,它的源码如下:

def action(methods=None, detail=None, url_path=None, url_name=None, **kwargs):
"""
Mark a ViewSet method as a routable action. `@action`-decorated functions will be endowed with a `mapping` property,
a `MethodMapper` that can be used to add additional method-based behaviors
on the routed action. :param methods: A list of HTTP method names this action responds to.
Defaults to GET only.
:param detail: Required. Determines whether this action applies to
instance/detail requests or collection/list requests.
:param url_path: Define the URL segment for this action. Defaults to the
name of the method decorated.
:param url_name: Define the internal (`reverse`) URL name for this action.
Defaults to the name of the method decorated with underscores
replaced with dashes.
:param kwargs: Additional properties to set on the view. This can be used
to override viewset-level *_classes settings, equivalent to
how the `@renderer_classes` etc. decorators work for function-
based API views.
"""
methods = ['get'] if (methods is None) else methods
methods = [method.lower() for method in methods] assert detail is not None, (
"@action() missing required argument: 'detail'"
) # name and suffix are mutually exclusive
if 'name' in kwargs and 'suffix' in kwargs:
raise TypeError("`name` and `suffix` are mutually exclusive arguments.") def decorator(func):
func.mapping = MethodMapper(func, methods) func.detail = detail
func.url_path = url_path if url_path else func.__name__
func.url_name = url_name if url_name else func.__name__.replace('_', '-') # These kwargs will end up being passed to `ViewSet.as_view()` within
# the router, which eventually delegates to Django's CBV `View`,
# which assigns them as instance attributes for each request.
func.kwargs = kwargs # Set descriptive arguments for viewsets
if 'name' not in kwargs and 'suffix' not in kwargs:
func.kwargs['name'] = pretty_name(func.__name__)
func.kwargs['description'] = func.__doc__ or None return func
return decorator

viewsets.ModelViewSet包含了增删改查视图集合,源码如下:

class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass

它继承了mixins.CreateModelMixinmixins.RetrieveModelMixinmixins.UpdateModelMixinmixins.DestroyModelMixinmixins.ListModelMixinGenericViewSet

其中mixins.RetrieveModelMixinmixins.ListModelMixinGenericViewSet在前面已经介绍过了

mixins.CreateModelMixin

class CreateModelMixin:
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) def perform_create(self, serializer):
serializer.save() def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}

mixins.UpdateModelMixin

class UpdateModelMixin:
"""
Update a model instance.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer) if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {} return Response(serializer.data) def perform_update(self, serializer):
serializer.save() def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)

mixins.DestroyModelMixin

class DestroyModelMixin:
"""
Destroy a model instance.
"""
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT) def perform_destroy(self, instance):
instance.delete()

修改URLConf

视图改为ViewSet后,需要同时修改URLConf,编辑snippets/urls.py

from snippets.views import SnippetViewSet, UserViewSet, api_root
from rest_framework import renderers 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([
path('', api_root),
path('snippets/', snippet_list, name='snippet-list'),
path('snippets/<int:pk>/', snippet_detail, name='snippet-detail'),
path('snippets/<int:pk>/highlight/', snippet_highlight, name='snippet-highlight'),
path('users/', user_list, name='user-list'),
path('users/<int:pk>/', user_detail, name='user-detail')
])

注意,ViewSet需要绑定http methods和action,以指定请求方法对应的处理动作。

Routers

使用ViewSet的一大好处是可以自动配置路由,DRF提供了rest_framework.routers

我们重新编写snippets/urls.py看看是什么效果:

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from snippets import views # 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.
urlpatterns = [
path('', include(router.urls)),
]

真是厉害!这封装简直高级!而且DefaultRouter提供了API根目录的Endpoint,我们甚至可以把views.py中的api_root也删了。

东方说

本文是官方教程学习笔记的最后一篇了。可以从教程中了解到从函数视图到类视图,从View到ViewSet是如何封装起来的,实际运用中并不一定全部都得用类视图和ViewSet,还是需要平衡选择。后续还会有DRF系列的文章输出,不过作为阶段性成果,我把这7篇文章导成了PDF,和最后一版的源码,打包一起上传了,需要的话,可以在公众号后台回复【drf】获取:

参考资料:

https://www.django-rest-framework.org/tutorial/6-viewsets-and-routers/

DRF终极封装ViewSet和Router附教程PDF源码的更多相关文章

  1. HTML与CSS入门经典(第9版)试读 附随书源码 pdf扫描版​

    HTML与CSS入门经典(第9版)是经典畅销图书<HTML与CSS入门经典>的最新版本,与过去的版本相同,本书采用直观.循序渐进的方法,为读者讲解使用HTML5与CSS3设计.创建并维护世 ...

  2. Visual Studio 2015开发Qt项目实战经验分享(附项目示例源码)

    Visual Studio 2015开发Qt项目实战经验分享(附项目示例源码)    转 https://blog.csdn.net/lhl1124281072/article/details/800 ...

  3. LIRE教程之源码分析 | LIRE Tutorial of Analysis of the Source Code

    LIRE教程之源码分析 |LIRE Tutorial of Analysis of the Source Code 最近在做地理图像识别和检索的研究,发现了一个很好用的框架LIRE,遂研究了一通.网上 ...

  4. GEF入门实例_总结_01_教程、源码、开发环境准备

    一.前言 最近在学Eclipse插件开发,发现了一个比较好的GEF入门教程,并且按照教程上的操作,一步步实现了一个入门Demo,在此感谢作者的贡献. 好记性不如烂笔头,故决定总结一下这段时间的学习心得 ...

  5. NLP大赛冠军总结:300万知乎多标签文本分类任务(附深度学习源码)

    NLP大赛冠军总结:300万知乎多标签文本分类任务(附深度学习源码)       七月,酷暑难耐,认识的几位同学参加知乎看山杯,均取得不错的排名.当时天池AI医疗大赛初赛结束,官方正在为复赛进行平台调 ...

  6. web框架前言与学生数据库系统(附1.0源码)

    对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. import socket def f1(request): ""&quo ...

  7. Rest_framework Router 路由器(含SimplyRouter源码浅解)

    目录 Rest_framework Router 路由器 ViewSet结合Router,自动生成url. 将ViewSet注册到Router中,需要三个要素: 关于路由规则,细分有四类: rest_ ...

  8. 【绝对给力】Android开发免豆资料(教程+工具+源码)地址汇总

    教程下载: [免费]android界面效果全汇总.pdf http://down.51cto.com/data/209179 Android终极开发教程[pdf高清版] http://down.51c ...

  9. 自定义View系列教程02--onMeasure源码详尽分析

    深入探讨Android异步精髓Handler 站在源码的肩膀上全解Scroller工作机制 Android多分辨率适配框架(1)- 核心基础 Android多分辨率适配框架(2)- 原理剖析 Andr ...

随机推荐

  1. 左右声道音频怎么制作,用Vegas就对啦

    一款优秀的视频剪辑软件,不仅有高水平的视频制作功能,它的音频编辑功能也是必不可少的.Vegas就是这么一款软件,同时具备视频制作特效制作的同时,还能帮助制作轨道音频效果. 下面,就让小编带大家去学习, ...

  2. MySQL开发篇(未完待续)

    一.索引 什么是索引? 索引是帮助Mysql提高获取数据的数据结构,换一句话讲就是"排好序的快速查找的数据结构". 1.索引的分类 MySQL主要的几种索引类型:1.普通索引.2. ...

  3. SwiftUI:看我展示52张扑克牌,“很快啊!”

    目录 思路 效果图 相关代码解析 枚举创建扑克牌号码 枚举创建扑克牌类型 viewModel逻辑 UI实现 源码 感受 思路 使用 SwiftUI 创建 UI 结构: 使用 swift 的枚举和结构体 ...

  4. python-交互模式

    1.打开python交互式命令行: Windows+R→回车→输入python 如图 输入python进入交互模式,相当于启动了python解释器,输入一行代码就执行一行代码,可以用交互模式去验证每一 ...

  5. 网络 华为 ensp 命令

    VLAN 端口有三种模式:access,hybrid,trunk. Access类型端口:只能属于1个VLAN,且该端口不打tag,一般用于连接计算机端口: Trunk类型端口:可以允许多个VLAN通 ...

  6. 初学者值得拥有【Hadoop伪分布式模式安装部署】

    目录 1.了解单机模式与伪分布模式有何区别 2.安装好单机模式的Hadoop 3.修改Hadoop配置文件---五个核心配置文件 (1)hadoop-env.sh 1.到hadoop目录中 ​ 2.修 ...

  7. flask基本使用

    一.flask安装以及启动 1.安装 pip install flask 2.开启第一个flask项目 新建文件HelloWord.py from flask import Flask app = F ...

  8. Python 装饰器(装饰器的简单使用)

    简单介绍了装饰器的一些基本内容,包含定义.本质.原则.如何实现. 1.装饰器的定义 定义:一种增加函数功能的简单方法,可以快速地给不同的函数或类插入相同的功能. 简单点就是:高阶函数+嵌套函数 -&g ...

  9. VirtualBox安装Centos出现E_FAIL (0x80004005)的解决方法

    问题描述:UUID已经存在 Cannot register the hard disk 'F:\hadoop\VirtualBox-centos\centos6.4\centos6.4.vdi' {0 ...

  10. C#中SQL SERVER 2008字符数据类型使用心得

    一.尽可能使用Varchar,少使用或者不使用Char字符类型 因为char类型输入的数据长度达不到设计长度,会用空格补足,下面是数据表设计图: 下面是编辑前200行的图: 凡是输入的数据长度达不到设 ...