zoukankan      html  css  js  c++  java
  • 七、drf过滤排序分页异常

    drf过滤排序分页异常

    一、过滤组件

    1. 步骤

    1. 安装:pip3 install django-filter
    2. 注册: settings.py中注册
        INSTALLED_APPS = [
    	    ...
        	'django_filters',  # 需要注册应用,
    	]
        
    3. 全局配置 或者 局部配置
        全局配置: 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
        局部配置:
            指定所有字段: filter_fields = '__all__'
            指定固定字段: filter_fields = ['name', ...]   # 提示: 可以元组, 也可以是列表
    

    2. 内置过滤组件

    # 缺点: 外键字段的搜索操作将会抛出异常: Related Field got invalid lookup: icontains
    
    # 1)在视图文件views.py中导入drf的搜索组件
    from rest_framework.filters import SearchFilter
    
    # 2)将搜索组件配置给群查接口视图类的filter_backends
    filter_backends = [SearchFilter]
    
    # 3)配置视图类关联的Model表参与搜索的字段
    search_fields = ['name', 'id']
    
    # 4)前台访问该群查接口,采用拼接参数方式用search关键字将搜索目标提供给后台
    http://127.0.0.1:8000/course/free/?search=2  # id或name中包含2的所有结果
    

    3. 第三方过滤组件

    # 介绍: 争对django内置搜索组件的拓展, 在django内置的基础之上还拓展了外键字段的过滤功能.
    # 前提:安装django-filter插件
    pip install django-filter  (注意: 不要安装成了django-filters)
    
    """方式一"""
    # 1)在视图文件views.py中导入django-filter的功能组件
    from django_filters.rest_framework import DjangoFilterBackend
    
    # 2)将搜索组件配置给群查接口视图类的filter_backends
    filter_backends = [DjangoFilterBackend]
    
    # 3)配置视图类关联的Model表可以分类的字段(通常是可以分组的字段)
    filter_fields = ['course_category']
    
    # 4)前台访问该群查接口,采用拼接参数方式用分类course_category字段将分类条件提供给后台
    http://127.0.0.1:8000/course/free/?course_category=1  # 拿课程分类1下的所有课程
    
    '''方式二'''
    # 1)自定义过滤类继承django-filter插件的FilterSet类,绑定Model表,并设置分类字段
    from django_filters.filterset import FilterSet
    from . import models
    class CourseFilterSet(FilterSet):
        class Meta:
            model = models.Course
            fields = ['course_category']
    
    # 2)在视图文件views.py中导入django-filter的功能组件及自定义的过滤类
    from django_filters.rest_framework import DjangoFilterBackend
    from .filters import CourseFilterSet
            
    # 3)将搜索组件配置给群查接口视图类的filter_backends
    filter_backends = [DjangoFilterBackend]
    
    # 4)配置视图类关联的自定义过滤类
    filter_class = CourseFilterSet
    
    # 5)前台访问该群查接口,采用拼接参数方式用分类course_category字段将分类条件提供给后台
    http://127.0.0.1:8000/course/free/?course_category=1  # 拿课程分类1下的所有课程
    

    4.django-filter实现区间过滤

    # 1)自定义过滤类继承django-filter插件的FilterSet类,绑定Model表,并设置自定义区间规则字段
    from django_filters.filterset import FilterSet
    from . import models
    class CourseFilterSet(FilterSet):
        # 区间过滤: students学生中总人数要大于等于min_students, 要小于等于max_students. [min_students, max_students]
        max_students = filters.NumberFilter(field_name='students', lookup_expr='lte')
        min_students = filters.NumberFilter(field_name='students', lookup_expr='gte')
    
        class Meta:
            model = Course
            fields = ['course_category', 'students', 'min_students', 'max_students']
    
    # 2)在视图文件views.py中导入django-filter的功能组件及自定义的过滤类
    from django_filters.rest_framework import DjangoFilterBackend
    from .filters import CourseFilterSet
            
    # 3)将搜索组件配置给群查接口视图类的filter_backends
    filter_backends = [DjangoFilterBackend]
    
    # 4)配置视图类关联的自定义过滤类
    filter_class = CourseFilterSet
    
    # 5)前台访问该群查接口,采用拼接参数方式用自定义区间规则字段将区间条件提供给后台
    http://127.0.0.1:8000/course/free/?min_students=230&max_students=250  # 获取学生总人数230~250之间的数据  
    

    5. 自定义过滤

    # filters.py
    from rest_framework.filters import BaseFilterBackend
    
    
    class CustomFilter(BaseFilterBackend):
        def filter_queryset(self, request, queryset, view):
            # 老师的模糊匹配
            name = request.GET.get('teacher')
            if not name:
                return queryset
            teacher_queryset = queryset.filter(teacher__name__contains=name)
            return teacher_queryset
        
    # views.py
    # 自定义过滤: 通过老师名进行模糊匹配
    filter_backends = [CustomFilter]
    

    6. 注意

    django-filter的安装可能会出现django版本最低要求问题,  如果下载最新版本的django-filter
    如果使用的是django 1.11版本会自动升级到3.x.
    # 解决方法:pip install django-filter==2.1.0即可适配django1.11.11
    

    二、排序组件

    1. 全局配置/局部配置

    # 全局配置
        # 排序
        REST_FRAMEWORK = {
                'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.OrderingFilter')
            }
    
        # 过滤 和 排序
            REST_FRAMEWORK = {
                'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend', 'rest_framework.filters.OrderingFilter')
            }
    
    
    # 局部配置
        # 排序
            from rest_framework.filters import OrderingFilter
            filter_backends = [OrderingFilter]  # 注意: 如果这样就会覆盖全局配置配置的过滤
    
        # 过滤 和 排序
            from rest_framework.filters import OrderingFilter
            from django_filters.rest_framework import DjangoFilterBackend
            filter_backends = [OrderingFilter, DjangoFilterBackend]
            filter_fields = '__all__'
    

    2. 代码示例

    class TextView7(ListAPIView):
        # 局部将全局可能配置的认证+权限+频率禁用
        authentication_classes = []
        permission_classes = []
        throttle_classes = []
    
        # 局部配置排序组件.
        # 注意: 如果要过滤和排序, 需要注意的是如果全局配置了过滤, 需要在声明排序的基础之上再什么过滤. 因为filter_backends的局部指定会覆盖过滤的配置.
        # filter_backends = [OrderingFilter, DjangoFilterBackend, ]  # 提示: 2者之间没有顺序
        filter_backends = [DjangoFilterBackend, OrderingFilter]
    
        queryset = models.Book.objects.all()
        serializer_class = BookModelSerializer
    
        filter_fields = ['name', 'price']  # 可以用列表, 也可以用元组
    

    3. 总结

    # 过滤导入
    from django_filters.rest_framework import DjangoFilterBackend
    # 排序导入
    from rest_framework.filters import OrderingFilter
    # 注意问题
    它们2个全局配置都是共用一个配置路径, 如果局部指定了就会将全局配置的对应项所有的覆盖
    

    三、分页组件

    1. 分页的三种方式

    from rest_framework.pagination import PageNumberPagination
    
    
    # 第一种分页方式: 通过指定page获取页数, 通过size获取每页显示的条目
    class CoustomPageNumberPagination(PageNumberPagination):
        """
        url地址栏目支持的查询格式:
        http://api.example.org/accounts/?page=4
        http://api.example.org/accounts/?page=4&page_size=100
        """
        page_size = 3  # 页面大小. 表示显示每页数据条数. 可配置全局('PAGE_SIZE': None)
        page_query_param = 'page'  # 页面查询参数. 表示查询第几页的key. (可自定义名称)
        page_size_query_param = 'size'  # 页面大小查询参数. 表示每一页显示的条数 (可自定义名称)
        max_page_size = 5  # 最大页面大小. 表示每页最大显示条数
    
    
    from rest_framework.pagination import LimitOffsetPagination
    
    
    # 第二种分页方式: 通过指定offset找到指定位置, 通过limit往后获取条目数
    class CustomLimitOffsetPagination(LimitOffsetPagination):
        """
        url地址栏目支持的查询格式:
        http://api.example.org/accounts/?limit=100
        http://api.example.org/accounts/?offset=400&limit=100
        """
        default_limit = 3  # 每页条数. 可配置全局('PAGE_SIZE': None,)
        limit_query_param = 'limit'  # 从offset标杆的位置, 往后获取的条目数 (可自定义名称)
        offset_query_param = 'offset'  # 标杆. 可以理解为旗帜, 插在这里, 后去旗帜后面的内容条目 (可自定义名称)
        max_limit = 5  # 每页显示的最大条目数. 默认为None, 不做任何限制.
    
    
    from rest_framework.pagination import CursorPagination
    
    
    # 第三种分页方式: 光标分页
    class CustomCursorPagination(CursorPagination):
        """
        提示: 这种方式实现的方式很复杂, 因此带来的好处就是查询效率极高, 不过也有它的缺陷, 就是通过这种方式制作的分页只有上一页下一页,
            如果数据库数据非常大那么使用它将会是非常好的选择.
        url地址栏目支持的查询格式:
            直接浏览器输入访问即可, 因为不能指定位置.
        """
        cursor_query_param = 'cursor'  # 每一页查询的key (可自定义名称)
        page_size = 3  # 每页显示的条数. 可配置全局('PAGE_SIZE': None,)
        ordering = '-id'  # 排序字段. 如果不指定默认使用'-created'进行分页, 如果你的数据库中没有这个字段, 那么就会抛出异常
    
    
    from rest_framework.generics import ListAPIView
    from . import ser
    from . import models
    
    
    class BookAPIView(ListAPIView):
        queryset = models.Book.objects.all()
        serializer_class = ser.BookModelSerializer
    
        """
        APIView -> api_settings -> DEFAULTS -> 51行
        # Generic view behavior
        'DEFAULT_PAGINATION_CLASS': None,
        """
        # pagination_class = CoustomPageNumberPagination
        # pagination_class = CustomLimitOffsetPagination
        pagination_class = CustomCursorPagination
    
    # 总结
        from rest_framework.pagination import PageNumberPagination
            通过page分页, 通过site获取分页条目数
        from rest_framework.pagination import LimitOffsetPagination
            通过offset找到分页位置, 通过limit获取当前数据之后的条目数
        from rest_framework.pagination import CursorPagination
            没有参数, 默认只有上一页, 下一页. 通过ordering排序进行分页
            优点: 查询速度快. 缺点: 无法选择起始位置
        提示: 以上都可以限制最大显示条目数 和 默认获取条目数. 以及筛选的key都可以重定义.
    

    2. 如何规范化分页器的restful返回规范

    from rest_framework.generics import ListAPIView
    from utils.response import CommonResponse
    from utils.throttle import CustomSimpleRateThrottle
    from . import ser
    from . import models
    
    
    class BookAPIView(ListAPIView):
        queryset = models.Book.objects.all()
        serializer_class = ser.BookModelSerializer
        throttle_classes = [CustomSimpleRateThrottle, ]
    
        def get(self, request, *args, **kwargs):
            instance = self.get_queryset()
    
            # 调用分页器, 实例化出分页器对象
            page_obj = CustomCursorPagination()
            # 此处的instance要和上面的查询集一样
            instance = page_obj.paginate_queryset(queryset=instance, request=request, view=self)
            # 通过分页器对象. 获取下一条的url链接
            next_url = page_obj.get_next_link()
            # 通过分页器对象. 获取上一条的url链接
            previous_url = page_obj.get_previous_link()
            # print('next_url:', next_url)
            # print('previous_url:', previous_url)
    
            serializer = self.get_serializer(instance=instance, many=True)
    
            return CommonResponse(results=serializer.data, next_url=next_url, previous_url=previous_url)
    
    # 总结
        # 1. 调用分页器, 实例化出分页器对象
        page_obj = CustomCursorPagination()
        # 2. 通过分页器对象. 获取下一条的url链接  (注意: 一下获取都在序列化之后)
        next_url = page_obj.get_next_link()
        # 3. 通过分页器对象. 获取上一条的url链接
        previous_url = page_obj.get_previous_link()
    
    # 补充: 通过自定义类继承分页类, 就可以实现修改参数了, 如果像上面一样那么就不能自定义一些参数了
        default_limit = 3
        limit_query_param = 'limit'
        offset_query_param = 'offset'
        max_limit = 5
    

    四、异常处理

    1. 从源码分析到如何实现

    # 思路: 发现有些错误被drf捕获了, 而有些错误会交给django自己处理, 这是为什么呢? 源码分析一波
    
    # 查找路径: APIView -> dispatch -> try..except -> handle_exception
    
    # 源码分析:
        try:
            self.initial(request, *args, **kwargs)
    
            # Get the appropriate handler method
            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:
            response = self.handle_exception(exc)   # 这里
    
        1. 异常的捕获范围: 注意, 并不是所有位置的异常出可以捕获. 例如: 自定义视图中的类中抛出的异常就不行
            self.initial(request, *args, **kwargs)
                认证: self.perform_authentication(request)
                    提示: 不会捕获自定义的认证类. 因此perform_authentication做的事情就是request.user赋值
                权限: self.check_permissions(request)
                频率: self.check_throttles(request)
                自定义视图类中的方法:
                    response = handler(request, *args, **kwargs)
        2. 关键实现 handle_exception方法
            def handle_exception(self, exc):
                # 1) 这里的在认证失败的时候会走
                if isinstance(exc, (exceptions.NotAuthenticated,
                                    exceptions.AuthenticationFailed)):
                    # WWW-Authenticate header for 401 responses, else coerce to 403
                    auth_header = self.get_authenticate_header(self.request)
    
                    if auth_header:
                        exc.auth_header = auth_header
                    else:
                        exc.status_code = status.HTTP_403_FORBIDDEN
    
                # 2) 这里就是通过配置文件配置的路径, 拿到处理异常的函数, 内部就一句代码: self.settings.EXCEPTION_HANDLER
                '''
                def get_exception_handler(self):
                    """
                    配置文件导入的内容:
                    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
                    """
                    return self.settings.EXCEPTION_HANDLER
                '''
                exception_handler = self.get_exception_handler()
    
                # 3) 获取异常的处理的上下文内容, 本质里面就是获取操作的视图对象的结果
                '''
                def get_exception_handler_context(self):
                    return {
                        'view': self,
                        'args': getattr(self, 'args', ()),
                        'kwargs': getattr(self, 'kwargs', {}),
                        'request': getattr(self, 'request', None)
                    }
                '''
                context = self.get_exception_handler_context()
    
                # 4) 将刚刚从配置文件中导入的视图函数传参调用
                '''
                exc: 这里的exc是APIView中定义的dispatch中传过来的异常对象
                context: 这里的context是对出现异常对象的上下文捕获
                '''
                response = exception_handler(exc, context)
    
                # 5) 关键转折:
                '''
                这里就是通过在drf提供的exception_handler函数处理的返回值结果来判断时候交给django自己处理.
                如果response的返回值是None就会交给django处理了, 现在我们要的就是在exception_handler函数执行完毕以后将返回值进行判断,
                并且返回的结果不再是None, 而应该是response对象
                '''
                if response is None:
                    self.raise_uncaught_exception(exc)
    
                response.exception = True
                return response
    
    # 步骤:
        1. 先新建一个.py文件存放自定义的异常处理函数
        2. 在drf提供的默认配置文件中导入exception_handler函数的
        3. 在自定义异常处理函数中先将exception_handler传入让drf先处理一番, 根据返回的结果为None是来执行自己的判断.
            如果返回不为None也不应该直接将原本的response对象直接返回, 可以自己封装一个符合restful规范的类用来继承Response类
            将原本的response对象中的返回结果通过 response.data.get('detail') 方法获取
        4. settings.py文件中配置自定义的exception_handler函数的路径
            'EXCEPTION_HANDLER': 'app01.app_auth.custom_exception_handler',
    

    2. 代码示例

    from rest_framework.views import exception_handler
    from rest_framework import status
    
    from rest_framework.response import Response
    
    
    class APIResponse(Response):
        def __init__(self, code=1000, messages='成功', results=None, error=None,
                     status=None,
                     template_name=None, headers=None,
                     exception=False, content_type=None, **kwargs):
            data = {
                'code:': code,
                'messages:': messages,
            }
            print('error:', error)
            print('results:', results)
            if results:
                data['results'] = results
            if error:
                data['error'] = error
            data.update(kwargs)
    
            super().__init__(data=data, status=status,
                             template_name=template_name, headers=headers,
                             exception=exception, content_type=content_type)
    
    
    def custom_exception_handler(exc, context):
        """
        :param exc: 这里的exc是APIView中定义的dispatch中传过来的异常对象
            try:
                ...
            except Exception as exc:
                response = self.handle_exception(exc)
        :param context: 这里的context是对出现异常对象的上下文捕获
            查找: handle_exception -> get_exception_handler_context
                def get_exception_handler_context(self):
                    return {
                        'view': self,
                        'args': getattr(self, 'args', ()),
                        'kwargs': getattr(self, 'kwargs', {}),
                        'request': getattr(self, 'request', None)
                    }
        :return: 这里返回Response对象, 本来drf没有处理的的异常会交给django处理, 但是我们捕获这种异常, 规定成统一的处理. 让drf处理.
    
        注意!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        这里可以捕获的异常范围由以下源码得知:
            try:
                self.initial(request, *args, **kwargs)
    
                # Get the appropriate handler method
                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:
                response = self.handle_exception(exc)
    
        捕获范围:
            self.initial(request, *args, **kwargs)
                认证: self.perform_authentication(request)
                    提示: 不会捕获自定义的认证类. 因此perform_authentication做的事情就是request.user赋值
                权限: self.check_permissions(request)
                频率: self.check_throttles(request)
                自定义视图类中的方法:
                    response = handler(request, *args, **kwargs)
        """
        obj = None
        response = exception_handler(exc, context)
        # 注意: exc, context都不是可json序列化的格式, 需要转换成字符串类型.
        if not response:
            # 自己的处理
            if isinstance(exc, AttributeError):
                obj = APIResponse(2000, '失败', error=str(exc), results=str(context), status=status.HTTP_403_FORBIDDEN)
            elif isinstance(exc, ImportError):
                obj = APIResponse(2002, '失败', error=str(exc), results=str(context), status=status.HTTP_403_FORBIDDEN)
            elif isinstance(exc, TypeError):
                obj = APIResponse(2003, '失败', error=str(exc), results=str(context), status=status.HTTP_403_FORBIDDEN)
            elif isinstance(exc, Exception):
                obj = APIResponse(2004, '失败', error=str(exc), results=str(context), status=status.HTTP_403_FORBIDDEN)
        else:
            # 在drf处理的基础之上再次处理
            obj = APIResponse(2005, '失败', error=response.data.get('detail'), results=str(context),
                              status=status.HTTP_403_FORBIDDEN)
        return obj
    

    3. 注意

    配置文件中配置自定义的异常处理函数时, drf提供的exception_handler的导入会与在同一个文件中自定义的认证类 或者 自定义的权限类的导入起冲突.
    自定义的异常的处理代码逻辑最好新建一个纯净的.py文件存放
    

    4. 快速使用

    from rest_framework.views import exception_handler
    from rest_framework.response import Response
    
    
    class CommonResponse(Response):
        def __init__(self, code=1000, messages='ok', results=None,
                     status=None, template_name=None, headers=None,
                     exception=False, content_type=None,
                     **kwargs):
            data = {
                'code': code,
                'messages': messages,
            }
            data.update(kwargs)
            if results:
                data['results'] = results
            super().__init__(data=data, status=status,
                             template_name=template_name, headers=headers,
                             exception=exception, content_type=content_type)
    
    
    
    def common_exception_handler(exc, context):
        response = exception_handler(exc, context)
        if not response:
            obj = CommonResponse(code=2000, messages='error', results=str(exc))
        else:
            obj = CommonResponse(code=2000, messages='error', results=response.data.get('detail'))
        return obj
    

    5. 总结

    1. 导入需要在drf提供的默认函数的基础之上的函数
    	from rest_framework.views import exception_handler
    2. 自定义异常处理函数2个参数exc, context
    3. 先让drf处理一波, 处理它处理不完的, 或者 在他处理完的基础之上拓展, 通过response返回结果来进行区分
        提示: 可以通过 response.data.get('detail') 获取drf处理完的对象中返回的响应信息
    
  • 相关阅读:
    angular安装指定版本
    Fluttter通过按钮来打开抽屉Drawer或者endDrawer
    angular中的animation动画
    flutter pubspec.yaml配置文件详解
    angular-cli卸载安装
    angular的项目基本配置的了解
    angular使用代理解决跨域
    IOS开发之UI布局
    用Objective-C写了一个简单的批量更改文件名的程序
    使用Objective-C 计算代码运行时间
  • 原文地址:https://www.cnblogs.com/borntodie/p/14330806.html
Copyright © 2011-2022 走看看