DRF 数据的过滤

  • 参考网址

https://www.cnblogs.com/songhaixing/p/14687072.html

  • drf 内置的过滤组件 SearchFilter
### models
class BookInfo(models.Model):
    title = models.CharField(max_length=64)
    simple_content = models.CharField(max_length=128)
    content = models.TextField()
    price = models.FloatField()

    def __str__(self):
        return self.title

### serializers
class BookInfoSerializer(serializers.ModelSerializer):
    class Meta:
        model = BookInfo
        fields = '__all__'

### views
from rest_framework.filters import SearchFilter

class BookInfoListView(ListAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    filter_backends = [SearchFilter] # 指定过滤类
    # 这里不能写成 filter_fields
    search_fields = ('price','title') # 指定字段

### 测试
# 127.0.0.1:8000/book2/?search=12
# 127.0.0.1:8000/book2/?search=2  (模糊查询)
# 127.0.0.1:8000/book2/?title=雪  (模糊查询,无效)
  • 小结
- 支持模糊查询
- 只能使用 search 来进行条件指定
- 可以使用 "," 来隔离多个条件来进行查询
- 不能查询外键字段,报错 : Related Field got invalid lookup: icontains
- DRF内置的过滤查询,局限性较大

第三方过滤组件 Django-filter

  • 安装一个坑
- 不要直接 pip install django-filter,会把django更新成最新版

    - 在pycharm中安装即可(不要安装最新版,最新版有坑,没有效果)
    - 目前安装的是 2.4.0(22.1是最新版,很坑,不要去装...)
### settings
INSTALLED_APPS = [
    ...
    'django_filters',
]

# 全局配置
REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
}

### views 局部配置
from django_filters.rest_framework import DjangoFilterBackend

class BookInfoListView(ListAPIView):
  
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    filter_backends = [DjangoFilterBackend,] # 指定过滤类
    filter_fields = ('price','title') # 指定字段

### 测试
# 127.0.0.1:8000/book2/?title=萨嘎达
# 127.0.0.1:8000/book2/?price=2  (不支持模糊查询)
# 127.0.0.1:8000/book2/?price=34&title=阿嘎达

  • 小结
- 可以指定字段名进行查询 (还有一些其他强大功能,如:通过时间过滤,(后边介绍))
- 不支持模糊查询
- 针对django内置搜索组件的拓展, 在django内置的基础之上还拓展了外键字段的过滤功能

自定义过滤查询(推荐)

- 创建一个存放过滤类的文件: custom_filter.py

- 继承 BaseFilterBackend , 必须重写 filter_queryset 方法
### custom_filter.py
from rest_framework.filters import BaseFilterBackend
from django.db.models import F,Q

# 自定义过滤类(继承BaseFilterBackend)
class MyFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        price = request.GET.get('price')
        title = request.GET.get('title')
        # 匹配的逻辑代码自己定制
        if title and price:
            # contains : 包含(模糊匹配)
            queryset = queryset.filter(Q(title__contains=title)|Q(price__contains=price))  
        if title:
            queryset = queryset.filter(title__contains=title)
        if price:
            queryset = queryset.filter(price__contains=price)
        return queryset

### views
from .custom_filter import MyFilter

class BookInfoListView(ListAPIView):
   
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    filter_backends = [MyFilter,] # 指定自定义过滤类

### 测试
# 127.0.0.1:8000/book2/?title=萨嘎达
# 127.0.0.1:8000/book2/?price=2  (模糊查询)
# 127.0.0.1:8000/book2/?price=2&title=雪  (多个条件模糊查询)

  • 小结
- 可以自定义匹配逻辑
- 自定义模糊匹配等等, 扩展性比较强

DRF 排序

### settings
......
REST_FRAMEWORK = {
        # 过滤
        # 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
        # 排序(key都是一样的)
        # 'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.OrderingFilter'),
        # 过滤和排序放一起
        'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend', 'rest_framework.filters.OrderingFilter'),
        }

### views
from rest_framework.filters import OrderingFilter

class BookInfoListView(ListAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    filter_backends = [MyFilter,OrderingFilter] # 指定排序类
    ordering_fields = "__all__" # 排序所有的字段

### 测试
# 127.0.0.1:8000/book2/?ordering=price   # 默认升序
# 127.0.0.1:8000/book2/?ordering=-price  # - 号,反序
# 127.0.0.1:8000/book2/?ordering=-price,title  # 若第一个条件重复可以继续使用第二个条件
# 127.0.0.1:8000/book2/?ordering=-price,-title

DRF 自定义异常

  • 在原有异常的基础上,可以加上自定义的异常

  • 参考网址

https://www.cnblogs.com/songhaixing/p/14687072.html

### 美多商城示例: 在原有异常的基础上,添加了 数据库异常处理
# utils.exception
from rest_framework.views import exception_handler as drf_exception_handler
import logging
from django.db import DatabaseError
from redis.exceptions import RedisError
from rest_framework.response import Response
from rest_framework import status

# 获取在配置文件中定义的logger,用来记录日志
logger = logging.getLogger('django')

def exception_handler(exc, context):
    """
    自定义异常处理
    :param exc: 异常
    :param context: 抛出异常的上下文
    :return: Response响应对象
    """
    # 调用drf框架原生的异常处理方法
    response = drf_exception_handler(exc, context)

    if response is None:
        view = context['view']
        if isinstance(exc, DatabaseError) or isinstance(exc, RedisError):
            # 数据库异常
            logger.error('[%s] %s' % (view, exc))
            response = Response({'message': '服务器内部错误'}, status=status.HTTP_507_INSUFFICIENT_STORAGE)

    return response

分页介绍

  • DRF 框架提供了三个类来实现分页功能
- PageNumberPagination : 可选分页类, 可以选择查询某一页内容
    # 子类中可定义的属性 : 
    - page_size              # 每页数目
    - page_query_param       # 前端发送的页数关键字名,默认为”page”
    - page_size_query_param  # 前端发送的每页数目关键字名,默认为None
    - max_page_size          # 前端最多能设置的每页数量

- LimitOffsetPagination : 偏移分页类, 可以指定从第几条数据开始查询
    # 子类中可定义的属性
    - default_limit             # 默认限制,默认值与PAGE_SIZE设置一直
    - limit_query_param limit   # 参数名,默认’limit’
    - offset_query_param offset # 参数名,默认’offset’
    - max_limit                 # 最大limit限制,默认None

- CursorPagination : 游标分页类(加密分页), 只能上一页下一页, 不能指定, 并且url中的页码是加密的
    # 子类中可定义的属性
    - cursor_query_param  # 默认查询字段
    - page_size           # 每页数目
    - ordering            # 按什么排序,需要指定
  • 可选分页类的使用 : PageNumberPagination

    • 直接使用类实例化来实现
class PageNumberView(APIView):

    authentication_classes = []
    permission_classes = []

    def get(self, request, *args, **kwargs):
        # 拿到所有数据
        book_obj = BookInfo.objects.all()
        # 使用类实例化得到分页器对象
        page_obj = PageNumberPagination()
        # 设置四个参数
        page_obj.page_size = 2                   # 默认每页显示的条数
        page_obj.max_page_size = 4               # 每页最大显示条数
        page_obj.page_query_param = 'page'       # 查询页数的关键字(?page=2)
        page_obj.page_size_query_param = 'size'  # 每页显示查询条数条件关键字(?page=2&size=2)
        # 调用分页器对象方法对数据进行分页处理
        # 参数: queryset(数据集),request(请求),view(处理分页的视图类)
        page = page_obj.paginate_queryset(queryset=book_obj, request=request, view=PageNumberView)
        # 将分页后的数据对象进行序列化处理
        page_ser = BookInfoSerializer(instance=page, many=True)
        # get_next_link() : 下一页
        # get_previous_link() : 上一页
        return Response({'status': 200, 'msg': '查询成功', 'data': page_ser.data, 'next': page_obj.get_next_link(),'previous': page_obj.get_previous_link()})
        # 或者直接使用分页类自带的Response(自带上一页下一页)
        # return page_obj.get_paginated_response(page_ser.data)

### 测试
# 跳到第1页,每页显示3条数据
http://127.0.0.1:8888/tests/books/page?page=1&size=3
  • 注意事项: 超过显示的页数, 则显示 "无效页面"

  • 自定义普通(页码)分页类: 和上述一模一样的效果(推荐)

### pagination
from rest_framework.pagination import PageNumberPagination

class CustomPageNum(PageNumberPagination):
    page_size = 3
    max_page_size = 4
    page_query_param = 'page'
    page_size_query_param = 'size'

### views
class PageNumberView(ListAPIView):

   
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    pagination_class = CustomPageNum # 导入自定义的分页类

偏移分页类的使用 : LimitOffsetPagination

  • 直接使用类实例化来实现(逻辑与上述类似)

  • 自定义偏移分页类

### pagination
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination

class CustomPageNum(PageNumberPagination):
    ......


class CustomLimitOff(LimitOffsetPagination):
    default_limit = 2
    max_limit = 5
    limit_query_param = 'limit'
    offset_query_param = 'offset'

### views

class PageNumberView(ListAPIView):

    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    pagination_class = CustomLimitOff # 指定分页类

### 测试: http://127.0.0.1:8888/tests/books/page?limit=1&offset=2
# 显示第三条数据

游标(加密)分页类的使用 : CursorPagination

  • 直接使用类实例化来实现(不再赘述)

  • 自定义游标类实现: 只能上一页和下一页

### pagination
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination

class CustomPageNum(PageNumberPagination):
    ......


class CustomLimitOff(LimitOffsetPagination):
    ......

class CustomCursor(CursorPagination):
    cursor_query_param = 'cursor'
    page_size = 2
    ordering = 'id' # 排序

### views
class PageNumberView(ListAPIView):

    authentication_classes = []
    permission_classes = []
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    pagination_class = CustomCursor

### 测试: http://127.0.0.1:8888/tests/books/page?cursor=cD0y
  • 报错问题: Using cursor pagination, but filter class OrderingFilter returned a None ordering.
# 过滤以及排序与加密分页的冲突,如果setting.py设置了自定义的排序就会出现该问题
# 在setting.py文件中将'DEFAULT_FILTER_BACKENDS'注释

三种分页类总结

- CursorPagination也可以被称为加密分页, 会对页码进行加密处理, 访问者无法通过修改页码来进行访问
- 这种方式相对于PageNumberPagination分页的优点是避免因用户任意修改页码, 从而数据库查询数量过大, 造成数据库过载和查询速度慢的问题
- 这个也是数据库查询性能优化, 例如PageNumberPagination中用户可以直接将页码改为10000, 数据库需要从头遍历检索到10000这条记录
- 而CursorPagination中只能查看上下页, 对数据库产生的压力极小, 但对用户的体验不友好