DRF从入门到精通六(排序组件、过滤组件、分页组件、异常处理、排序过滤源码分析、异常处理源码分析)

一、排序组件

一般涉及到了查询所有才有排序,对于表模型查询所有数据时需要根据某个字段进行排序时,我们就可以使用到REST framework提供的内置排序组件OrderingFilter来帮助我们快速指名数据按照指定字段进行排序

使用方法

  1. 导入DRF内置排序组件:from rest_framework.filters import OrderingFilter
  2. 在视图类中设置类属性:filter_backends=[OrderingFilter]
    注意这里能使用这个类属性,只有是继承了GenericAPIView才行。否则无法使用
    所以像继承APIView则无法使用内置过滤器
  3. REST framework会在请求的查询字符串参数中检查是否包含了ordering参数
  4. 如果包含了ordering参数,则按照ordering参数指明排序字段对数据集进行排序
  5. 前端可以传递的ordering参数的可选字段值需要再ordering_fields中指明

继承GenericAPIView使用DRF内置排序组件

	'需要有表数据,我这里直接沿用上一篇博客中的使用,然后使用自动生成路由的方式'
	from rest_framework.generics import ListAPIView
	from rest_framework.viewsets import ViewSetMixin
	from rest_framework.filters import OrderingFilter
	from .serializer import BookSerializer
	from . import models

	class BookView(ViewSetMixin,ListAPIView):
	    queryset = models.Book.objects.all()
	    serializer_class = BookSerializer
	    
	    '配置属性'
	    filter_backends = [OrderingFilter]
	    '必须指定表模型数据字段'
	    ordering_fields = ['price'] # 可以写多个排序字段
	    # ordering_fields = ['price','id']
	    
	    '127.0.0.1:8000/api/v2/books/?ordering=-price 倒序'
	    '127.0.0.1:8000/api/v2/books/?ordering=price 升序'

在这里插入图片描述


继承APIView的是使用不了以上DRF提供的排序组件,需要自己写,自己从请求地址中取出排序规则,然后自己排序

继承APIView编写排序

	from rest_framework.views import APIView
	from rest_framework.response import Response
	from rest_framework.viewsets import ViewSetMixin
	from rest_framework.mixins import ListModelMixin
	
	'这里我还是沿用上面自动生成路由的方式,所以需要配置ViewSetMixin'
	
	class BookView(ViewSetMixin, APIView, ListModelMixin):
		'由于是APIView需要自己重写list方法,在自动生成路由中是get:list'
	    def list(self, request, *args, **kwargs):
	        '从地址栏中取出过滤条件'
	        print(request.query_params)  # ?ordering=-price,id
	        query_params = request.query_params  # {ordering:price}
	        '''支持多个条件排序ordering=-price,id'''
	        # try:
	        '127.0.0.1:8000/api/v2/books/?ordering=-price,id'
	        if ',' in query_params.get('ordering'):
	            query = query_params.get('ordering').split(',')
	            book_list = models.Book.objects.all().order_by(*query)
	        else:
	            book_list = models.Book.objects.all().order_by(query_params.get('ordering'))
	
	        ser = BookSerializer(instance=book_list, many=True)
	        return Response(ser.data)

在这里插入图片描述


二、过滤组件

restful规范中,要求请求地址中带过滤条件,五个接口中,只有查询所有接口需要过滤和排序。

其实上面排序使用的就是DRF内置过滤器,因为排序本就是过滤的一种特殊情况,所以这里就不在过多介绍了

继承GenericAPIView使用DRF内置过滤器实现过滤

	from rest_framework.filters import SearchFilter
	from rest_framework.viewsets import ViewSetMixin
	from rest_framework.generics import ListAPIView
	from rest_framework.filters import OrderingFilter,SearchFilter
	
	class BookView(ViewSetMixin, GenericAPIView, ListModelMixin):
	    queryset = models.Book.objects.all()
	    serializer_class = BookSerializer
	    'SearchFilter,使用的是模糊匹配'
	    filter_backends = [SearchFilter]
	    
	    '''只能填写表中字段'''
	    search_fields = ['name']
	    search_fields = ['name','price']  可多字段过滤
	    # 127.0.0.1:8000/api/v2/books/?search=东
	    # 127.0.0.1:8000/api/v2/books/?search=46  
	    '因为是多字段过滤,并且SearchFilter是模糊匹配,所以如果多字段中有一样的东西,如1都会被查到'

使用DRF内置过滤器,它搜索结果的关系为or(或)的关系,不是and关系。并且只能是使用关键字search才能使用,如果我们想要用name=东&price=66这种方式的得使用别的方法来实现

在这里插入图片描述


使用第三方模块django-filter实现and关系的过滤

	'首先需要安装第三方模块:pip install django-filter'
	from django_filters.rest_framework import DjangoFilterBackend
	from rest_framework.viewsets import ViewSetMixin
	from rest_framework.generics import ListAPIView
	'使用这个第三方模块可以实现and关系,并且只能是精准匹配。并且暂不支持or关系'
	class BookView(ViewSetMixin,ListAPIView):
	    queryset = models.Book.objects.all()
	    serializer_class = BookSerializer
	    
	    filter_backends = [DjangoFilterBackend]
	    filterset_fields = ['name', 'price']
	    # 127.0.0.1:8000/api/v2/books/?name=东游记&price=46 精准匹配
	    '但是对于django-filter来讲它是支持扩写的,所以是可以支持模糊匹配,具体操作自寻查找'

在这里插入图片描述

使用这个django-filter还是有局限性的,无法实现or关键以及模糊匹配,那么我们可以使用自定制过滤类


自定制过滤类

	使用步骤:
		1.定义一个过滤类,并且继承BaseFilterBackend
		2.重写filter_queryset方法,并且在内部完成过滤规则
		3.在视图类中配置自定义的过滤类

	from rest_framework.filters import BaseFilterBackend
	class CommonFilter(BaseFilterBackend):
	    def filter_queryset(self, request, queryset, view):
	        '''queryset是之前所有数据,models.Book.object.all()'''
	        # request是当次请求
	        # query_params = request.query_params
	        '方式一:'
			name = request.query_params.get('name',None)
	        price = request.query_params.get('price',None)
	        res = queryset.filter(name__contains=name,price=price)
	        
			'方式二:'
	        if request.query_params.get('name'):
            	queryset = queryset.filter(name__contains=request.query_params.get('name'))
	        if request.query_params.get('price'):
	            '支持链式调用'
	            queryset = queryset.filter(price=request.query_params.get('price'))
	        return queryset  # 这里返回过滤后的对象 res

	
   	from .filters import CommonFilter  
	class BookView(ViewSetMixin,ListAPIView):
	    queryset = models.Book.objects.all()
	    serializer_class = BookSerializer
	    '无需再配置字段了,因为在自定制类中已经写好了过滤规则,所以在视图类中无需配置'
	    filter_backends = [CommonFilter]
	    # 127.0.0.1:8000/api/v2/books/?name=游记&price=666
	    '这样就实现了模糊匹配name字段并且精准匹配price字段,以及查询单个字段的规则'

在这里插入图片描述


排序搭配过滤使用

	from .filters import CommonFilter  # 使用的是上面的自定制过滤类
	from rest_framework.filters import OrderingFilter
	class BookView(ViewSetMixin,ListAPIView):
	    queryset = models.Book.objects.all()
	    serializer_class = BookSerializer
	    filter_backends = [OrderingFilter,CommonFilter]  # 排序加过滤,从左到右依次执行
	    ordering_fields = ['price','id']

在这里插入图片描述


三、分页组件

分页只针对查询所有的接口,其他四个接口不需要分页。drf内置了三个分页器,对应三种分页方式,内置的分页类不能直接使用,需要继承,定制一些参数后才能使用。一个接口只能有一种分页方式,不能混合分页方式

分页器一:Pagination(基本分页)

通过自定义Pagination类,来为视图添加不同分页行为。在视图中通过pagination_clas属性来指明。

	from rest_framework.pagination import PageNumberPagination

	class CommonPageNumberPagination(PageNumberPagination):
	    page_size = 3 # 默认每页显示的条数
	    page_query_param = 'page' # 使用该关键字指定页码(客户端使用)
	
	    page_size_query_param = 'size' # 通过该关键字可以手动指定页面显示的数据条数(客户端使用)
	
	    max_page_size = 5 # 只针对客户端手动指定页面显示的数据条数做出一个限制,但不影响page_size属性。

视图类

	'导入配置的分页类'
	from .pagination import CommonPageNumberPagination
	
	from rest_framework.viewsets import ViewSetMixin
	from rest_framework.generics import ListAPIView
	from . import models
	from .serializer import BookSerializer
	from .filters import CommonFilter
	from rest_framework.filters import OrderingFilter
	
	class BookView(ViewSetMixin,ListAPIView):
	    queryset = models.Book.objects.all()
	    serializer_class = BookSerializer
	    '注意分页方式一个接口只能使用一种,所以没有加中括号'
	    pagination_class = CommonPageNumberPagination
	    '''使用分页并不影响排序和过滤'''
	    filter_backends = [OrderingFilter,CommonFilter]
	    filterset_fields = ['name', 'price']

	# 127.0.0.1:8000/api/v2/books/?page=2
	# 127.0.0.1:8000/api/v2/books/
	# 127.0.0.1:8000/api/v2/books/?page=2&size=3

在这里插入图片描述

响应时额外携带了3个参数,分别是:

  1. count:book接口的数据总量
  2. next:下一页的URL
  3. previous:上一页的URL,如果没有上一页返回None

当然,我们也可以手动指定页面显示的条数,根据page_size_query_param属性值来作为关键字。

在这里插入图片描述

可以看到我们需要显示的是6条数据,而后端只响应了5条,这是因为max_page_size属性值的作用:限制了客户端手动获取数据时最大返回的数据条数。


分页器二:LimitOffsetPagination(偏移分页)

偏移分页,该分页组件作用是:从哪一条数据之后开始显示,以及制定数据显示的条数

前端访问形式:http://127.0.0.1:8080/book/?offset=2&limit=4,这表示从第3条数据之后显示4条数据

自定义分页类

	from rest_framework.pagination import LimitOffsetPagination
	
	class CommonLimitOffsetPagination(LimitOffsetPagination):
		# 等同于上面的:page_size
	    default_limit = 2  # 如果前端没有手动指定获取的数据条数时,使用该属性值
	
	    limit_query_param = 'limit'  # 前端通过该关键字指定数据条数
	    '每页显示条数,查询的条数  例子 ?limit=100 意思就是每页显示100条,如果不传应用default_limit的参数'
	
	    offset_query_param = 'offset'  # 通过该关键字指定从哪条数据之后开始获取数据
	    '偏移量  举个例子offset=6&limit=30  意思就是从第6条开始,拿30条'
	    
	    # 等同于上面的:max_page_size
	    max_limit = 5  # 只限制limit_query_param能够获取的最大数据条数,而不影响default_limit

视图类

	'导入配置的分页类'
	from .pagination import CommonPageNumberPagination
	
	class BookView(ViewSetMixin,ListAPIView):
	    queryset = models.Book.objects.all()
	    serializer_class = BookSerializer
	    '注意分页方式一个接口只能使用一种,所以没有加中括号'
	    pagination_class = CommonPageNumberPagination

		# http://127.0.0.1:8000/api/v2/books/?limit=6&offset=2  从第2条开始拿6条

在这里插入图片描述

从第二条数据之后开始指定获取数据的条数,使用了limit指定了获取6条,但是被max_limit=5给限制了,所以后端只能返回2条数据,如果不使用limit指定获取的数据条数,那么默认使用default_limit=2后端会返回2条数据。


分页器三:CursorPagination(游标分页)

自定义分页类

	from rest_framework.pagination import CursorPagination
	class CommonCursorPagination(CursorPagination):
	    cursor_query_param = 'cursor'  # 按游标查询的查询条件,value值前端是不知道的,只能通过后台返回
	    page_size = 2  # 每页显示多少条啊
	    ordering = 'id'  # 排序规则,必须是表中的字段

视图类

	from .pagination import CommonCursorPagination
	class BookView(ViewSetMixin,ListAPIView):
	    queryset = models.Book.objects.all()
	    serializer_class = BookSerializer
	    '注意分页方式一个接口只能使用一种,所以没有加中括号'
	    '另外使用游标分页的方式后就不能再其中使用排序了,因为其内部已经排好序了'
	    pagination_class = CommonCursorPagination
	   '并且只能选择上一页和下一页,不能指定跳转某一页,但是速度快,针对与特别大的数据量,游标分页有优势'

在这里插入图片描述


四、异常处理

REST framework提供了异常处理,我们可以自定义异常处理函数。主要是为了DRF在运行过程中发生的错误进行一个捕获,然后响应友好的提示信息给前端。

自定义异常处理函数:(定制一个异常处理统一的返回格式)

	from rest_framework.views import exception_handler,Response
	# drf的异常处理是exception_handler处理了,到那时没处理非drf的异常
	# 自己写个函数,处理drf异常和自己的异常,以后只要是出了异常都会走这里
	def Common_exception_handler(exc, context):
	    # exc:异常信息
	    # context:执行请求的视图、args、kwargs参数、request请求
	
	    res = exception_handler(exc, context)
	
	    # 在此处补充自定义的异常处理
	    if res:  # 有值说明是drf的异常,如果没有值说明是自己的异常
	        # data = {'detail': exc.detail}
	        detail = res.data.get('detail') or "drf异常,请联系系统管理员"
	        return Response({'code':666,'message':detail})
	    else:
	        return Response({'code':777,'message':f"系统异常,请联系管理{str(exc)}"})

在配置文件中声明自定义的异常处理

	REST_FRAMEWORK = {
	    'EXCEPTION_HANDLER': 'appo1.exception.Common_exception_handler',
	}

如果未声明,会采用默认的方式,如下:

	REST_FRAMEWORK = {
	    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
	}

视图类

	'''全局异常处理'''
	from rest_framework.exceptions import AuthenticationFailed,APIException
	'''
	Drf中无论在三大认证还是视图类中 方法执行报错包括主动抛异常
	都会执行一个函数excption_exception处理异常的函数
	只要出了异常APIView的dispatch中就可以捕获到 
	执行配置文件中的'EXCEPTION_HANDLER':'rest_framework.views.exception_handler'
	'''
	class UserView(ViewSetMixin, APIView):
	    def create(self, request):
	        raise Exception('你出错了')
	
	        # drf默认能处理自己的异常,它的异常都是继承自APIException的异常
	        # raise AuthenticationFailed('认证失败')
	        return Response({'code':100,'msg':'1111'})

在这里插入图片描述


五、排序及过滤组件源码分析

首先我们从上面几个知识点知道,排序组件OrderingFilter它主要就是用于查询所有数据时进行排序的,并且是基于GenericAPIView才可使用的,因为是查询所有,所以配合ListModelMixin使用。然后在视图类中配置filter_backendsfilterset_fields两个类属性就可以实现排序了。

而排序本就是属于过滤的一种特殊情况,所以过滤组件SearchFilter它和排序使用前提都是一样的,只是定义的雷属性略微不同而已,它需要再视图类中配置filter_backends(这个是一模一样,由此可见是可以排序过滤放在一个里面的),另一个类属性search_fields就是过滤需要使用的,配置了这两个就可以实现过滤了。

前要list方法源码的过滤解读

	'因为是查询所有才使用的排序和过滤,所以我们得从ListModelMixin里的list方法来开头'
	class ListModelMixin:
	    def list(self, request, *args, **kwargs):
	    	'''
	    	queryset = self.filter_queryset(self.get_queryset())
	    	这里的self是视图类的对象
	    	首先这里的self.get_queryset()就是继承GenericAPIView后拿到的所有数据
	    	然后执行了self.filter_queryset(传入了总数据),我们知道视图类是没有这个方法的,
	    	所以我们得去GenericAPIView中找找有没有这个方法。可以看到我们最后是找到了
	    	'''
	    	'从GenericAPIView中找到的filter_queryset方法'
	    	def filter_queryset(self, queryset):
	    		'''
	    		这里是GenericAPIView所以self还是视图类的对象,而这个filter_backends就是我们使用排序组件
	    		需要配置的其中一个类属性,这个backends里面就是配置在视图类中的一个个的过滤类
	    	(排序本就是过滤的一种特殊情况),所以在视图类中这个类属性你可以写成单个,也可以用一个列表括起来
	    	因为在这里它也给这个类属性弄成了一个列表,然后循环遍历这个一个个的过滤类,赋值给backend。
	    		'''
		        for backend in list(self.filter_backends):
		        	'''
		        	然后到这里它把backend加括号,就是把一个个过滤类加括号实例化得到对象,然后调用了
		        	filter_queryset(self.request, queryset, self),这就是为什么我们自定义过滤类的时候
		        	需要重写filter_queryset方法的原因。然后这里传入了几个参数,
		        	self.reqeust就是视图类的request,queryset就是视图类的queryset对象就是表模型总数据,
		        	然后就是self就是视图类的对象。所以自定义过滤类中也需要写着几个参数的原因。在这里就懂了
		        	每个参数接收到是那个数据了。所以这个queryset就是经过过滤规则后的返回的数据
		        	'''
		            queryset = backend().filter_queryset(self.request, queryset, self)
		        
		        '因为filter_backends是个列表,所以最后会依次经过所有的过滤类操作完毕后,返回queryset对象'
		        return queryset

			'所以这里我们就知道了,这个list方法里面的queryset就是完成了所有过滤类操作后的返回的queryset对象'
	        queryset = self.filter_queryset(self.get_queryset())
			
			'serializer就是序列化类,然后给queryset对象传入进去后返回给前端的就是过滤后的数据'
			所以无论是自定义还是第三方还是内置本质都是在上面进行了过滤后,进行序列化类返回给前端过滤后的数据
	        serializer = self.get_serializer(queryset, many=True)
	        return Response(serializer.data)

OrderingFilter排序源码分析

经过上面的list方法里面的过滤排序源码解读后,我们知道,无论是自定义还是第三方亦或者是DRF内置的排序组件内部都是重写了filter_queryset方法,然后在里面进行过滤、排序规则的书写。下面我们来看看``

   def filter_queryset(self, request, queryset, view):
   		'通过对视图类中的排序字段+前端View传入的条件request.query_params.get拿到  (ordering=price)'
    	'对查询数据进行排序,这里的get_ordering方法里面就是书写的获取排序规则,具体我就不在进去看了'
        ordering = self.get_ordering(request, queryset, view)
		'[ordering=price,id]'
		'具体想验证是否是列表的样子可以直接在这里打印ordering'
        if ordering:
        	'然后就是当有排序规则的时候,执行这里,使用order_by方法对查询总数据进行排序'
            return queryset.order_by(*ordering)
            
		'最后进行排序后,返回给queryset对象最终返回给了前端,如果没有排序规则就直接返回查询总数据'
        return queryset

SearchFilter过滤源码分析

同上排序的一样,我们直接去DRF内置的过滤组件源码中找它重写的filter_queryset方法来看看怎么实现的

    def filter_queryset(self, request, queryset, view):
    	'这里获取查询字段和查询关键字,是从视图类中拿到的'
        search_fields = self.get_search_fields(view, request)
        '拿到前端传入的查询字段'
        search_terms = self.get_search_terms(request)
		
		'当从上面两个钟都没有拿到查询字段时,直接返回原始的查询数据(没过滤的)'
        if not search_fields or not search_terms:
            return queryset
		
		'构建ORM查询表达式,此处你可以直接使用debug调试看看具体怎么玩的'
        orm_lookups = [
        	'这个construct_search进行了ORM操作并且是以模糊查询的方式'
            self.construct_search(str(search_field))
            for search_field in search_fields
        ]
		
		'使用Q对象构建查询条件'
        base = queryset  # 在这里把queryset返回的赋值给了base
        conditions = []
        for search_term in search_terms:
            queries = [  
            	'在这里使用Q对象,给每个查询关键字床架一个查询条件,'
                models.Q(**{orm_lookup: search_term})
                for orm_lookup in orm_lookups
            ]
            '然后在这里使用reduce(operator.or_, queries)将它们组成了或(or)关系的条件列表'
            conditions.append(reduce(operator.or_, queries))
        '在这里将组成的或关系的条件列表应用到查询数据集中,实现过滤搜索,并且使用reduce将所有条件组成and关系'
        queryset = queryset.filter(reduce(operator.and_, conditions))
        
        return queryset

六、全局异常处理源码分析

DRF中,继承APIView后,它的执行流程是先去除了所有请求的csrf认证,然后把视图类的request对象变成了新的request对象,新的reqeust对象是DRF的,但是以前Django的request对象用起来是一样的,同时把新的reqeust对象放到了视图类的对象中,然后在执行视图类的方法之前,又执行了三大认证。最后就是在执行三大认证或视图类方法的过程中只要报错了,就会被全局异常捕获处理。而在这里我们要说的就是这个DRF执行流程中最后一个环节,全局异常处理。

	'通过我们对APIView执行流程及源码的了解,我们知道,在它的执行流程中,在dispatch方法里面有一个全局异常处理'
    def dispatch(self, request, *args, **kwargs):
        try:
        	'三大认证执行'
            self.initial(request, *args, **kwargs)
			
			'视图类的方法开始执行'
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed

            response = handler(request, *args, **kwargs)
            '视图类方法结束执行'
		
		'当三大认证或视图类方法报错,执行全局异常处理捕获'
        except Exception as exc:
        	'''
        	这里self是视图类的对象,现在在它的父类APIView里面,所以我们从APIView中找找有没有
        	这个handle_exception方法,另外这个括号里面的exc就是异常对象
        	'''
            response = self.handle_exception(exc)


	'最后我们在APIView里面找到了handle_exception方法'
   def handle_exception(self, exc):
	  '如果没有认证通过,http响应状态码变成403,并且往响应头中放入了数据'
       if isinstance(exc, (exceptions.NotAuthenticated,
                           exceptions.AuthenticationFailed)):
           # WWW-Authenticate header for 401 responses, else coerce to 403
           '如果异常是 NotAuthenticated 或 AuthenticationFailed,处理授权相关的逻辑'
           auth_header = self.get_authenticate_header(self.request)
			
			'如果存在认证头,将其添加到异常中'
           if auth_header:
               exc.auth_header = auth_header
           else:
           	   '如果不存在认证头,将状态码更改为 403 FORBIDDEN'
               exc.status_code = status.HTTP_403_FORBIDDEN
               
               
	 '这里的exception_handler就是我们自定义异常处理重写的函数'
	 '''
	self是视图类对象,已知视图类对象中没有get_excpetion_handler这个方法,然后去父类APIView中查找到了
	def get_exception_handler(self):
       return self.settings.EXCEPTION_HANDLER
    可以看到,它是去配置文件中拿这个EXCEPTION_HANDLER了,这也是为什么我们自定义的异常处理需要配置,
    如果在配置文件中没有拿到,就会去DRF的配置文件中拿到它
        'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
        这个我放在最后去读它究竟是什么操作的
	'''
       exception_handler = self.get_exception_handler()
       
		
	  '获取异常处理上下文'
       context = self.get_exception_handler_context()

	  '''
	 这里的exception_handler就是我们自定义异常处理重写的函数,这也就是为什么我们需要传两个参数的原因	
	  exc就是错误对象,context就是上下文,它是一个对象,并且里面存着当次请求的request和视图类
	  这个response就是最后返回的异常结果,这就是为什么我们重写后最后需要返回一个响应对象
	  '''
       response = exception_handler(exc, context)
		
	  '''
	  如果响应对象是None就是抛出异常,就不会捕获,直接返回给前端,所以这个全局异常只能捕获DRF的异常
	  这也是为什么我们需要重写,就是为了返回全局异常无法捕获的异常
	  '''
       if response is None:
           self.raise_uncaught_exception(exc)
           '''
			也可以看一下这个方法,还是一样self是视图类对象,然后在父类APIView中找到了这个方法
			def raise_uncaught_exception(self, exc):
		        if settings.DEBUG: 在调试模式下处理未捕获的异常
		            request = self.request  获取当次请求
		            请求接受的渲染格式
		            renderer_format = getattr(request.accepted_renderer, 'format')
		            然后根据渲染格式渲染到页面
		            use_plaintext_traceback = renderer_format not in ('html', 'api', 'admin')
		            request.force_plaintext_errors(use_plaintext_traceback)
		            
		        最后将未捕获的异常直接渲染给页面
		        raise exc
			'''
		
	  '这里将响应对象的exception属性设置为True,表示异常已被处理'
       response.exception = True
       '最后返回处理后的响应对象'
       return response


	'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
	接下来我们单独去读读DRF默认配置的异常处理
	def exception_handler(exc, context):
   		'看到DRF默认配置的全局异常处理,就可以知道为什么我们要那么配置自定义的了,就是抄着写的'
   		'这里就是自己的异常'
	    if isinstance(exc, Http404):
	        exc = exceptions.NotFound()
	    elif isinstance(exc, PermissionDenied):
	        exc = exceptions.PermissionDenied()
		
		'这里是DRF的所有异常,只要是DRF的异常都会走这里,因为DRF的异常都继承了APIException'
	    if isinstance(exc, exceptions.APIException):
	        '如果异常的详细信息是列表或字典,直接使用该信息'
	        if isinstance(exc.detail, (list, dict)):
	            data = exc.detail
	        else:
	           '否则,将详细信息包装在 detail 键下'
	            data = {'detail': exc.detail}
	
	        '返回处理后的响应'
	        '''
	        所以这里有两个情况
	        Response(data:{'detail': exc.detail})
	        Response(data:错误信息(字典或列表))
	        所以这种情况,这也是为什么我们自定义的时候,取detail时,怕取不到设置了自定义的一个异常信息
	        或者在or一个data这样会更好一些,例子:
	        detail = res.data.get('detail') or res.data or "drf异常,请联系系统管理员"
        	return Response({'code':666,'message':detail})
	    	
	        '''
	        return Response(data, status=exc.status_code, headers=headers)
		
		'对于其他类型的异常,返回 None,让 DRF 使用默认的异常处理机制,就是不处理直接返回给前端页面'
	    return None

分析了异常处理源码,我们在自定义异常处理可以写的更规范一些

	from rest_framework.views import exception_handler,Response
	
	def Common_exception_handler(exc,context):
	    '''
	    :param exc: 异常信息
	    :param context: 执行请求的视图、request
	    :return:
	    '''
	    response = exception_handler(exc, context)
	    '''因为DRF的异常已经处理了,所以我们只需要统一返回格式即可'''
	    print(type(exc))  # 查看异常信息
	    if response:  # 当有值就是drf的异常
	        detail = response.data.get('detail') or response.data or 'drf异常,请联系管理员'
	        return Response({'status': 10011, 'message': detail})
	    else:  # 没有值就是自己的异常
	        if isinstance(exc, IndexError):
	            return Response({'status': 10010, 'message': '越界异常'})
	        else:
	            return Response({'status': 666, 'message': f"操作失败,请稍后在尝试:{str(exc)}"})


	'然后在配置文件中配置一下异常处理'
	REST_FRAMEWORK = {
	    'EXCEPTION_HANDLER': 'appo1.exception.Common_exception_handler',
	}


	'视图类'
	class UserView(ViewSetMixin, APIView):
	    def create(self, request):
	    	'直接测试抛异常,这个是自己的异常'
	        raise Exception('你出错了')
	
	        # drf默认能处理自己的异常,它的异常都是继承自APIException的异常
	        # raise AuthenticationFailed('认证失败')
	
	        return Response('ok')

七、分页源码分析

我们知道分页也是只针对查询所有接口的时候才使用的,所以我们还是从ListModelMixin开始读源码

	'这个ListModelMixin源码从上面几个源码中我们基本都在这里读过,所以我这里就直接删减只看分页的'
	class ListModelMixin:
	    def list(self, request, *args, **kwargs):
			'分页开始'
			'''
			这里的self还是视图类的对象
			所以我们需要找这个self.paginate_queryset方法在哪里,这个ListModelMixin中是没有的
			当然我们的视图类更是没有,所以我们得去继承的父类中GenericAPIView中查找,最后找到了
			'''
		'''
		通过下面对self.paginate_queryset方法追代码知道了,这里就是调用了我们写的paginate_queryset方法
		然后返回了分页后的queryset对象,为什么是queryset对象?,因为下面的get_serializer里面第一个参数
		instance就是对应的queryset对象,
		'''
	        page = self.paginate_queryset(queryset)
	        
	        '当page有值时执行'
	        if page is not None:
	        	# 完成序列化
	        	'然后在这里把page放到了序列化类中,也就是instance中'
	            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)
	
	'从GenericAPIView中找到了paginate_queryset方法'
	def paginate_queryset(self, queryset):
		'''
		这里是self也是视图类的对象,这里就是如果视图类中没有这个paginator,就相当于说不分页。
		如果视图类中有这个paginator就进行分页操作,所以我们来看看这个paginator是什么,它是一个伪装成属性的
		方法。直接ctrl+鼠标左键点进去看看
	    @property
	    def paginator(self):
	    	# 检查是否已经存在分页器实例
	    	检查视图类对象中是否有_paginator这个属性,如果没有就执行
	        if not hasattr(self, '_paginator'):
	        	# 如果尚未创建_paginator(分页器),执行以下逻辑
	        	
	        	'这里是如果在视图类的对象中pagination_class这个对象设置为空的时候执行'
	            if self.pagination_class is None:
	            	# 如果没有设置分页类,将分页器设置为 None
	                self._paginator = None
	            else:
	            	# 如果设置了分页类,创建相应的分页器实例
	            	我们知道,如果有这个pagiination_class就把它赋值给self._paginator对象,
	            	所以我们看看self.pagination_class是什么,
	            	    pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
	            	
	            	#可以看到我们查看到的self.pagination_class就是我们配置的分页类,如果我们没有配置
	            	就是用上面DRf默认配置的,如果配置就使用自己的配置的。最终加括号执行。
	            	所以我们之前都是局部使用的,我们也可以配置在配置文件中,让全局使用
	                self._paginator = self.pagination_class()
	            
	        # 返回分页器实例(可能为 None)
	        '返回分页类的对象'
	        return self._paginator
		'''
       if self.paginator is None:'通过上面的追代码我们知道了self.paginator就是配置在视图类中的分页类的对象'
           return None
           
       '所以这里是调用我们配置的分页类对象的paginate_queryset对象,这也是为什么我们重写分页类中需要重写它'
       '这里的queryset就是视图类中配置的queryset,也就是表模型对象的所有数据,self.request就是视图类的,view就是视图类'
       return self.paginator.paginate_queryset(queryset, self.request, view=self)

基于APIView实现分页功能

通过上面的源码分析,我们就可以自己基于APIView实现分页功能了

	'''基于APIView实现分页'''
	from rest_framework.viewsets import ViewSet
	from .pagination import CommonPageNumberPagination,CommonLimitOffsetPagination
	from .serializer import BookSerializer
	from rest_framework.response import Response
	
	class BookView(ViewSet):
	    def list(self, reqeust):
	        queryset = models.Book.objects.all()
	        # 调用咱们写的分页类对象的paginate_queryset方法返回了,分页后的queryset对象
	        '''使用PageNumberPagination(普通分页)'''
	        # pagenation = CommonPageNumberPagination()  # 实例化得到对象
	        '''使用LimitOffsetPagination(偏移分页)'''
	        # pagenation = CommonLimitOffsetPagination()  # 实例化得到对象
	        '''使用CursorPagination(游标分页)'''
	        pagenation = CommonCursorPagination()  # 实例化得到对象
	        page = pagenation.paginate_queryset(queryset, reqeust, self)
	        serializer = BookSerializer(page, many=True)  # page分页后的对象
	        '''使用它自己的方法(三种分页方式都兼容)'''
	        # return pagenation.get_paginated_response(serializer.data)
	        '''
	        使用定制的分页返回格式,得去pagination源码里面的对应每种分页方法中的
	        def get_paginated_response(self, data):方法里面看返回格式怎么写
	        '''
	        '''也可以自己定制PageNumberPagination(普通分页)'''
	        return Response({
	           'status': 100,
	           'message': '查询成功',
	           'count': pagenation.page.paginator.count,
	           'next': pagenation.get_next_link(),
	           'previous': pagenation.get_previous_link(),
	           'results': serializer.data,
	        })
	        '''定制LimitOffsetPagination'''
	        return Response({
	            'status': 100,
	            'message': '查询成功',
	            'count': pagenation.count,
	            'next': pagenation.get_next_link(),
	            'previous': pagenation.get_previous_link(),
	            'results': serializer.data,
	        })
	
	        '''定制CursorPagination'''
	        return Response({
	            'status': 100,
	            'message': '查询成功',
	            'next': pagenation.get_next_link(),
	            'previous': pagenation.get_previous_link(),
	            'results': serializer.data,
	        })

使用自己定制的PageNumberPagination格式,返回的效果
在这里插入图片描述
使用自己定制的LimitOffsetPagination格式,返回的效果
在这里插入图片描述

使用自己定制的CursorPagination格式,返回的效果
在这里插入图片描述

  • 16
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值