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

发布时间 2024-01-02 16:47:56作者: jntmwl

排序

1.只有查询所有需要排序

2 如何使用

 	1 必须是继承 GenericAPIView 及其子类

   	2 在类中配置类属性

    	   filter_backends = [OrderingFilter]

    3 类中写属性

    	   ordering_fields = ['price','id']  # 必须表的字段

	4 以后再前端,就可以访问	

	http://127.0.0.1:8000/api/v1/books/?ordering=price 按price升序排	

	http://127.0.0.1:8000/api/v1/books/?ordering=-price 按price降序排	

	http://127.0.0.1:8000/api/v1/books/?ordering=-price,id

3.继承APIView写排序

### 如果继承APIView,过滤规则自己写
from rest_framework.views import APIView
from rest_framework.response import Response


class BookView(ViewSetMixin, APIView):
    def list(self, request):
        # 从地址栏中取出用户过滤条件
        query_params = request.query_params  # {ordering:price}
        # 支持多个条件排序  ordering=-price,id
        if ',' in query_params.get('ordering'):
            query = query_params.get('ordering').split(',')
        obj_list = Book.objects.all().order_by(*query)
        ser = BookSerializer(instance=obj_list, many=True)

        return Response(ser.data)

过滤

restful规范中有一条是请求地址中带过滤条件,查询所有数据,只查询出我们想要的

1.内置过滤类的使用【使用前提是继承GenericAPIView】

内置过滤类,必须继承GenericAPIView及其子类,才能使用这种方法---》配置过滤类的方式
from rest_framework.filters import SearchFilter, OrderingFilter

class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    #  SearchFilter内置的,固定用法,模糊匹配
    #  就有过滤功能了,指定按哪个字段过滤
    filter_backends = [SearchFilter]
    # search_fields = ['name']  # 可以按名字模糊匹配
    search_fields = ['name','price']  # 可以按名字模糊匹配或价格模糊匹配
    
 # 可以使用的搜索方式
	http://127.0.0.1:8000/api/v1/books/?search=红  # name或price中只要有红就会搜出来
            
            
# 继承APIView如何写,完全自己写,麻烦,但是清晰(功能都是自己写出来的)     

2 使用第三方django-filter实现过滤

需要安装:django-filter模块

from django_filters.rest_framework import DjangoFilterBackend
'导入模块,这个类就是一个过滤类,在使用的时候我们不再使用search_fields,而是使用filterset_fields,需要注意的是这个第三方模块是完整匹配(即匹配内容一模一样才会被匹配上)'

class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = []
    authentication_classes = []
    throttle_classes = []
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['name','price']  # 支持完整匹配  name=聊斋11&price=933
    
    
 # 支持的查询方式
http://127.0.0.1:8000/api/v1/books/?price=939
http://127.0.0.1:8000/api/v1/books/?price=939&name=红楼猛

3.自己定制过滤类实现过滤(类似前三个组件)

需求举例:查询价格大于100的所有图书

使用步骤
  • 第一步; 定义一个过滤类,继承BaseFilterBackend,重写filter_queryset方法
class CommonFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        # 在里面实现过滤,返回qs对象,就是过滤后的数据
        price_gt = request.query_params.get('price_gt', None)
        if price_gt:
            qs = queryset.filter(price__gt=int(price_gt))
            return qs

        else:
            return queryset
  • 第二步:配置在视图类上,路由等配置跟之前的一样
from django_filters.rest_framework import DjangoFilterBackend

class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    filter_backends = [CommonFilter]  # 可以定制多个,从左往右,依次执行
  • 测试时使用的路由
	http://127.0.0.1:8000/api/v1/books/?price_gt=100
代码

filter.py(自定义的过滤类)

from rest_framework.filters import BaseFilterBackend


class CommonFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        # 在里面实现过滤,返回qs对象,就是过滤后的数据
        price_gt = request.query_params.get('price_gt', None)
        if price_gt:
            qs = queryset.filter(price__gt=int(price_gt))
            return qs

        else:
            return queryset

views.py

from .filter import CommonFilter

class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = []
    authentication_classes = []
    throttle_classes = []
    filter_backends = [CommonFilter]  # 可以定制多个,从左往右,依次执行
    ordering_fields = ['price','id']

注意事项:

1、因为最开始的时候我们给这个价格字段在模型层中设置的是CharField字段,因此会导致我们过滤类中出现问题,因为CharField字段不支持双下划线的gt方法(mysql神器的双下划线部分知识点)

2、这里我们介绍了三种过滤类的使用,我们也要想到这方面,就是这三种过滤类是可以混着一起用上的

models.py(需要修改的部分代码)

class Book(models.Model):
    name = models.CharField(max_length=32, default='xx')
    price = models.IntegerField()

    publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)  # 留住,还有很多
    authors = models.ManyToManyField(to='Author')

    def publish_detail(self):
        return {'name': self.publish.name, 'addr': self.publish.addr}

    def author_list(self):
        l = []
        for author in self.authors.all():
            l.append({'name': author.name, 'phone': author.phone})
        return l

分页

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

使用步骤

  • 步骤一:创建一个py文件编写分页用到的自定义类,分页的三个类并不能直接使用,需要我们进行配置
  • 步骤二:编写这个自定义类
  • 步骤三:导入视图类中,并添加配置

代码

page.py(自定义的分页类)

from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination


# 网页用它
class CommonPageNumberPagination(PageNumberPagination):
    page_size = 2  # 每页显示2条
    page_query_param = 'page'  # page=10  查询第10页的数据,每页显示2条
    page_size_query_param = 'size'  # page=10&size=5    查询第10页,每页显示5条
    max_page_size = 5  # 每页最大显示10条

'''
page_size 每页数目
page_query_param 前端发送的页数关键字名,默认为”page”
page_size_query_param 前端发送的每页数目关键字名,默认为None
max_page_size 前端最多能设置的每页数量
'''
    
    
# LimitOffset
class CommonLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 3  # 每页显示2条
    limit_query_param = 'limit'  # limit=3   取3条
    offset_query_param = 'offset'  # offset=1  从第一个位置开始,取limit条
    max_limit = 5
    # offset=3&limit=2      0  1 2 3 4 5
'''
default_limit 默认限制,默认值与PAGE_SIZE设置一直
limit_query_param limit参数名,默认’limit’
offset_query_param offset参数名,默认’offset’
max_limit 最大limit限制,默认None
'''

# app 用下面

class CommonCursorPagination(CursorPagination):
    cursor_query_param = 'cursor'  # 查询参数
    page_size = 2  # 每页多少条
    ordering = 'id'  # 排序字段

'''
cursor_query_param:默认查询字段,不需要修改
page_size:每页数目
ordering:按什么排序,需要指定

# 注:必须基于排序规则下进行分页
# 1)如果接口配置了OrderingFilter过滤器,那么url中必须传ordering
# 1)如果接口没有配置OrderingFilter过滤器,一定要在分页类中声明ordering按某个字段进行默认排序
'''

views.py

#  分页功能   必须是继承GenericAPIView ,如果是继承APIView,要自己写(你写)
from .page import CommonPageNumberPagination as PageNumberPagination
from .page import CommonLimitOffsetPagination as LimitOffsetPagination
from .page import CommonCursorPagination as CommonCursorPagination



class BookView(ViewSetMixin, ListAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    permission_classes = []
    authentication_classes = []
    throttle_classes = []
    # 之前的东西一样用 ,内置的分页类不能直接使用,需要继承,定制一些参数后才能使用
    # pagination_class = PageNumberPagination
    #基本分页方式(基本是这种,网页端):http://127.0.0.1:8000/api/v1/books/?page=2&size=3

    # pagination_class = LimitOffsetPagination
    # 偏移分页 http://127.0.0.1:8000/api/v1/books/?limit=4&offset=1
    # 从第一条开始,取4条

    pagination_class = CommonCursorPagination
    # 游标分页,只能下一页,上一页,不能跳到中间,但它的效率最高,大数据量分页,使用这种较好

ps1:我们不难发现,今天讲解的组件,除了分页都是用列表来传需要使用的类的,这里我们也可以点到rest_frameword的源码中查看,我们会发现他的配置中分页的配置默认为None

ps2:三种分页方式中第三种游标分页效率最高,因为他不能根据页数自行跳转,只能上一页或者下一页进行跳转。这也导致了他的数据量小,所以相比前需要算出所有页面的网址的两种方式,他的速度更快。

异常处理

1 读APIView源码时,即是三大认证和视图类的方法中出现了异常,都会被try捕获,做全局异常处理

2 捕获到,统一处理,处理成固定格式---》给前端是统一的

3 无论是否出异常,给前端格式都是统一的

4 使用步骤

- 1 新建py文件,写一个函数,在函数中处理异常
     from rest_framework.views import exception_handler
    from rest_framework.response import Respons
    def common_exception_handler(exc, context):
        # drf的异常,处理了
        res = exception_handler(exc, context)
        if res:  # 有值说明是drf的异常,
            # data = {'detail': exc.detail}
            # return Response(data)
            # {code: 100, msg: 成功}
            detail = res.data.get('detail') or "drf异常,请联系系统管理员"
            return Response({'code': 999, 'msg': detail})
        else: # 如果没值,说明是自己的异常
            return Response({'code': 888, 'msg': '系统异常,请联系系统管理员:%s'%str(exc)})
    - 2  把函数配置在配置文件中
    	REST_FRAMEWORK = {
            'EXCEPTION_HANDLER': 'app01.exceptions.common_exception_handler',
        }
    - 3 只要三大认证或视图类的方法出了异常,就会走这个函数