本文将介绍django视图集的内部实现,并带你重写部分代码自己组装强大且趁手的视图集,以满足自定义的业务需求,避免编写大量重复代码。
一、基础知识
Django Rest framework框架允许你将一组相关视图的逻辑组合到一个类中,也就是我们所谓的视图集ViewSet
。
与APIView
相比,ViewSet
更加抽象,更难理解。APIView
的使用方法非常直观,需要你提供诸如.get()
或.post()
之类的处理方法,并由其自动将请求分发到对应的方法上。ViewSet
则不同,它要求你提供诸如.list()
或.create()
这类操作方法,在实现了这些方法之后,使用.as_view()
方法请操作方法映射到不同的处理方法上,比如list->get
、destroy->delete
。
一个简单的示例
python
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from myapps.serializers import UserSerializer
from rest_framework import viewsets
from rest_framework.response import Response
class UserViewSet(viewsets.ViewSet):
"""
一个简单的视图集,实现获取用户列表和查询单个用户的功能
"""
def list(self, request):
queryset = User.objects.all()
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = User.objects.all()
user = get_object_or_404(queryset, pk=pk)
serializer = UserSerializer(user)
return Response(serializer.data)
在很多普通业务中,增删改查的操作是相同的,比如请求数据列表的操作,无非是三步:数据库获取查询集->序列化->返回响应。drf预制了这些重复的工作,将通用的方法封装进了ModelViewSet
,借助ModelViewSet
我们可以非常轻松的完成增删改查等工作(对应APIView就是ModelAPIView):
python
class AccountViewSet(viewsets.ModelViewSet):
"""
只需要指定查询集和序列化器即可
"""
queryset = Account.objects.all()
serializer_class = AccountSerializer
permission_classes = [IsAccountAdminOrReadOnly]
ModelViewSet
本身不提供这些通用的list
或create
之类的方法,而是由一些列Mixin
类来实现,ModelViewSet
负责把它们组合起来:
python
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet): pass
就以CreateModelMixin
为例,CreateModelMixin
为我们提供了一个通用的和符合标准的create()
方法,其过程也就是获取查询集->序列化->返回,没有特殊需求我们的视图集继承它就能获取预制的create
方法,不需要再自己实现:
python
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 {}
细心观察在CreateModelMixin
中我们获取序列化器的方法是get_serializer
,此外一些其它的Minxin类中,你可能发现其获取查询集的方法是get_queryset
或者filter_queryset
,还有诸如paginate_queryset
这样的方法,一个典型的示例就是ListModelMixin
:
python
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)
这些方法并非凭空而来而是由GenericViewSet
类来提供,准确说是它的父类GenericAPIView
:
python
class GenericViewSet(ViewSetMixin, generics.GenericAPIView): pass
GenericViewSet
继承GenericAPIView
为各种Mixin
提供统一的获取查询集、序列化器、分页、授权等等接口。
python
class GenericAPIView(views.APIView):
...
queryset = None
serializer_class = None
...
def get_queryset(self):...
def get_object(self):...
def get_serializer(self, *args, **kwargs):...
def get_serializer_class(self):...
def get_serializer_context(self):...
def filter_queryset(self, queryset):...
def paginator(self):...
def paginate_queryset(self, queryset):...
def get_paginated_response(self, data):...
二、灵活自定义
drf预制的Mixin
足够标准和通用,但如果我们的业务中有特殊需求,我们就需要对drf预制的Mixin
重新烹饪,实际操作并不困难,接下来我们通过几个具体的场景来实际体会一下。
自定义响应格式
假如我想让视图集返回的响应遵循如下格式:
python
{
"status": "ok",
"code": 200,
"messages": [],
"result": {
"user": {
"id": 123,
"name": "shazow"
}
}
}
我们可以先实现一个自定义的响应类来替换掉Mixin
中使用的响应类。
python
import json
from rest_framework.response import Response
class Rep(Response):
"""struct json response"""
def __init__(self, result=None, message=None, status=None, code=None, **kwargs):
if message is None:
message = []
data = {
"status": status,
"code": code,
"message": message,
"result": result
}
super().__init__(data, code, **kwargs)
@staticmethod
def ok(result=None, message=None, code=None, **kwargs):
return Rep(result=result, message=message, status="ok", code=code, **kwargs)
@staticmethod
def err(result=None, message=None, code=None, **kwargs):
return Rep(result=result, message=message, status="err", code=code, **kwargs)
以RetrieveModelMixin
为例,你可以继承并重写retrieve
,也可以干脆复制一份到自己的项目中,再修改retrieve
方法,我们这里选择复制一份到自己的项目中。为了和原来的RetrieveModelMixin
做区分,且将其命名为XRetrieveModelMixin
:
python
class XRetrieveModelMixin:
"""
Retrieve a model instance.
"""
# 使用我们自己的Rep响应类替换了Response响应类
def retrieve(self, request, *args, **kwargs):
try:
instance = self.get_object()
except Http404:
return Rep.err(None, ["查询数据不存在"], status.HTTP_404_NOT_FOUND)
serializer = self.get_serializer(instance)
return Rep.ok(serializer.data, None, code=status.HTTP_200_OK)
# 对比原来的
# 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)
自动记录创建和更新数据的用户
细心观察drf的Minxin
类并不是将全部分逻辑写在一个create
方法或者update
方法中,实际上它把实现功能的代码拆分到了多个函数中。
以CreateModelMixin
类为例,你可以看到create
的方法由perform_create
方法和get_success_headers
组合而来:
python
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 {}
这样非常有利于我们进行重写,假如我们想对serializer.save()
的过程做些修改,比如记录创建用户,我们就可以通过重写perform_create
来实现:
python
class TrackerModelViewSet(ModelViewSet):
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
# 记录更新操作的用户;perform_update来自UpdateModelMixin
def perform_update(self, serializer):
serializer.save(updated_by=self.request.user)
三、总结
学习drf是如何预制Mixin
的,我们可以预制自己的Mixin
类和视图集,运用得当我们可以打造属于自己的趁手工具以从大量重复工作中解脱。