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方法代码如下

DRF--重写views
 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方法(第十七行)

DRF--重写views
 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,否则会报错

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

 

上一篇:Django REST framework基础:序列化


下一篇:drf 视图功能