zoukankan      html  css  js  c++  java
  • DRF--重写views

    前戏

    在前面几篇文章里,我们写了get请求,post请求,put请求,在来写个delete请求,大概如下。

    class BookView(APIView):  # 查询所有的数据和post方法
    
        def get(self, request):
            book_queryset = Book.objects.all()
            # 拿出来的是一个queryset,用序列化器进行序列化
            ser_obj = BookSerializer(book_queryset, many=True)
            return Response(ser_obj.data)  # 序列化后的数据在data里
    
        def post(self, request):
            # 确定数据类型以及数据结构
            # 对前端传来的数据进行校验
            book_obj = request.data  # post传来的数据
            ser_obj = BookSerializer(data=book_obj)  # 有data参数,表示反序列化
            if ser_obj.is_valid():
                ser_obj.save()
                return Response(ser_obj.validated_data)
            return Response(ser_obj.errors)  # 返回错误
    
    
    class BookEditView(APIView): # 查询单条数据和put、delete方法 def get(self, request, id): book_obj = Book.objects.filter(id=id).first() ser_obj = BookSerializer(book_obj) # 查询出的是一条数据,不需要加 many=True return Response(ser_obj.data) def put(self, request, id): book_obj = Book.objects.filter(id=id).first() # instance必传,data=request.data前端传的参数,partial=True部分修改 ser_obj = BookSerializer(instance=book_obj, data=request.data, partial=True) if ser_obj.is_valid(): ser_obj.save() return Response(ser_obj.data) # 返回数据,注意不是ser_obj.validated_data return Response(ser_obj.errors) def delete(self, request, id): book_obj = Book.objects.filter(id=id).first() if not book_obj: return Response('删除的对象不存在') book_obj.delete() return Response('Success')

    路由

    urlpatterns = [
        url(r'^book/$', BookView.as_view()),
        url(r'^book/(?P<id>d+)', BookEditView.as_view()),
    ]

    这个视图只是实现了Book表的增删改查功能,如果有几十张表,我们就要写几十个对应的类,复制,粘贴,复制,粘贴。。。身为一个优秀的测试工程师。这种比较low的方法肯定不是我们干的,应该是开发干的,手动滑稽。

    如果分析上面的代码,我们会发现,每个请求只要传不同的ORM语句和序列化器就可以实现了。所以我们可以对代码进行重构,面向对象的三大特性,继承,封装,多态。我们可以写一个通用的方法来继承它,每个子类都可以改写继承过来的方法,就是多态。

    第一版

     1 from django.shortcuts import render
     2 from rest_framework.views import APIView
     3 from rest_framework.response import Response
     4 from djangoDemo.models import Book  # 导入表
     5 from .serializers import BookSerializer
     6 
     7 
     8 class GenericAPIView(APIView):  # 通用的API视图
     9     queryset = None
    10     serializer_class = None
    11 
    12     def get_queryset(self):
    13         # 这里要加.all() 虽然下面的ORM查询出来的是所有的数据
    14         # 但是由于DRF的内部机制,这里如果不加就会报错
    15         return self.queryset.all()
    16 
    17     def get_serializer(self, *args, **kwargs):
    18         # 不同请求的序列化器里传的参数是不一样的
    19         return self.serializer_class(*args, **kwargs)
    20 
    21 
    22 class BookView(GenericAPIView):  # 继承通用的方法
    23     queryset = Book.objects.all()
    24     serializer_class = BookSerializer
    25 
    26     def get(self, request):
    27         # 调用外部的get方法
    28         queryset = self.get_queryset()
    29 
    30         # 调用外部的序列化方法
    31         ser_obj = self.get_serializer(queryset, many=True)
    32         return Response(ser_obj.data)
    33 
    34     def post(self, request):
    35         ser_obj = self.get_serializer(data=request.data)
    36         if ser_obj.is_valid():
    37             ser_obj.save()
    38             return Response(ser_obj.validated_data)
    39         return Response(ser_obj.errors)

    当上面的代码执行get请求时,先执行28行,28行调用的是12,执行15行,返回的是一个queryset,然后先在自己内部找,自己内部有,也就是23行,在执行31行,调用的是17行,执行19行,返回的是serializer_class,先在自己内部找,自己内部有,也就是24行。在执行32行

    上面的代码只是简单的封装了一下,还可以在次封装。

    第二版

     1 from django.shortcuts import render
     2 from rest_framework.views import APIView
     3 from rest_framework.response import Response
     4 from djangoDemo.models import Book  # 导入表
     5 from .serializers import BookSerializer
     6 
     7 
     8 class GenericAPIView(APIView):  # 通用的API视图
     9     queryset = None
    10     serializer_class = None
    11 
    12     def get_queryset(self):
    13         # 这里要加.all() 虽然下面的ORM查询出来的是所有的数据
    14         # 但是由于DRF的内部机制,这里如果不加就会报错
    15         return self.queryset.all()
    16 
    17     def get_serializer(self, *args, **kwargs):
    18         # 不同请求的序列化器里传的参数是不一样的
    19         return self.serializer_class(*args, **kwargs)
    20 
    21 class ListModelMixin(object):  # get方法,查询所有数据
    22     def list(self, request):
    23         queryset = self.get_queryset()
    24         ser_obj = self.get_serializer(queryset, many=True)
    25         return Response(ser_obj.data)
    26 
    27 class CreateModelMixin(object):  # post方法
    28     def create(self, request):
    29         ser_obj = self.get_serializer(data=request.data)
    30         if ser_obj.is_valid():
    31             ser_obj.save()
    32             return Response(ser_obj.validated_data)
    33         return Response(ser_obj.errors)
    34 
    35 
    36 class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):  # 继承
    37     queryset = Book.objects.all()
    38     serializer_class = BookSerializer
    39 
    40     def get(self, request):
    41         return self.list(request)  # 调用get方法
    42 
    43     def post(self, request):
    44         return self.create(request)  # 调用post方法

     上面对BookView类进行了封装处理,还有一个BookEditView类,也来进行封装处理

    from django.shortcuts import render
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from djangoDemo.models import Book  # 导入表
    from .serializers import BookSerializer
    
    
    class GenericAPIView(APIView):  # 通用的API视图
        queryset = None
        serializer_class = None
    
        def get_queryset(self):
            # 这里要加.all() 虽然下面的ORM查询出来的是所有的数据
            # 但是由于DRF的内部机制,这里如果不加就会报错
            return self.queryset.all()
    
        def get_serializer(self, *args, **kwargs):
            # 不同请求的序列化器里传的参数是不一样的
            return self.serializer_class(*args, **kwargs)
    
    
    class RetrieveModelMixin(object):  # get方法,查询单条数据
        def retrieve(self, request, id):
            # 先查询出所有,在过滤,在取第一个
            book_obj = self.get_queryset().filter(id=id).first()
            ser_obj = self.get_serializer(book_obj)
            return Response(ser_obj.data)
    
    
    
    class UpdateModelMixin(object):  # put方法
        def update(self, request, id):
            book_obj = self.get_queryset().filter(id=id).first()
            ser_obj = self.get_serializer(instance=book_obj, data=request.data, partial=True)
            if ser_obj.is_valid():
                ser_obj.save()
                return Response(ser_obj.data)  # 返回数据,注意不是ser_obj.validated_data
            return Response(ser_obj.errors)
    
    
    class DestroyModelMixin(object):  # delete方法
        def destoy(self, request, id):
            book_obj = self.get_queryset().filter(id=id).first()
            if not book_obj:
                return Response('删除的对象不存在')
            book_obj.delete()
            return Response('Success')
    
    
    class BookEditView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):  # 查询单条数据和put、delete方法
        queryset = Book.objects.all()
        serializer_class = BookSerializer
    
        def get(self, request, id):
            return self.retrieve(request, id)
    
        def put(self, request, id):
            return self.update(request, id)
    
        def delete(self, request, id):
            return self.destoy(request, id)

    这样我们就完成了所有请求的封装

    在来看下,每个BookView类和BookEditView类都继承了好几个类,如果以后还有其他的类,都要继承这几个类,多麻烦,我们可以写两个单独的类,来继承,以后所有的类都继承这两个类就可以了

    第三版

    from django.shortcuts import render
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from djangoDemo.models import Book  # 导入表
    from .serializers import BookSerializer
    
    
    class GenericAPIView(APIView):  # 通用的API视图
        queryset = None
        serializer_class = None
    
        def get_queryset(self):
            # 这里要加.all() 虽然下面的ORM查询出来的是所有的数据
            # 但是由于DRF的内部机制,这里如果不加就会报错
            return self.queryset.all()
    
        def get_serializer(self, *args, **kwargs):
            # 不同请求的序列化器里传的参数是不一样的
            return self.serializer_class(*args, **kwargs)
    
    
    class ListModelMixin(object):  # get方法,查询所有数据
        def list(self, request):
            queryset = self.get_queryset()
            ser_obj = self.get_serializer(queryset, many=True)
            return Response(ser_obj.data)
    
    
    class CreateModelMixin(object):  # post方法
        def create(self, request):
            ser_obj = self.get_serializer(data=request.data)
            if ser_obj.is_valid():
                ser_obj.save()
                return Response(ser_obj.validated_data)
            return Response(ser_obj.errors)
    
    
    class RetrieveModelMixin(object):  # get方法,查询单条数据
        def retrieve(self, request, id):
            # 先查询出所有,在过滤,在取第一个
            book_obj = self.get_queryset().filter(id=id).first()
            ser_obj = self.get_serializer(book_obj)
            return Response(ser_obj.data)
    
    
    class UpdateModelMixin(object):  # put方法
        def update(self, request, id):
            book_obj = self.get_queryset().filter(id=id).first()
            ser_obj = self.get_serializer(instance=book_obj, data=request.data, partial=True)
            if ser_obj.is_valid():
                ser_obj.save()
                return Response(ser_obj.data)  # 返回数据,注意不是ser_obj.validated_data
            return Response(ser_obj.errors)
    
    
    class DestroyModelMixin(object):  # delete方法
        def destoy(self, request, id):
            book_obj = self.get_queryset().filter(id=id).first()
            if not book_obj:
                return Response('删除的对象不存在')
            book_obj.delete()
            return Response('Success')
    
    
    class ListCreateAPIView(GenericAPIView, ListModelMixin, CreateModelMixin):
        pass
    
    
    class RetrieveUpdateDestroyAPIView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
        pass
    
    
    class BookView(ListCreateAPIView):  # 查询所有数据和添加数据的方法
        queryset = Book.objects.all()
        serializer_class = BookSerializer
    
        def get(self, request):
            return self.list(request)  # 调用get方法
    
        def post(self, request):
            return self.create(request)  # 调用post方法
    
    
    class BookEditView(RetrieveUpdateDestroyAPIView):  # 查询单条数据和put、delete方法
        queryset = Book.objects.all()
        serializer_class = BookSerializer
    
        def get(self, request, id):
            return self.retrieve(request, id)
    
        def put(self, request, id):
            return self.update(request, id)
    
        def delete(self, request, id):
            return self.destoy(request, id)

    第四版

    上面有两个get方法,一个是查询一条,一个是查询多条。那我们可不可以通过路由传参的方式来解决呢?只让它走一个,路由类似于这样

    urlpatterns = [
        # url(r'^book/$', BookView.as_view()),
        # url(r'^book/(?P<id>d+)', BookEditView.as_view()),
        url(r'^book/$', BookView.as_view({"get": "list", "post": "create"})),
        url(r'^book/(?P<id>d+)', BookEditView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
    ]

    我们知道在CBV中,在执行视图函数时会先执行dispatch方法,我们继承了APIView,来看看源码是怎样写的,APIView里的as_view方法代码如下

     1  def as_view(cls, **initkwargs):
     2         """
     3         Store the original class on the view function.
     4 
     5         This allows us to discover information about the view when we do URL
     6         reverse lookups.  Used for breadcrumb generation.
     7         """
     8         if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
     9             def force_evaluation():
    10                 raise RuntimeError(
    11                     'Do not evaluate the `.queryset` attribute directly, '
    12                     'as the result will be cached and reused between requests. '
    13                     'Use `.all()` or call `.get_queryset()` instead.'
    14                 )
    15             cls.queryset._fetch_all = force_evaluation
    16 
    17         view = super(APIView, cls).as_view(**initkwargs)
    18         view.cls = cls
    19         view.initkwargs = initkwargs
    20 
    21         # Note: session based authentication is explicitly CSRF validated,
    22         # all other authentication is CSRF exempt.
    23         return csrf_exempt(view)
    View Code

    这里可以看到可以传参,在去看看父类的as_view方法(第十七行)

     1     def as_view(cls, **initkwargs):
     2         """
     3         Main entry point for a request-response process.
     4         """
     5         for key in initkwargs:
     6             if key in cls.http_method_names:
     7                 raise TypeError("You tried to pass in the %s method name as a "
     8                                 "keyword argument to %s(). Don't do that."
     9                                 % (key, cls.__name__))
    10             if not hasattr(cls, key):
    11                 raise TypeError("%s() received an invalid keyword %r. as_view "
    12                                 "only accepts arguments that are already "
    13                                 "attributes of the class." % (cls.__name__, key))
    View Code

    很显然,从源代码里可以看出,父类的as_view也可以接收字典类型的传参,但是,如果key在initkwargs里,就会抛出一个错误(第七行),所以我们传参肯定会报错的,既然这样,那我们可以重写as_view方法,DRF已经替我们想好了,也替我们封装了,我们只需要使用就可以了,导入

    from rest_framework.viewsets import ViewSetMixin

    可以点进入看91到93行的源码

    for method, action in actions.items():
        handler = getattr(self, action)
        setattr(self, method, handler)

    这里,action 就是我们传来的参数,for循环之后,method就是原来的get请求,action就是我们写的list方法({"get":"list"})。然后通过setattr,执行get时,就会去执行我们写的list方法。

    我们可以写个ModelViewSet类,来继承我们的两个get方法

    class ModelViewSet(ViewSetMixin, ListCreateAPIView, RetrieveUpdateDestroyAPIView):
        pass

    然后再写个类,让它继承这个类

    class BookModelView(ModelViewSet):
        queryset = Book.objects.all()
        serializer_class = BookSerializer

    在来改写路由

    from django.conf.urls import url, include
    from .views import  BookModelView
    
    urlpatterns = [
        url(r'^book/$', BookModelView.as_view({"get": "list", "post": "create"})),
        url(r'^book/(?P<id>d+)', BookModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
    ]

    这样我们所有的请求就都会去执行BookModelView视图了。

    终极版

    上面我们封装了那么多的类,其实DRF已经给我们封装好了,我们写的所有类都在下面的几个类里面,只是比我们自己写的全很多

    from rest_framework import views
    from rest_framework import viewsets
    from rest_framework import generics
    from rest_framework import mixins

    我们进入viewsets里面看看最后的代码

    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

    这里已经写好了,所以我们只需要继承ModelViewSet就可以了

    只需要导入 from rest_framework import viewsets,继承就可以了,通过继承就可以看出,前面我们写了上百行的代码,只需要六行就实现了,这就上python的强大之处

    from djangoDemo.models import Book  # 导入表
    from .serializers import BookSerializer
    from rest_framework import viewsets
    
    
    class BookModelView(viewsets.ModelViewSet):
        queryset = Book.objects.all()
        serializer_class = BookSerializer

    然后再改写路由

    from django.conf.urls import url, include
    from .views import  BookModelView
    
    urlpatterns = [
        url(r'^book/$', BookModelView.as_view({"get": "list", "post": "create"})),
        url(r'^book/(?P<pk>d+)', BookModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
    ]

    注意:路由分组命名那里要为pk,否则会报错

    这样,简单的代码就实现了我们上面所有的代码

  • 相关阅读:
    MySQL学习-- UNION与UNION ALL
    图解MySQL 内连接、外连接、左连接、右连接、全连接……太多了
    mysql的三种连接方式
    Spring Boot MyBatis配置多种数据库
    @Value注解分类解析
    SpringBoot启动报错Failed to determine a suitable driver class
    idea启动报错:Access denied for user 'root '@'192.168.100.XXX' (using password: YES)
    QStandardItemModel的data线程安全(在插入数据时,临时禁止sizeHint去读model中的data)
    ubuntu 交叉编译qt 5.7 程序到 arm 开发板
    继承QWidget的派生类控件不能设置QSS问题解决(使用style()->drawPrimitive(QStyle::PE_Widget,也就是画一个最简单最原始的QWidget,不要牵扯其它这么多东西)
  • 原文地址:https://www.cnblogs.com/zouzou-busy/p/11565015.html
Copyright © 2011-2022 走看看