drf 过滤、排序、分页、异常处理

内容概要

  • 过滤
  • 排序
  • 分页
  • 异常处理

内容详细

过滤

过滤是 涉及到查询数据的接口才需要过滤功能

DRF 中使用的过滤方式:

  • 1、 内置过滤类 在请求数据中用”search=字符”条件过滤(模糊查询)
  • 2、 第三方过滤类 在请求数据中用”字段名=字符”条件过滤 (严格查询)
  • *3、 自定义过滤类

内置过滤类

使用模块: from rest_framework.filters import SearchFilter

在视图层中使用内置过滤器类

[En]

Use built-in filter classes in the view layer

前提:需要使用 GenericAPIView 类中的 filter_backends属性,所以视图类得继承 GenericAPIView

class GenericAPIView(views.APIView):
    queryset = None
    serializer_class = None
    lookup_field = 'pk'
    lookup_url_kwarg = None
    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
    pagination_class = api_settings.DEFAULT_PAGINATION_CLASS

步骤

  1. 视图类内filter_backends中使用SearchFilter
  2. 类属性search_fields指定过滤的字段
from rest_framework.filters import SearchFilter

class BookViewSet(ViewSetMixin, ListAPIView):
    queryset = models.Book.objects
    serializer_class = BookModelSerializer
    authentication_classes = [LoginAuth, ]
    # throttle_classes = [IPThrottling, ]
    filter_backends = [SearchFilter]
    search_fields = ['name', 'price', ]

drf 过滤、排序、分页、异常处理

如果是过滤外键字段:

使用带双拒绝的正向查询

[En]

Use a forward query with double declines

class BookViewSet(ViewSetMixin, ListAPIView):
    queryset = models.Book.objects
    serializer_class = BookModelSerializer
    authentication_classes = [LoginAuth, ]
    # throttle_classes = [IPThrottling, ]
    filter_backends = [SearchFilter, ]
    search_fields = ['publish__name', ]

总结

  • 内置过滤类的使用,模糊查询会将包含过滤字段的数据都过滤出来,前提是在search_fields列表内指定的字段;
  • 内置过滤的特点是 模糊查询
  • 过滤字段参数为 search
  • 过滤外键字段,使用双降正向查询
    [En]

    filter foreign key fields and use forward query with double declines*

第三方过滤类

1、安装: pip install django-filter

2、使用模块: from django_filters.rest_framework import DjangoFilterBackend

3、在项目配置文件 settings.py 中注册下载的 app

4、第三方过滤类在 filter_backends字段中写, filter_fields字段指定过滤的字段

INSTALLED_APPS = [
    ...

    'django_filters',  # 需要注册应用,
]

4、视图层中使用

class BookViewSet(ViewSetMixin, ListAPIView):
    queryset = models.Book.objects
    serializer_class = BookModelSerializer
    authentication_classes = [LoginAuth, ]
    filter_backends = [DjangoFilterBackend, ]
    filter_fields = ['name', 'price']

drf 过滤、排序、分页、异常处理

总结

  • 第三方过滤类在 filter_backends字段中写, filter_fields字段指定过滤的字段
  • 第三方过滤类不支持模糊查询,精确匹配
    [En]

    the third-party filter class does not support fuzzy query and is an accurate match*

  • 第三方过滤类的使用,视图类也必须继承 GenericAPIView才能使用
  • 在链接内通过 &来表示和的关系

外键字段怎么查?

自定义过滤类

1、新建一个过滤文件,写一个类继承 BaseFilterBackend,重写 filter_queryset(self, request, queryset, view)方法,返回queryset对象,qs对象是过滤后的

2、视图层使用,只需要指定 filter_backend属性为自定义类列表

3、查询过滤,支持模糊查询(自己定制过滤方式),在 filter_queryset方法自定义过滤规则

自定义过滤类的书写:

from rest_framework.filters import BaseFilterBackend
from django.db.models import Q

class Myfilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        # 获取过滤参数
        qs_name = request.query_params.get('name')
        qs_price = request.query_params.get('price')
        # title__contains:精确大小写查询,SQL中-->like BINARY
        # 利用Q查询构造或关系
        if qs_name:
            queryset = queryset.filter(name__contains=qs_name)
        elif qs_price:
            queryset = queryset.filter(price__contains=qs_price)
        elif qs_name or qs_price:
            queryset = queryset.filter(Q(name__contains=qs_name) | Q(price__contains=qs_price))
        return queryset

视图类:

from app01.filter import Myfilter

class BookViewSet(ViewSetMixin, ListAPIView):
    queryset = models.Book.objects
    serializer_class = BookModelSerializer
    authentication_classes = [LoginAuth, ]
    filter_backends = [Myfilter, ]

drf 过滤、排序、分页、异常处理

源码分析

我们知道过滤的前提条件是视图继承了GenericAPIView才能使用,那么在GenericAPIView中的执行流程是什么?

1、调用了GenericAPIView中的filter_queryset方法
2、filter_queryset方法源码:
    def filter_queryset(self, queryset):
        for backend in list(self.filter_backends):
            queryset = backend().filter_queryset(self.request, queryset, self)
        return queryset
'''
1.backend是通过遍历该类的filter_backends列表的得到的,也就是我们指定的过滤类列表,那么backend就是我们的过滤类
2.通过实例化得到对象来调用了类内的filter_queryset返回了过滤后的对象
'''

排序

使用模块: from rest_framework.filters import OrderingFilter

步骤

  1. 视图类中配置,且视图类必须继承 GenericAPIView
  2. 与过滤类一样要把排序类存入 filter_backends 属性的列表中
  3. 通过 ordering_fields指定要排序的字段
  4. 排序过滤, -号代表倒序,且必须使用 ordering指定排序字段

视图类书写:

from app01.filter import Myfilter
from rest_framework.filters import OrderingFilter

class BookViewSet(ViewSetMixin, ListAPIView):
    queryset = models.Book.objects
    serializer_class = BookModelSerializer
    authentication_classes = [LoginAuth, ]
    # throttle_classes = [IPThrottling, ]
    filter_backends = [Myfilter, OrderingFilter, ]  # 先过滤后排序
    ordering_fields = ['id', 'price']

drf 过滤、排序、分页、异常处理

注:过滤可以和排序同时使用,但在排序之前进行过滤提高了代码的效率(先过滤后排序),因为如果先排序,然后数据库数量很大,直接操作整个数据库,消耗资源。只对过滤后的一小部分数据进行排序。

[En]

Note: filtering can be used at the same time as sorting, but performing filtering before sorting improves the efficiency of the code (first filtering and then sorting), because if sorting first, then the number of databases is large, it directly manipulates the whole database and consumes resources. Sorting is only for a small part of the data after filtering.

drf 过滤、排序、分页、异常处理

分页

分页只在查询所有接口中使用

导入分页类: from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination

DRF 中分页的三种方式:

  • 1、PageNumberPagination,基本分页
  • 2、LimitOffsetPagination,偏移分页
  • 3、CursorPagination,游标分页

PageNumberPagination

步骤

自定义类,继承 PageNumberPagination,重写四个类属性

  • page_size:设置每页默认显示的条数
  • page_query_param:url中的查询条件,books/?page=2表示第二页
  • page_size_query_param:每页显示多少条的查询条件,books/?page=2&size=5,表示查询第二页,显示5条
  • max_page_size:设置每页最多显示条数,不管查多少条,最大显示该值限制的条数

注意: 配置在视图类中,通过 pagination_class指定, 必须继承GenericAPIView才有

分页类书写:

from rest_framework.pagination import PageNumberPagination

class BookPagination(PageNumberPagination):
    page_size = 3  # 默认每页显示2条
    page_query_param = 'page'  # 查询条件,eg:page=3
    page_size_query_param = 'size'  # 查询条件参数size=5显示五条
    max_page_size = 10  # 每页最大显示条数

视图层类:

pagination_class 属性赋值分页类

from app01.page import BookPagination

class BookViewSet(ViewSetMixin, ListAPIView):
    queryset = models.Book.objects.all()
    serializer_class = BookModelSerializer
    authentication_classes = []
    filter_backends = [Myfilter, OrderingFilter, ]  # 先过滤后排序
    ordering_fields = ['id', 'price']
    pagination_class = BookPagination

LimitOffsetPagination

步骤

  1. 自定义类,继承LimitOffsetPagination,重写四个类属性
  2. default_limit:默认每页获取的条数
  3. limit_query_param:每页显示多少条的查询条件,比如?limit=3,表示获取三条,如果不写默认使用default_limit设置的条数
  4. offset_query_param:表示偏移量参数,比如?offset=3表示从第三条开始往后获取默认的条数
  5. max_limit:设置最大显示条数
  6. 视图类内配置,pagination_class参数指定, 必须继承GenericAPIView才有

分页类书写:

from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination

class MyLimitOffset(LimitOffsetPagination):
    default_limit = 2  # 默认每页显示2条
    limit_query_param = 'limit'  # ?limit=3,查询出3条
    offset_query_param = 'offset'  # 偏移量,?offset=2,从第2条后开始
    max_limit = 5  # 最大显示5条

视图层类:

pagination_class 属性赋值分页类

from app01.page import BookPagination, MyLimitOffset

class BookViewSet(ViewSetMixin, ListAPIView):
    queryset = models.Book.objects.all()
    serializer_class = BookModelSerializer
    authentication_classes = []
    filter_backends = [Myfilter, OrderingFilter, ]  # 先过滤后排序
    ordering_fields = ['id', 'price']
    pagination_class = MyLimitOffset

drf 过滤、排序、分页、异常处理

CursorPagination

步骤

  1. 自定义类,继承CursorPagination,重写三个类属性
  2. page_size:每页显示的条数
  3. cursor_query_param:查询条件
  4. ordering:排序规则,指定排序字段
  5. 视图类内配置,pagination_class参数指定, 必须继承GenericAPIView才有

分页类书写:

from rest_framework.pagination import CursorPagination

class MyCursor(CursorPagination):
    page_size = 3
    cursor_query_param = 'cursor'
    ordering = 'id'

视图层类:

pagination_class 属性赋值分页类

from app01.page import BookPagination, MyLimitOffset, MyCursor

class BookViewSet(ViewSetMixin, ListAPIView):
    queryset = models.Book.objects.all()
    serializer_class = BookModelSerializer
    authentication_classes = []
    filter_backends = [Myfilter, ]  # 使用了 cursor 游标分页,不要指定排序规则,会报错
    ordering_fields = ['id', 'price']
    pagination_class = MyCursor

查询方式

http://127.0.0.1:8000/books/?cursor=cD02

drf 过滤、排序、分页、异常处理

注意:分页类内指定了排序,视图内不要写排序规则,不然报错

  • 跟上面两种的区别:上面两种,可以从中间位置获取某一页,Cursor方式只能上一页和下一页
  • 按照以下方式,首先进行排序并在内部维护游标。光标只能选择前进或后退。在获取页面时,不需要过滤以前的数据。
    [En]

    in the following way, sort first and maintain a cursor internally. The cursor can only choose to go forward or backward. When fetching a page, there is no need to filter the previous data.*

  • 这种寻呼方式是特殊的。您只能选择上一页和下一页,但不能指定页面,但它速度快,适合大数据量的分页。
    [En]

    this paging method is special. You can only select the previous page and the next page, but you cannot specify a page, but it is fast and suitable for paging with a large amount of data.*

  • 大数据量和app分页—》下拉加载下一页,不需要指定跳转到第几页

异常处理

之前读APIView源码的时候,捕获了全局异常,在执行三大认证,视图类的方法时候,如果出了异常,会被全局异常捕获

以下是 APIView捕获异常 的流程

1、 APIView源码
dispatch方法源码
    except Exception as exc:
         response = self.handle_exception(exc)
handle_exception方法源码
    exception_handler = self.get_exception_handler()
    response = exception_handler(exc, context)

2、 默认配置文件
get_exception_handler() 调用的是 views 中的 exception_handler

'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',

3、views种的exception_handler方法
def exception_handler(exc, context):
    if isinstance(exc, Http404):
        exc = exceptions.NotFound()
    elif isinstance(exc, PermissionDenied):
        exc = exceptions.PermissionDenied()
    if isinstance(exc, exceptions.APIException):
        headers = {}
        if getattr(exc, 'auth_header', None):
        else:
            data = {'detail': exc.detail}
        return Response(data, status=exc.status_code, headers=headers)

    return None

由上源码可知,exception_handler(exc, context) 方法,如果报的是已知的错会返回 Response 对象,未知错误返回 None

自定义异常

您可以自定义异常发生后的处理方法和返回数据的格式

[En]

You can customize the handling method after the exception occurs and the format of the returned data

  • exc:错误原因
  • context:字典,包含了当前请求对象和视图类对象

重写异常处理方法:

from rest_framework.views import exception_handler
from rest_framework.response import Response

def myexception_handler(exc, context):
    # 先执行原来的exception_handler帮助我们处理
    res = exception_handler(exc, context)
    if res:
        # res有值代表处理过了APIException对象的异常了,返回的数据再定制
        res = Response(data={'code': 998, 'msg': res.data.get('detail', '服务器异常,请联系系统管理员')})
        # res = Response(data={'code': 998, 'msg': '服务器异常,请联系系统管理员'})
        # res.data.get从响应中获取原来的处理详细信息
    else:
        res = Response(data={'code': 999, 'msg': str(exc)})
        print(exc)  # list index out of range

    '''模拟日志处理'''
    request = context.get('request')  # 当次请求的request对象
    view = context.get('view')  # 当次执行的视图类对象
    print('错误原因:%s,错误视图类:%s,请求地址:%s,请求方式:%s' % (str(exc), str(view), request.path, request.method))
    '''结果:
    错误原因:list index out of range,错误视图类:,请求地址:/test/,请求方式:GET
    '''
    return res

修改异常的配置路径:

REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'app01.myexception.exception_handler' # 再出异常,会执行自己定义的函数
}

当view类中报告错误时,会自动触发异常处理:

[En]

Exception handling is automatically triggered when an error is reported in the view class:

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.exceptions import APIException
测试异常视图
class Test(APIView):
    def get(self,request):

        # 1、 其他报错
        # l = [1,2,3]
        # print(l[100])

        # 2、APIException异常
        # raise APIException('APIException errors!')

        return Response('success!')

drf 过滤、排序、分页、异常处理

REST framework定义的异常

  • APIException 所有异常的父类
  • ParseError 解析错误
  • AuthenticationFailed 认证失败
  • NotAuthenticated 尚未认证
  • PermissionDenied 权限决绝
  • NotFound 未找到
  • MethodNotAllowed 请求方式不支持
  • NotAcceptable 要获取的数据格式不支持
  • Throttled 超过限流次数
  • ValidationError 校验失败

Original: https://www.cnblogs.com/elijah-li/p/16128524.html
Author: elijah_li
Title: drf 过滤、排序、分页、异常处理

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/500014/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球