DRF 4 视图类路由含源码详解

4,视图类集合,路由

4.1 APIView

APIView是最最基础的视图类,如果只继承APIView,一切都得手写

book_list=Book.objects.all()
book_ser=BookSerializer(book_list,many=True)

手动写业务逻辑处理不同的请求

def get(self,request):

def post(self,request):

视图的类需要写两个,分别对应两个url的有名分组传参和不传参情况.

class BookView(APIView):
    def get(self,request):
        book_list=Book.objects.all()
        book_ser=BookSerializer(book_list,many=True)

        return Response(book_ser.data)
    def post(self,request):
        book_ser = BookSerializer(data=request.data)
        if book_ser.is_valid():
            book_ser.save()
            return Response(book_ser.data)
        else:
            return Response({'status':101,'msg':'校验失败'})


class BookDetailView(APIView):
    def get(self, request,pk):
        book = Book.objects.all().filter(pk=pk).first()
        book_ser = BookSerializer(book)
        return Response(book_ser.data)

    def put(self, request,pk):
        book = Book.objects.all().filter(pk=pk).first()
        book_ser = BookSerializer(instance=book,data=request.data)
        if book_ser.is_valid():
            book_ser.save()
            return Response(book_ser.data)
        else:
            return Response({'status': 101, 'msg': '校验失败'})

    def delete(self,request,pk):
        ret=Book.objects.filter(pk=pk).delete()
        return Response({'status': 100, 'msg': '删除成功'})

路由配置:

url(r'^api/books2/$', views.Books2.as_view()),
url(r'^api/book2/(?P<num>\d+)/', views.Book2.as_view())

4.2 GenericAPIView

简化步骤,在类里直接申明了操作哪张表,调用哪个序列化器:

queryset = models.Booklib.objects
serializer_class = BookModelSerializer

GenericAPIView类里封装的4个常用方法:

get_queryset() 获取多条表查询对象

get_object() 获取一条表查询对象(必须传pk,源码规定的)

-get_object()源码解析
            #如果配置了过滤的话,先过滤queryset对象	
    		queryset = self.filter_queryset(self.get_queryset()) 
    		
            # lookup_url_kwarg就是pk,路由中有名分组分出来的pk
            lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
            # {pk:4}  4 浏览器地址中要查询的id号http://127.0.0.1:8000/books6/4/
            filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
            # 根据pk去queryset中get单个对象
            obj = get_object_or_404(queryset, **filter_kwargs)
            self.check_object_permissions(self.request, obj)
            return obj

get_serializer() 调用序列化类,返回序列化的对象

    def get_serializer(self, *args, **kwargs):
		#这里调用的序列化类是get_serializer_class()返回的序列化类
        serializer_class = self.get_serializer_class()

        return serializer_class(*args, **kwargs)

get_serializer_class() 之所以加这一步是为你提供了选择序列化类的方法,重写该方法能实现一个视图函数配置多个序列化类,根据业务逻辑调用不同的序列化类.

    def get_serializer_class(self):
        
        assert self.serializer_class is not None, (
            % self.__class__.__name__
        )
        return self.serializer_class

调用这些方法举例:

book=self.get_queryset().filter(pk=num).first()
ser_obj=self.get_serializer(book)

注意事项:

由于没有继承Mixin模块的5个类,这里还需要注意以下两点(通常情况下不会单独继承GenericAPIView类的,配合Mixin模块的5个类使用更佳)

  • 反序列化数据化时还需要手动判断数据是否合法,并调用序列化器的save方法
class Books2(GenericAPIView):

    queryset = models.Booklib.objects.all()
    serializer_class = BookModelSerializer

    def get(self,request):

        book=self.get_queryset()
        book_ser=self.get_serializer(book,many=True)

        return CommomResponse(data=book_ser.data,headers={'key':'value'})


    def post(self,request):#创建,修改了id为只读属性,创建时不需要id参数
        book_ser=self.get_serializer(data=request.data)

        if book_ser.is_valid():
            book_ser.save()

            return CommomResponse(data=book_ser.data)
        else:

            return CommomResponse(101,'数据不和法',book_ser.errors)

class Book2(GenericAPIView):
    queryset = models.Booklib.objects
    serializer_class = ser.BookModelSerializer

    def get(self,request,num):
        book=self.get_queryset().filter(pk=num).first()
        ser_obj=self.get_serializer(book)

        return CommomResponse(data=ser_obj.data)


    def delete(self,request,num):
        book=self.get_queryset().filter(pk=num).delete()

        return CommomResponse(msg='删除成功')

    def put(self,request,num):
        book=self.get_queryset().filter(pk=num).first()#只有一个值时一定有用.first()
        ser_obj=self.get_serializer(book,request.data)
        if ser_obj.is_valid():
            ser_obj.save()  #这是序列化器的save方法,不是queryset的
            return CommomResponse(data=ser_obj.data)
        else:
            return CommomResponse(msg='数据不合法')

路由生成:

url(r'^api/books2/$', views.Books2.as_view()),
url(r'^api/book2/(?P<num>\d+)/', views.Book2.as_view())

4.3 Mixns模块的五个扩展类

这五个扩展类就是为了配合GenericAPIView而设计的,两者配合是有可以大大减少重复代码的书写

5个视图扩展类源码分析:

ListModelMixin-->get

获取所有数据,并进行了群查结果的过滤和分页

lass ListModelMixin:
    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)

RetrieveModelMixin-->有参数的get

获取一条数据,不用分页,过滤已经再GenericAPIView的get_object方法中进行了

lass RetrieveModelMixin:
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

CreateModelMixin-->post

UpdateModelMixin-->put

创建和更新原理类似,这里以创建分析

lass CreateModelMixin:
    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 {}

DestroyModelMixin-->delete

lass DestroyModelMixin:

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance):
        instance.delete()

使用:需要哪个功能继承哪个类,在不同请求方法下调用该类对应的方法即可.

必要时需要重写5个类的方法

重写list时需要注意:配置了过滤和分页的情况,可以直接cp源码再修改.

class Books3(GenericAPIView,ListModelMixin,CreateModelMixin):
    queryset = models.Booklib.objects
    serializer_class = ser.BookModelSerializer

    def get(self,request):
        return self.list(request)
    def post(self,request):
        return self.create(request)


class Book3(GenericAPIView,RetrieveModelMixin,DestroyModelMixin,UpdateModelMixin):
    queryset = models.Booklib.objects
    serializer_class = ser.BookModelSerializer

    def get(self,request,pk):
        return self.retrieve(request,pk)

    def put(self,request,pk):
        return self.update(request,pk)

    def delete(self,request,pk):
        return self.destroy(request,pk)

路由配置:

这5个类只是为了配合GenericAPIView跟路由没有半毛钱关系

4.4 九个组合拓展类

这九个类是generics模块将GenericAPIView和五个视图扩展类进行整合得到类(如果你喜欢自动生成路由,那么基本就用不到它们)分别是:

5个基础组合类
CreateAPIView,ListAPIView,RetrieveAPIView,DestroyAPIView,UpdateAPIView 
4个扩展组合类
ListCreateAPIView,RetrieveUpdateAPIView,RetrieveDestroyAPIView,RetrieveUpdateDestroyAPIView 

由于五个视图拓展类会自动调用GenericAPIView的get_object,get_queryset,get_serializer,方法

所以使用方法很简单:只需要指明queryset,serializer_class即可

class Books4(ListCreateAPIView):
    queryset = models.Booklib.objects 
    #这里需要一个queryse对像所以要去到object
    serializer_class = ser.BookModelSerializer

class Book4(RetrieveUpdateDestroyAPIView):
    queryset = models.Booklib.objects
    serializer_class = ser.BookModelSerializer

路由生成:

url(r'^api/books2/$', views.Books2.as_view()),
url(r'^api/book2/(?P<num>\d+)/', views.Book2.as_view())

4.5ViewSetMixin

由于APIView源码中用反射的方式直接获取视图对象的方法属性执行,所以APIView有个致命的弱点,即只继承了APIView的类中的方法属性名需要和请求方式对应才能被自动调用执行.

ViewSetMixin的出现就是为了改变这种现状的,即继承了ViewSetMixin的类可以自行命名函数,而且可以被调用,

但是路由配置的方法就改变了.

实现原理:

1,首先需要在路由配置加入参数actions={'k':'v'},k是请求方法,v是视图类对象的方法名
path('books/', views.BookViewSet.as_view(actions={'get':'get_1'}))

2,ViewSetMixin重写了as_view的view方法
def as_view(cls, actions=None, **initkwargs):
    #这里比APIView的参数多了一个actions
    
    def view(request, *args, **kwargs):
        self = cls(**initkwargs)


        self.action_map = actions

		#将请求方式和响应方法重新建立关系
        for method, action in actions.items():
            handler = getattr(self, action)
            setattr(self, method, handler)

            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get

                self.request = request
                self.args = args
                self.kwargs = kwargs

                # And continue as usual
                return self.dispatch(request, *args, **kwargs)

注意:因为ViewSetMixin重写了as_view,查找as_view时会现去继承的第一父类的名称空间查找,如果APIView在前则,不会执行重写的as_view导致功能失效.

使用举例:

class Book05(ViewSetMixin,APIView):
    def get_one_book(self,request,pk):

        obj_list = models.Booklib.objects.filter(pk=pk).first()
        ser_book = ser.BookModelSerializer(obj_list)

        return CommomResponse(data=ser_book.data)

    def get_books(self,request):
        obj_list = models.Booklib.objects.all()
        ser_book=ser.BookModelSerializer(obj_list,many=True)

        return CommomResponse(data=ser_book.data)
     

由于ViewSetMixin需要和APIView或GenericAPIView配合使用所以就有了下面两个类:

ViewSet=ViewSetMixin+APIView

GenericViewSet=ViewSetMixin+GenericAPIView

其实Mixins模块的5个拓展类也是经常使用的,所以就有了ModelViewSet,该类继承了6个父类

ModelViewSet=GenericViewSet+CreateModelMixin+DestroyModelMixin+ListModelMixin+RetrieveModelMixin+UpdateModelMixin

路由配置:此处路由配置以及改变了,注意actions={'get':'get_books'}

url(r'^api/books5/$', views.Book05.as_view(actions={'get':'get_books'})),
url(r'^api/book5/(?P<pk>\d+)/', views.Book05.as_view(actions={'get':'get_one_book'})),

4.6自动生成路由

每次都在路由配置参数太麻烦了,所以作者就设计了自动生成路由的类,想要使用自动生成路由,视图类必须继承ViewSetMixin类或其子类即ViewSet或GenericViewSet或ModelViewSet.(其实还有其他的类只是不常用)

使用步骤:在路由层

第一步:导入routers模块

第二步:实例化得到路由对象

第三步:调用路由对象的注册功能

第四部:自动生成的路由,加入到原路由列表中

#  继承自视图类,ModelViewSet的路由写法(自动生成路由)
	-urls.py
    
        # 第一步:导入routers模块
        from rest_framework import routers
        
        # 第二步:有两个类,实例化得到对象
        # routers.DefaultRouter 生成的路由更多
        # routers.SimpleRouter
        router=routers.DefaultRouter()
        
        # 第三步:调用对象的注册功能
        # router.register('前缀','继承自ModelViewSet视图类','别名')
        #如果使用了路由分发,这里前缀也不需要写了,只写一个''就行
        router.register('books',views.BookViewSet) 

        # 第四步
        urlpatterns+=router.urls
        

4.7 action的使用

使用了自动生成路由后没法直接给路由加actions参数了,但可以再视图类里导入actions装饰器实现

第一步:from rest_framework import decorators

第二步:第一个参数传入列表,列表中写请求方式['GET','POST'],第二个参数传布尔值,需要携带参数就写True

# action干什么用?为了给继承自ModelViewSet的视图类中定义的函数也添加路由使用
class BookViewSet(ModelViewSet):
    queryset =Book.objects.all()
    serializer_class = BookSerializer
    
    @action(methods=[get,],detail=True)
    #如果detail=True 会生成一条带有有名分组的路由匹配:如下
    #^books/(?P<pk>[^/.]+)/get_1/$ [name='book-get-1']
    #具体形如:/books/1/get_1 ,
    
    #如果detail=False 会生成一条普通的路由:如下
    #^books/get_1/$ [name='book-get-1']
    #具体形如:/books/get_1 ,
    
    def get_1(self,request,pk):
        book=self.get_queryset()[:2]  # 从0开始截取一条
        ser=self.get_serializer(book,many=True)
        return Response(ser.data)

4.8 使用技巧总结

1,如果想要自动配置路由视图类必须继承ViewSetMixin类或其子类

2,GenericAPIView和五个拓展类搭配使用效果更佳

3,如果想使用过滤,分页功能视图类必须继承GenericAPIView类或其子类且需要搭配五个拓展类使用

4,具体选择哪些类需要结合业务需求和以上3条技巧考虑

5,如果业务需求很复杂,可以考虑重写某些类的方法实现(有时间再总结)

posted @ 2020-07-10 20:30  Franciszw  阅读(383)  评论(0编辑  收藏  举报