Django一分钟:DRF ViewSet烹饪指南,创建好用的视图集

本文将介绍django视图集的内部实现,并带你重写部分代码自己组装强大且趁手的视图集,以满足自定义的业务需求,避免编写大量重复代码。

一、基础知识

Django Rest framework框架允许你将一组相关视图的逻辑组合到一个类中,也就是我们所谓的视图集ViewSet

APIView相比,ViewSet更加抽象,更难理解。APIView的使用方法非常直观,需要你提供诸如.get().post()之类的处理方法,并由其自动将请求分发到对应的方法上。ViewSet则不同,它要求你提供诸如.list().create()这类操作方法,在实现了这些方法之后,使用.as_view()方法请操作方法映射到不同的处理方法上,比如list->getdestroy->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本身不提供这些通用的listcreate之类的方法,而是由一些列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类和视图集,运用得当我们可以打造属于自己的趁手工具以从大量重复工作中解脱。

相关推荐
长和信泰光伏储能17 小时前
京津冀光伏发电:绿色能源的未来之路
python·能源
浦信仿真大讲堂17 小时前
从重复操作到自动化闭环:如何让 CST 与 Python 真正协同起来
python·自动化·cst·仿真软件·达索软件
Gu Gu Study18 小时前
ScoutLoop开放域深度研究引擎(agent的初步设计想法)
人工智能·python
卷无止境18 小时前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境18 小时前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd12318 小时前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy123931021619 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
bamb0020 小时前
一个项目带你入门AI应用开发01
python
05664620 小时前
Python康复训练——常用标准库
开发语言·python·学习
昆曲之源_娄江河畔20 小时前
Python如何安装flask, pymssql
开发语言·python·flask·pymssql