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中只能查看上下页, 对数据库产生的压力极小, 但对用户的体验不友好