前戏
在前面几篇文章里,我们写了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,否则会报错
这样,简单的代码就实现了我们上面所有的代码