视图家族
视图家族在rest_framework源码位置和学习曲线为:
rest_framework.views: 基本视图(APIView)
rest_framework.generics: 工具视图(GenericAPIView)
rest_framework.mixins: 视图工具集(Create/Destroy/List/Retrieve/Update)
rest_framework.viewsets: 视图集
APIView
前几章代码都是基于APIView写的, 这里就不赘述了, 简单来说APIView做了三件事:
1. 重写了as_view()方法, 在as_view()中主要调用了父类的as_view()方法 view = super().as_view(**initkwargs) , 并在最后返回时, 加上了csrf验证 return csrf_exempt(view)
2. 重写了dispatch()方法, 在父类的as_view()中被调用, 这是DRF的真正核心主函数, 在dispatch()中实现了各大模块
GenericAPIView
源码为: rest_framework.generics.GenericAPIView
class GenericAPIView(views.APIView): """ Base class for all other generic views. """ # You'll need to either set these attributes, # or override `get_queryset()`/`get_serializer_class()`. # If you are overriding a view method, it is important that you call # `get_queryset()` instead of accessing the `queryset` property directly, # as `queryset` will get evaluated only once, and those results are cached # for all subsequent requests. queryset = None serializer_class = None # If you want to use object lookups other than pk, set 'lookup_field'. # For more complex lookup requirements override `get_object()`. lookup_field = 'pk' lookup_url_kwarg = None # The filter backend classes to use for queryset filtering filter_backends = api_settings.DEFAULT_FILTER_BACKENDS # The style to use for queryset pagination. pagination_class = api_settings.DEFAULT_PAGINATION_CLASS def get_queryset(self): .......
1. 继承与APIView, 保留了APIView的特性, 并在其基础上定义了一下更加方便易用的属性
2. 定义了很多类属性, 如queryset(ORM查找结果集), serializer_class(序列化类), lookup_field(主键字段变量名),
3. 对于上述属性定义了对应的get_xxx方法, 如get_queryset(获取结果查找集), get_serializer(获取序列化类), get_object(获取模型对象)等等
如何使用GenericAPIView
让我们先回顾一下APIView如何实现查询接口的
class BookAPIView(APIView): def get(self, request, *args, **kwargs): pk = kwargs.get('pk') if pk: # 单查 books = Book.objects.get(pk=pk) many = False else: # 群查 books = Book.objects.filter(is_delete=False).all() many = True serializer = BookSerializer(instance=books, many=many) return MyResponse(result=serializer.data)
可以看到一般查询接口分为3步:
1. 获取结果查找集books
2. 序列化查询结果集得到序列化对象serializer
3. 返回响应
GenericAPIView就是将这通用的三步, 进行封装, 让使用者只需要编写关键的变量值即可
将上面的代码转化为GenericAPIView就是:
class BookGenericAPIView(GenericAPIView): queryset = Book.objects.filter(is_delete=False) serializer_class = BookSerializer def get(self, request, *args, **kwargs): pk = kwargs.get('pk') if pk: # 单取 return self.retrieve(request, *args, **kwargs) # 群取 return self.list(request, *args, **kwargs) # 单取 def retrieve(self, request, *args, **kwargs): # books = Book.objects.filter(is_delete=False, pk=pk).first() books = self.get_object() # serializer = BookSerializer(instance=books) serializer = self.get_serializer(instance=books) return MyResponse(result=serializer.data) # 群取 def list(self, request, *args, **kwargs): # books = Book.objects.filter(is_delete=False).all() books = self.get_queryset() # serializer = BookSerializer(instance=books, many=True) serializer = self.get_serializer(instance=books, many=True) return MyResponse(result=serializer.data)
这里将单取和群取做成了两个独立的函数, 且这样看似使用GenericAPIView时代码似乎比使用APIView更多, 但是先不要着急, 我们可以配合后面的mixin一起使用时就不需要写这么多代码了
使用GenericAPIView时, 将原先使用APIView的写法进行了如下替换:
1. 定义了两个变量, queryset用于保存查询结果集, serializer用于保存序列化类
2. 将原获取查询结果集的ORM写法 books = Book.objects.filter(is_delete=False).all() 换成了 books = self.get_queryset() 或 books = self.get_object() , 这里需要注意的一点就是在get_object()内部, 默认是通过参数名'pk'获取具体的主键ID, 而这就需要在url中的参数名也要为'pk', 若url中参数名为'xxx', 则需要重写GenericAPIView中的lookup_field = 'xxx'
3. 将原通过实例化序列化类写法 serializer = BookSerializer(instance=books) 换成了 serializer = self.get_serializer(instance=books)
其中get_queryset()和get_serializer()其实就是去拿到类属性queryset和Serializer_class, 然后再进行实例化操作, 即将原来APIView的写法拆成了两步: 第一步, 定义类属性; 第二步, 获取类属性并实例化.
Mixins工具集
上面手动简单实现了GenericAPIView的单查retrieve和群查list, 而mixins的工具集中就已经封装好了对应的函数, 且功能更加强大, 源码位置为: rest_framework.mixins.py
mixins中主要定义了5个类: CreateModelMixin(用于创建模型类对象), DestroyModelMixin(用于删除模型类对象), ListModelMixin(用于群查模型类对象), RetrieveModelMixin(用于单查模型类对象), UpdateModelMixin(用于单增模型类对象)
Mixins的使用
我们先跟着上面的查询接口, 看一下ListModelMixin用法, 查看ListModelMixin源码可以看到, 其群查的list方法和我们上面手动写的list方法很像, 只是多加了过滤器和分页器的功能
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)
具体用法为:
class BookMixinGenericAPIView(ListModelMixin, GenericAPIView): queryset = Book.objects.filter(is_delete=False) serializer_class = BookSerializer def get(self, request, *args, **kwargs): # 群查 # mixins提供的list方法的响应对象是Response, 想将该对象格式化为自定的MyResponse # response的数据都存放在response.data中 response = self.list(self, request, *args, **kwargs) return MyResponse(result=response.data)
可以看到mixin工具类和GenericAPIView组合使用时, 实现群查接口非常方便, 只需要三步:
1. 继承ListModelMixin和GenericAPIView
2. 定义类属性queryset和Serializer_class
3. 调用list方法并返回response
同样想要实现单查的话, 再继承一下RetrieveModelMixin, 然后手动分配一下get的方法调用即可, 需要注意的是继承时, 先写mixins的类, 最后写GenericAPIView, 因为是在mixin中调用了GenericAPIView的方法
class BookMixinGenericAPIView(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericAPIView): queryset = Book.objects.filter(is_delete=False) serializer_class = BookSerializer def get(self, request, *args, **kwargs): pk = kwargs.get('pk') if pk: # 单查 response = self.retrieve(request, *args, **kwargs) else: # 群查 response = self.list(self, request, *args, **kwargs) # mixins提供的list方法的响应对象是Response, 想将该对象格式化为自定的MyResponse # response的数据都存放在response.data return MyResponse(result=response.data)
同理, mixins还提供了其他的单增/单删/单整体改/单局部改的接口, 完整案例如下:
from rest_framework.generics import GenericAPIView from rest_framework import mixins from utils.response import MyResponse from books.models import Book from books.serializers import BookSerializer class BookMixinGenericAPIView(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, mixins.DestroyModelMixin, mixins.UpdateModelMixin, GenericAPIView): queryset = Book.objects.filter(is_delete=False) serializer_class = BookSerializer # 查 def get(self, request, *args, **kwargs): pk = kwargs.get('pk') if pk: # 单查 response = self.retrieve(request, *args, **kwargs) else: # 群查 response = self.list(self, request, *args, **kwargs) # mixins提供的list方法的响应对象是Response, 想将该对象格式化为自定的MyResponse # response的数据都存放在response.data return MyResponse(result=response.data) # 单增 def post(self, request, *args, **kwargs): response = self.create(request, *args, **kwargs) return MyResponse(result=response.data) # 单删 def delete(self, request, *args, **kwargs): response = self.destroy(request, *args, **kwargs) return MyResponse(result=response.data) # 单整体改 def put(self, request, *args, **kwargs): response = self.update(request, *args, **kwargs) return MyResponse(result=response.data) # 单局部改 def patch(self, request, *args, **kwargs): response = self.partial_update(request, *args, **kwargs) return MyResponse(result=response.data)
mixins的钩子函数
在上面我们调用了mixin的self.create/destroy/update方法实现增删改, 但如果我们对增删改操作还有一些附加的操作(比如在新增时触发发送邮件功能)或者不想用其默认的方法(比如它原来的删除确实是把数据直接删除掉了, 而我们想要的删除只是改一下数据的is_dalete状态)时, mixins给我们提供了相应的钩子函数perform_create/perform_destroy/perform_update, 这些函数都在源码相应的类中定义了:
def perform_create(self, serializer): serializer.save() def perform_destroy(self, instance): instance.delete() def perform_update(self, serializer): serializer.save()
现在我们在我们的视图类中重写一下删除的操作perform_destroy, 将数据的is_dalete状态改为True
# 单删 def perform_destroy(self, instance): instance.is_delete = True instance.save() def delete(self, request, *args, **kwargs): response = self.destroy(request, *args, **kwargs) return MyResponse(result=response.data)
generics
上面我们把GenericAPIView和mixins的工具集联合使用, 同时继承, 可以实现简单的增删改查接口, 在generics.py中, 除了提供了GenericAPIView外, 还提供了一些其他的类, 如 CreateAPIView/DestroyAPIView/ UpdateAPIView/ ListCreateAPIView/ RetrieveDestroyAPIView 等等, 这些类都是上我们上面同时继承mixin和GenericAPIView一样, 是帮我们封装好的一些工具类, 源码如下:
# 单增 class CreateAPIView(mixins.CreateModelMixin, GenericAPIView): """ Concrete view for creating a model instance. """ def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) # 群查 class ListAPIView(mixins.ListModelMixin, GenericAPIView): """ Concrete view for listing a queryset. """ def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) # 单查 class RetrieveAPIView(mixins.RetrieveModelMixin, GenericAPIView): """ Concrete view for retrieving a model instance. """ def get(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs) # 单删 class DestroyAPIView(mixins.DestroyModelMixin, GenericAPIView): """ Concrete view for deleting a model instance. """ def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs) # 单改 class UpdateAPIView(mixins.UpdateModelMixin, GenericAPIView): """ Concrete view for updating a model instance. """ def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) def patch(self, request, *args, **kwargs): return self.partial_update(request, *args, **kwargs) # 群查和单增 class ListCreateAPIView(mixins.ListModelMixin, mixins.CreateModelMixin, GenericAPIView): """ Concrete view for listing a queryset or creating a model instance. """ def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) # 单查和单改 class RetrieveUpdateAPIView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, GenericAPIView): """ Concrete view for retrieving, updating a model instance. """ def get(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) def patch(self, request, *args, **kwargs): return self.partial_update(request, *args, **kwargs) # 单查和单删 class RetrieveDestroyAPIView(mixins.RetrieveModelMixin, mixins.DestroyModelMixin, GenericAPIView): """ Concrete view for retrieving or deleting a model instance. """ def get(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs) # 单查和单改和单删 class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, GenericAPIView): """ Concrete view for retrieving, updating or deleting a model instance. """ def get(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) def patch(self, request, *args, **kwargs): return self.partial_update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs)
viewsets
在使用了上面的mixin工具后, 会发现增删改查的函数名(create/destroy/partial_update/update/list/retrieve)与对应的请求方式名(post/delete/patch/put/get)两者的命名并不是一样的, 而是存在一个对应关系或者映射关系.我们把两者结合的方式就是在视图类中通过dispatch的反射而定义与请求方式名一样的函数(post/delete/patch/put/get), 在函数中调用mixin的对应的方法.
ViewSetMixin
那么这种结合方式或者对应关系, 能不能写的更加简单一点呢? 在rest_framework.viewsets.py中, 有一个 ViewSetMixin 类, 这个类的作用就是重写了 as_view() 方法, 让其增加一个字典参数(actions), 把请求方式名与接口函数名的对应的关系直接写在字典参数(actions)中, 源代码分析为:
class ViewSetMixin: """ This is the magic. Overrides `.as_view()` so that it takes an `actions` keyword that performs the binding of HTTP methods to actions on the Resource. For example, to create a concrete view binding the 'GET' and 'POST' methods to the 'list' and 'create' actions... view = MyViewSet.as_view({'get': 'list', 'post': 'create'}) """ @classonlymethod def as_view(cls, actions=None, **initkwargs): ....代码省略 # actions must not be empty if not actions: raise TypeError("The `actions` argument must be provided when " "calling `.as_view()` on a ViewSet. For example " "`.as_view({'get': 'list'})`") ....代码省略 def view(request, *args, **kwargs): self = cls(**initkwargs) # We also store the mapping of request methods to actions, # so that we can later set the action attribute. # eg. `self.action = 'list'` on an incoming GET request. self.action_map = actions # Bind methods to actions # This is the bit that's different to a standard view # 绑定请求方式method与接口函数action的对应关系 for method, action in actions.items(): handler = getattr(self, action) setattr(self, method, handler) ....代码省略 return self.dispatch(request, *args, **kwargs) ....代码省略 view.actions = actions return csrf_exempt(view)
可以看到:
1. 在类的说明文档中, 就告诉了我们说这是重写的as_view()方法, 新增了一个actions参数用来绑定HTTP请求方式和对应的操作, 例如: MyViewSet.as_view({'get': 'list', 'post': 'create'})
2. 在as_view中的view()方法中, 同样通过反射进行上述的绑定
GenericViewSet/ViewSet
上述的ViewSetMixin类只是重写了as_view()方法让其新增一个actions参数而已, 而具体使用时还需要和前面提到的GenericAPIView或者更基础的APIView一起使用, 因为具体的处理逻辑还是需要走dispatch方法.
于是在viewsets.py中还提供了两个类, GenericViewSet(ViewSetMixin, generics.GenericAPIView) 和 ViewSet(ViewSetMixin, views.APIView) , 就像之前我们选择使用基础的APIView还是GenericAPIView一样, DRF也提供了两个对应的ViewSet类, 这里我们使用GenericViewSet实现简单的群查功能:
# 在urls.py中添加配置 from django.urls import path from books import views urlpatterns = [ ... path('v4/', views.BookGenericViewSet.as_view({'get': 'list'})), ] # 在views.py中添加视图类 from rest_framework.viewsets import GenericViewSet
class BookGenericViewSet(GenericViewSet): queryset = Book.objects.filter(is_delete=False) serializer_class = BookSerializer def list(self, request): books = self.get_queryset() serializer = self.get_serializer(books, many=True) return MyResponse(result=serializer.data)
ModelViewSet/ReadOnlyModelViewSet
我们在上一步继承了GenericViewSet, 但是list方法还是我们自己手动写的, 我们可以结合上面的mixin工具一起使用, 例如同时继承mixins的ListModelMixin和GenericViewSet实现群查, 这样list方法在mixin中就定义好了, 我们也不需要手动写list方法, 视图类代码就变得更加简单, 只需要定义类属性queryset和Serializer_class即可:
# 在urls.py中添加配置 from django.urls import path from books import views urlpatterns = [ ... path('v5/', views.BookListMixinGenericViewSet.as_view({'get': 'list'})), ] # 在views.py中添加视图类 from rest_framework import mixins from rest_framework.viewsets import GenericViewSet class BookListMixinGenericViewSet(mixins.ListModelMixin, GenericViewSet): queryset = Book.objects.filter(is_delete=False) serializer_class = BookSerializer
同理, 我们还可以加上其他接口的mixin工具, 如单查mixins.RetrieveModelMixin, 单增mixins.CreateModelMixin等等, 而这一步DRF也为我们想好了, 在viewsets中定义了一个 ModelViewSet 类, 它继承了mixin的所有接口类
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
同时, DRF还定义了一个 ReadOnlyModelViewSet 类, 顾名思义, 这个类只能用来查询, 只继承了 单查mixins.RetrieveModelMixin和 群查 mixins.ListModelMixin两个接口工具类
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin, mixins.ListModelMixin, GenericViewSet): """ A viewset that provides default `list()` and `retrieve()` actions. """ pass
我们可以按需使用ModelViewSet类还是ReadOnlyModelViewSet, 这里我们使用ModelViewSet, 不需要几行代码就能完成简单的单增, 单删, 单整体改, 单局部改, 单查, 群查6个接口, 但是这里需要注意的是由于使用的是mixin的默认接口函数, 我们之前自定义的响应类MyResponse就无法使用了, 除非我们再重写一下这些默认的接口函数:
# 在urls.py中添加配置 from django.urls import path from books import views urlpatterns = [ ... path('v6/', views.BookModelViewSet.as_view({'get': 'list', 'post': 'create', })), path('v6/<int:pk>/', views.BookModelViewSet.as_view( {'get': 'retrieve', 'delete': 'destroy', 'put': 'update', 'patch': 'partial_update'})), ] # 在views.py中添加视图类 from rest_framework import mixins from rest_framework.viewsets import ModelViewSet class BookModelViewSet(ModelViewSet): queryset = Book.objects.filter(is_delete=False) serializer_class = BookSerializer
路由(Router)
有一些像Rails这样的Web框架提供自动生成Urls的功能。但是Django并没有。 REST framework为Django添加了这一功能,以一种简单、快速、一致的方式。
DRF的routers模块没有什么东西,主要包含下面几个类:
BaseRouter:路由的基类
SimpleRouter: 继承了BaseRouter,常用类之一
DefaultRouter:继承了SimpleRouter,常用类之一
APIRootView
我们主要使用的其实就是SimpleRouter和DefaultRouter, 基本用法为, 编辑urls.py:
from rest_framework.routers import DefaultRouter # 1.创建路由对象 router = DefaultRouter() # 2.注册视图类 router.register(r'v6', views.BookModelViewSet, basename='v6') router.register(r'v5', views.BookListMixinGenericViewSet, basename='v5') urlpatterns = [ # path('v5/', views.BookListMixinGenericViewSet.as_view({'get': 'list'})), # path('v6/', views.BookModelViewSet.as_view({'get': 'list', 'post': 'create', })), # path('v6/<int:pk>/', views.BookModelViewSet.as_view( # {'get': 'retrieve', 'delete': 'destroy', 'put': 'update', 'patch': 'partial_update'})), # 3.将路由类的urls添加到urlpatterns中 path('', include(router.urls)), ]