草庐IT

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

elijah_li 2023-03-28 原文

内容概要

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

内容详细

过滤

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

DRF 中使用的过滤方式:

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

内置过滤类

使用模块: from rest_framework.filters import SearchFilter

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

前提:需要使用 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', ]

如果是过滤外键字段:

使用双下滑的正向查询方式

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
  • 过滤外键字段,使用双下滑的正向查询方式

第三方过滤类

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']

总结

  • 第三方过滤类在filter_backends字段中写,filter_fields字段指定过滤的字段
  • 第三方过滤类不支持模糊查询,是精准匹配
  • 第三方过滤类的使用,视图类也必须继承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, ]

源码分析

我们知道过滤的前提条件是视图继承了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']

注意:过滤可以和排序同时使用,但是先执行过滤再执行排序,提升了代码的效率(先过滤后排序),因为如果先排序,那么数据库的数量庞大的话,直接操作了整个数据库,消耗资源,过滤完成后排序只是针对一小部分数据

分页

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

导入分页类: 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,重写四个类属性
    • default_limit:默认每页获取的条数
    • limit_query_param:每页显示多少条的查询条件,比如?limit=3,表示获取三条,如果不写默认使用default_limit设置的条数
    • offset_query_param:表示偏移量参数,比如?offset=3表示从第三条开始往后获取默认的条数
    • max_limit:设置最大显示条数
  2. 视图类内配置,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

CursorPagination

步骤

  1. 自定义类,继承CursorPagination,重写三个类属性
    • page_size:每页显示的条数
    • cursor_query_param:查询条件
    • ordering:排序规则,指定排序字段
  2. 视图类内配置,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

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

  • 跟上面两种的区别:上面两种,可以从中间位置获取某一页,Cursor方式只能上一页和下一页
  • 下面这种方式,先排序,内部维护了一个游标,游标只能选择往前走或往后走,在取某一页的时候,不需要过滤之前的数据
  • 这种分页方式特殊,只能选择上一页和下一页,不能指定某一页,但是速度快,适合大数据量的分页
  • 大数据量和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

自定义异常

可以自定义出现异常之后的处理方法和返回数据的格式

  • 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,错误视图类:<app01.views.TestView object at 0x000001C3B1C7CA58>,请求地址:/test/,请求方式:GET
    '''
    return res

修改异常的配置路径:

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

视图类中报错就会自动触发异常处理:

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!')

REST framework定义的异常

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

有关drf 过滤、排序、分页、异常处理的更多相关文章

  1. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  2. ruby-on-rails - Rails - 乐观锁定总是触发 StaleObjectError 异常 - 2

    我正在学习Rails,并阅读了关于乐观锁的内容。我已将类型为integer的lock_version列添加到我的articles表中。但现在每当我第一次尝试更新记录时,我都会收到StaleObjectError异常。这是我的迁移:classAddLockVersionToArticle当我尝试通过Rails控制台更新文章时:article=Article.first=>#我这样做:article.title="newtitle"article.save我明白了:(0.3ms)begintransaction(0.3ms)UPDATE"articles"SET"title"='dwdwd

  3. ruby - #之间? Cooper 的 *Beginning Ruby* 中的错误或异常 - 2

    在Cooper的书BeginningRuby中,第166页有一个我无法重现的示例。classSongincludeComparableattr_accessor:lengthdef(other)@lengthother.lengthenddefinitialize(song_name,length)@song_name=song_name@length=lengthendenda=Song.new('Rockaroundtheclock',143)b=Song.new('BohemianRhapsody',544)c=Song.new('MinuteWaltz',60)a.betwee

  4. ruby - 在 Ruby 中重新分配常量时抛出异常? - 2

    我早就知道Ruby中的“常量”(即大写的变量名)不是真正常量。与其他编程语言一样,对对象的引用是唯一存储在变量/常量中的东西。(侧边栏:Ruby确实具有“卡住”引用对象不被修改的功能,据我所知,许多其他语言都没有提供这种功能。)所以这是我的问题:当您将一个值重新分配给常量时,您会收到如下警告:>>FOO='bar'=>"bar">>FOO='baz'(irb):2:warning:alreadyinitializedconstantFOO=>"baz"有没有办法强制Ruby抛出异常而不是打印警告?很难弄清楚为什么有时会发生重新分配。 最佳答案

  5. ruby-on-rails - 事件管理员日期过滤器日期格式自定义 - 2

    是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s

  6. SPI接收数据异常问题总结 - 2

    SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手

  7. ruby-on-rails - 在 Controller 中干净地处理多个过滤器(参数) - 2

    我有一个名为Post的类,我需要能够适应以下场景:如果用户选择了一个类别,则只显示该类别的帖子如果用户选择了一种类型,则只显示该类型的帖子如果用户选择了一个类别和类型,则只显示该类别中该类型的帖子如果用户没有选择任何内容,则显示所有帖子我想知道我的Controller是否不可避免地会因大量条件语句而显得粗糙...这是我解决此问题的错误方法-有谁知道我如何才能做到这一点?classPostsController 最佳答案 您最好遵循“胖模型,瘦Controller”的惯例,这意味着您应该将这种逻辑放在模型本身中。Post类应该能够报告

  8. ruby-on-rails - 如何处理 Grape 中特定操作的过滤器之前? - 2

    我正在我的Rails项目中安装Grape以构建RESTfulAPI。现在一些端点的操作需要身份验证,而另一些则不需要身份验证。例如,我有users端点,看起来像这样:moduleBackendmoduleV1classUsers现在如您所见,除了password/forget之外的所有操作都需要用户登录/验证。创建一个新的端点也没有意义,比如passwords并且只是删除password/forget从逻辑上讲,这个端点应该与用户资源。问题是Grapebefore过滤器没有像except,only这样的选项,我可以在其中说对某些操作应用过滤器。您通常如何干净利落地处理这种情况?

  9. ruby-on-rails - 需要帮助最大化多个相似对象中的 3 个因素并适当排序 - 2

    我需要用任何语言编写一个算法,根据3个因素对数组进行排序。我以度假村为例(如Hipmunk)。假设我想去度假。我想要最便宜的地方、最好的评论和最多的景点。但是,显然我找不到在所有3个中都排名第一的方法。Example(assumingthereare20importantattractions):ResortA:$150/night...98/100infavorablereviews...18of20attractionsResortB:$99/night...85/100infavorablereviews...12of20attractionsResortC:$120/night

  10. Ruby-vips 图像处理库。有什么好的使用示例吗? - 2

    我对图像处理完全陌生。我对JPEG内部是什么以及它是如何工作一无所知。我想知道,是否可以在某处找到执行以下简单操作的ruby​​代码:打开jpeg文件。遍历每个像素并将其颜色设置为fx绿色。将结果写入另一个文件。我对如何使用ruby​​-vips库实现这一点特别感兴趣https://github.com/ender672/ruby-vips我的目标-学习如何使用ruby​​-vips执行基本的图像处理操作(Gamma校正、亮度、色调……)任何指向比“helloworld”更复杂的工作示例的链接——比如ruby​​-vips的github页面上的链接,我们将不胜感激!如果有ruby​​-

随机推荐