Django REST Framework 学习笔记(十):通用视图、视图工具类、视图集以及路由

  • Title(EN): Django REST Framework Learning Notes (10): Generic Views, MixIns, ViewSets and Routers
  • Author: dog2

基本信息

  • 源码
    • rest_framework.views
    • rest_framework.generics
    • rest_framework.viewsets
    • rest_framework.routers
  • 官方文档
    • API Guide - Generic views
    • API Guide - ViewSets
    • API Guide - Routers
  • 本文demo代码Github

AIPView - API视图类

APIView是Django REST Framework提供的所有视图的基类,继承自Django的View父类。

与Django View的不同

  1. 传入到视图方法中的是REST framework的Request对象,而不是Django的HttpRequeset对象
  2. 视图方法可以返回REST framework的Response对象,视图会为响应数据设置(render)符合前端要求的格式
  3. 任何APIException异常都会被捕获到,并且处理成合适的响应信息
  4. 在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制

重要类属性

AIPView有如下可设置的重要类属性:

  • authentication_classes:列表或元祖,身份认证类
  • permissoin_classes:列表或元祖,权限检查类
  • throttle_classes:列表或元祖,流量控制类

示例代码

APIView中仍有get()post()等其他请求方式的方法

1
2
3
4
5
6
7
8
9
from rest_framework.views import APIView
from rest_framework.response import Response

# path('books/', views.BookListView.as_view()),
class BookListView(APIView):
def get(self, request):
books = BookInfo.objects.all()
serializer = BookInfoSerializer(books, many=True)
return Response(serializer.data)

GenericAPIView - 通用API视图类

通用API视图类GenericAPIView继承自APIView,完全兼容APIView,主要增加了操作序列化器和数据库查询的方法,作用是为下面Mixin扩展类的执行提供基础类支持。通常在使用时,可以配合一个或多个Mixin扩展类。

GenericAPIViewAPIView多了什么

  1. get_queryset():从类属性queryset中获得modelqueryset数据。群操作就走get_queryset()方法(包括群查,群增等)。
  2. get_object():从类属性queryset中获得modelqueryset数据,再通过有名分组pk确定唯一操作对象。单操作就走get_object()方法(包括单查,单增等)。
  3. get_serializer():从类属性serializer_class中获得serializer的序列化类。

重要类属性

GenericAPIView有如下可设置的重要类属性:

  • 列表视图与详情视图共用
    • queryset:指明视图需要的数据(model查询数据)
    • permissoin_classes:指明视图使用的序列化器
  • 列表视图使用
    • pagination_class:指定分页控制类
    • filter_backends:指定过滤控制后端
  • 详情页视图使用
    • lookup_field:自定义主键,有名分组的查询,默认是pk
    • lookup_url_kwarg:查询单一数据时url中的参数关键字名称,默认与look_field相同

重要类方法

  • get_queryset():从类属性queryset中获得modelqueryset数据  
  • get_object():从类属性queryset中获得modelqueryset数据,再通过有名分组pk来确定唯一操作对象
  • get_serializer():从类属性serializer_class中获得serializer的序列化类,主要用来提供给Mixin扩展类使用

get_serializer 源码

1
2
3
4
5
6
7
8
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)

示例代码

视图层 views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class BookGenericAPIView(GenericAPIView):
queryset = models.Book.objects.filter(is_delete=False)
serializer_class = serializers.BookModelSerializer
lookup_field = 'pk' # 先定义好,单查可以使用,默认是pk 自定义主键的有名分组,如果路由有名分组不是pk,这个属性就要自己设置了

# 群取
def get(self, request, *args, **kwargs):
book_query = self.get_queryset() # 获取queryset数据(model查询数据)
book_ser = self.get_serializer(book_query, many=True)
book_data = book_ser.data
return APIResponse(results=book_data)

# # 单取
# def get(self, request, *args, **kwargs):
# book_query = self.get_object()
# book_ser = self.get_serializer(book_query)
# book_data = book_ser.data
# return APIResponse(results=book_data)

路由层 urls.py

1
2
3
4
urlpatterns = [
path('v2/books/', views.BookGenericAPIView.as_view()),
path('v2/books/<pk>/', views.BookGenericAPIView.as_view()),
]

xxxModelMixin - 视图类的模型工具集

作用

  • 提供了几种后端视图(对数据资源的增删改查)处理流程的实现,如果需要编写的视图属于这五种,则视图可以通过继承相应的扩展类来复用代码,减少自己编写的代码量。
  • mixins有五个工具类文件,一共提供了五个工具类,六个工具方法:单查、群查、单增、单删、单整体改、单局部改

使用

  • 继承工具类可以简化请求函数的实现体,但是必须继承GenericAPIView,需要GenericAPIView类提供序列化器与数据库查询的方法(见上方GenericAPIView基类知识点)
  • 工具类的工具方法返回值都是Response类型对象,如果要格式化数据格式再返回给前台,可以通过response.data拿到工具方法返回的Response类型对象的响应数据

五大模型工具类

ListModelMixin 群查

  • 列表视图扩展类,提供list方法快速实现查询视图
  • 返回200状态码
  • 除了查询,该list方法会对数据进行过滤和分页

CreateModelMixin 单增

  • 创建视图扩展类,提供create方法快速创建资源的视图,成功返回201的状态码
  • 没有群增的方法,需要自己手动写

RetrieveModelMixin 单查

  • 详情视图扩展类,提供retrieve方法,可以快速实现返回一个存在的数据对象

UpdateModelMixin 更新/修改

  • 更新视图扩展类,提供update方法,可以快速实现更新一个存在的数据对象,同时也提供partial_update方法,可以实现局部更新
  • 只有单整体改和单局部改,没有群整体改和群局部改

DestoryModelMixin 删除

  • 删除视图扩展类,提供destory方法,可以快速实现删除一个存在数据对象
  • 一般不怎么用到,因为实际开发中并不会真的删除数据,而是修改是否可用的标记

示例代码

视图层 views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin

class BookMixinGenericAPIView(ListModelMixin, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericAPIView):
# GenericAPIView提供的序列化器和查询的数据
queryset = models.Book.objects.filter(is_delete=False)
serializer_class = serializers.BookModelSerializer

# 单查和群查
def get(self, request, *args, **kwargs):
if 'pk' in kwargs:
# 单查 RetrieveModelMixin方法
response = self.retrieve(request, *args, **kwargs)
else:
# mixins提供的list方法的响应对象是Response,将该对象格式化为自定义的APIResponse
response = self.list(request, *args, **kwargs) # 群查 ListModelMixin
# response的数据都存放在response.data中
return APIResponse(results=response.data)

# 单增
def post(self, request, *args, **kwargs):
response = self.create(request, *args, **kwargs) # CreateModelMixin方法
return APIResponse(results=response.data)

# 单整体修改
def put(self, request, *args, **kwargs):
response = self.update(request, *args, **kwargs) # UpdateModelMixin
return APIResponse(results=response.data)

# 单局部修改
def patch(self, request, *args, **kwargs):
response = self.partial_update(request, *args, **kwargs)
return APIResponse(results=response.data)

路由层 urls.py

1
2
3
4
urlpatterns = [
path('v3/books/', views.BookMixinGenericAPIView.as_view()),
path('v3/books/<pk>/', views.BookMixinGenericAPIView.as_view()),
]

xxxAPIView - 功能性子视图类

功能性子视图类继承了GenericAPIView和各种Mixins工具类

  1. 功能性视图类都是GenericAPIView的子类,且不同的子类继承了不同的工具类
  2. 工功能性视图类的功能可以满足需求,只需要继承工具视图,并且提供querysetserializer_class即可

各大功能子视图类

一表胜千言:

视图 作用 请求类型 父类
ListAPIView 查询多条数据 get GenericAPIView
ListModelMixin
CreateAPIView 新增一条数据 post GenericAPIView
CreateModelMixin
RetrieveAPIView 查询一条数据 get GenericAPIView
RetrieveModelMixin
UpdateAPIView 修改一条数据 put
patch
GenericAPIView
UpdateModelMixin
DestroyAPIView 删除一条数据 delete GenericAPIView
DestroyModelMixin
RetrieveUpdateAPIView 单查
更新一条数据
get
put
patch
GenericAPIView
RetrieveModelMixin
UpdateModelMixin
RetrieveUpdateDestroyAPIView 单查
更新
删除一条数据
get
put
patch
delete
GenericAPIView
RetrieveModelMixin
UpdateModelMixin
DestroyModelMixin
ListCreateAPIView 群查
更新一条
get
post
GenericAPIView
ListModelMixin
mixins.CreateModelMixin

示例代码

视图层 views.py

1
2
3
4
from rest_framework.generics import ListCreateAPIView, UpdateAPIView
class BookListCreatePIView(ListCreateAPIView, UpdateAPIView):
queryset = models.Book.objects.filter(is_delete=False)
serializer_class = serializers.BookModelSerializer

路由层 urls.py

1
2
3
4
urlpatterns = [
path('v4/books/', views.BookListCreatePIView.as_view()),
path('v4/books/<pk>/', views.BookListCreatePIView.as_view()),
]

xxxViewset - 视图集

常用视图集父类

ViewSetMixin

ViewSetMixin主要是自定义了as_view方法,使可以通过其参数指定 HTTP_METHOD与函数的映射关系,如 view = MyViewSet.as_view({'get': 'list', 'post': 'create'})

ViewSet

继承自APIViewViewSetMixin,没有提供任何方法,需要自己写

GenericViewSet

继承GenericAPIViewViewSetMixin,其中GenericAPIView提供了基础方法,可以直接搭配Mixin扩展类使用,因此比较常用

ModelViewSet  

继承GenericViewset,但同时也包括ListModelMixinCreateModelMixinmixin扩展类

源码分析

  1. 视图集都是默认优先继承ViewSetMixin类,再继承一个视图类(GenericAPIViewAPIView
  2. ViewSetMixin提供了重写的as_view()方法,继承视图集的视图类,配置路由时调用as_view()必须传入 请求名-函数名 映射关系字典

例如:

1
path('v5/books/', views.BookGenericViewSet.as_view({'get': 'my_get_list'})),

表示get请求会交给my_get_list视图函数处理

GenericViewSet 示例代码

路由层 urls.py

1
2
3
4
5
6
7
8
urlpatterns = [
# View的as_view():将get请求映射到视图类的get方法
# ViewSet的as_view({'get': 'my_get_list'}):将get请求映射到视图类的my_get_list方法
path('v5/books/',
views.BookGenericViewSet.as_view({'get': 'my_get_list'})),
path('v5/books/<pk>/',
views.BookGenericViewSet.as_view({'get': 'my_get_obj'})),
]

视图层 views.py

1
2
3
4
5
6
7
8
9
10
11
12
from rest_framework.viewsets import GenericViewSet
from rest_framework import mixins #工具集 可以使用list,retrieve等方法

class BookGenericViewSet(RetrieveModelMixin, ListModelMixin, GenericViewSet):
queryset = models.Book.objects.filter(is_delete=False)
serializer_class = serializers.BookModelSerializer

def my_get_list(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)

def my_get_obj(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)

GenericViewSetViewSet

异同

  1. GenericViewSetViewSet都继承了ViewSetMixinas_view都可以配置 请求-函数 映射
  2. GenericViewSet继承的是GenericAPIView视图类,用来完成标准的 model 类操作接口
  3. ViewSet继承的是APIView视图类,用来完成不需要 model 类参与,或是非标准的 model 类操作接口,如
    • post请求在标准的 model 类操作下就是新增接口,登陆的post不满足。登陆的post请求,并不是完成数据的新增,只是用post提交数据,得到的结果也不是登陆的用户信息,而是登陆的认证信息
    • post请求验证码的接口,不需要 model 类的参与

源码

  • GenericViewSet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# viewsets.py
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

# generics.py
class GenericAPIView(views.APIView):
# ...

# vies.py
class APIView(View):
# ...
  • ViewSet
1
2
3
4
5
6
7
8
9
10

class ViewSet(ViewSetMixin, views.APIView):
"""
The base ViewSet class does not provide any actions by default.
"""
pass

# vies.py
class APIView(View):
# ...

ModelViewSet 示例代码

路由层 urls.py

1
2
3
4
5
6
urlpatterns = [
path('v6/books/',
views.BookModelViewSet.as_view({'get': 'list', 'post': 'create'})),
path('v6/books/<pk>/',
views.BookModelViewSet.as_view({'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'})),
]

视图层 views.py

1
2
3
4
5
6
7
8
9
10
11
12
class BookModelViewSet(ModelViewSet):
queryset = models.Book.objects.filter(is_delete=False)
serializer_class = serializers.BookModelSerializer

# 删不是数据库,而是该记录中的修改is_delete的值,因此重写默认的destroy函数
def destroy(self, request, *args, **kwargs):
instance = self.get_object() # type: models.Book
if not instance:
return APIResponse(1, '删除失败') # 实际操作,在此之前就做了判断
instance.is_delete = True
instance.save()
return APIResponse(0, '删除成功')

路由组件

因为具有局限性,所以在开发复杂接口时并不是首选。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.urls import path, include
from rest_framework.routers import SimpleRouter
from . import views

router = SimpleRouter()
# 所有路由与ViewSet视图类的都可以注册,会产生 'v7/books/' 和 'v7/books/<pk>/'
router.register('v7/books', views.BookModelViewSet)

urlpatterns = [
# router.urls添加方法一
# path('', include(router.urls)),
]

# router.urls添加方法二
# urlpatterns += router.urls

总结

一图胜千言,非原创,均来自网络: