25 过滤组件
一、filter过滤类源码分析
现在我们就先以一个简单的群查的接口为例:
现在我们就上述群查接口先来走一下filter源码,我们既然要用filter第三方的组件,我们就得搞清楚,他要在哪里配置呀,不然也不会用呀
所以我们该从哪里作为入口,去找这个filter的源码呢?
我们先想一下,我们既然继承了这个ListAPIView这个类,能直接帮我们实现了群查的功能,也就是说我们只需要配置一下model和序列化,他就可以帮我们实现群查的数据,并且得到结果返回,那么我们在之前介绍视图家族的时候,分析过drf中的视图家族了。详细请看:
10 drf 视图基类(APIView、GenericAPIView)
现在我们就从ListAPIView上手,去看看它的内部做是否有filter的线索吧
-
首先我们进入ListAPIView类中
-
但是实现群查的是它的第一个父类mixins中调用的filter_queryset()方法,只是在这之前,要先找在哪里配置的
-
根据以上图中的分析,我们来到GenericAPIView通用视图类中
-
在GenericAPIView通用视图类中我们发现了,filter_backend是在api_settings中配置的,所以我们在去到api_settings中看看他配置的是啥?---哈哈:这里剧透,配置的内容是空列表,所以也就很明显的得出结果,这个一定是要我们自己认为的手动配置的,至于如何配,我们先来验证一下,它到底是如何配置的吧
-
基于以上的验证,果然django自己配置的filter是空的列表。
-
这也就说明了,filter_backend一定是需要我们自己去配置的,而且再rest_framework内部一定实现了方法来启动我们配置的filter_backend
-
所以我们现在来看一下再rest_framework中到底是哪里实现了filter配置启动的方法
-
我们就直接从api_settings为入口找到rest_framework的配置文件包
-
好了到这里,我们就直接引出下面我们要说的两个部分的内容,排序、 搜索
二、排序
根据以上的推理我们已经找到了filter配置的入口了,那紧接着我们就来看一下在上面的OrderingFilter类到底做了哪些事情
-
我们可以看到这个OrderingFilter类是继承了BaseFilterBackend这个类的,我们直接定位到上面的filter.py文件
-
看到父类中说明,在它的子类中,必须重写这个filter_queryset方法才可以用,那行吧,我们在来看看他的子类OreringFilter
-
将上述方法的结果返回给get_ordering,get_ordering将结果返回给filter_queryset,然后执行queryset.order(字段名)。queryset就是我们自已在view视图类中定义好的模型类。
下面就是重点来了,如何使用这个排序过滤,返回正确的结果:
from . import models
from rest_framework.generics import ListAPIView
# 我们知道OrderingFiler是在哪一个类中的了,所以这个直接导入
from rest_framework.filters import OrderingFilter
class FreeCourseListAPIView(ListAPIView):
queryset = models.User.objects.filter(is_delete=False, is_show=True).order_by('-orders').all()
serializer_class = serializer.RegisterModelSerializer
# 还记得在GenericAPIView中的filter_backends吧,我们这是可以在我们自己的视图类中配置了
# 相当于是局部配置了,优先使用
# 在url中我们系需要携带排序的参数即可
# http://127.0.0.1:8000/user/?odering=price,id,students这里的意思是当price相同的时候id排序
filter_backends = [OrderingFilter]
# 这是我们自己要规定排序的字段
ordering_fields = ['price', 'id', 'students']
三、搜索
搜索的源码过程,和排序的一摸一样,参照排序的源码,即可,直接定位到filter.py文件中的SearchFilter类即可,
from . import models
from rest_framework.generics import ListAPIView
# 我们知道OrderingFiler是在哪一个类中的了,所以这个直接导入
from rest_framework.filters import OrderingFilter,SearchFilter
class FreeCourseListAPIView(ListAPIView):
queryset = models.User.objects.filter(is_delete=False, is_show=True).order_by('-orders').all()
serializer_class = serializer.RegisterModelSerializer
# SearchFilter搜索
filter_backends = [OrderingFilter,SearchFilter]
# 参与排序的字段: ordering=-price,id
ordering_fields = ['price', 'id', 'students']
# 参与搜索的字段: search=python (name字段中带python就ok)
search_fields = ['name', 'brief']
四、自定义limt限制过滤器
我们基于以上的排序、搜索过滤器来明白了,他们两个类都继承了BaseFilterBackend类,并且父类中明确规定一定要重写filter_queryset()方法,所以我们在用这两个过滤组件的时候,只是在视图类局部配置了过滤类,参与排序/搜索的字段。这都是类中自己封装好的
如果我们想要自定义过滤类的话,需要去继承BaseFilterBackend类,重写它的filter_queryset()方法即可
比如我们现在就写一个限制返回多少条结果给前台页面的过滤器
# 在当前的app下,新建一个.py文件
# 将BaseFilterBackend导入
from rest_framework.filters import BaseFilterBackend
# 自定义一个LimtFilter类,继承BaseFilterBackend
class LimitFilter(BaseFilterBackend):
# 重写filter_queryset方法
def filter_queryset(self, request, queryset, view):
# queryset就是这句话
# queryset = models.User.objects.filter(is_delete=False, is_show=True).order_by('-orders').all()
limit = request.query_params.get('limit') # 在url携带的参数去limit
try:
return queryset[:int(limit)] # 然后返回
except:
return queryset # 没有limit时,全部结果返回
# 视图类view中配置
from . import models
from rest_framework.generics import ListAPIView
# 导入我们自己定义的过滤类
from .myfilter import LimitFilter
class FreeCourseListAPIView(ListAPIView):
queryset = models.User.objects.filter(is_delete=False, is_show=True).order_by('-orders').all()
serializer_class = serializer.RegisterModelSerializer
# 将自己些定义的过滤类配置上就ok了
filter_backends = [LimitFilter]
注意:自定义的过滤器可以不用继承父类,但是必须要实现filter_queryset()方法
五、分类筛选
django和drf都实现不了分类,只能依赖第三方插件
# 首先我们安装第三方组件
pip install djanog_filter
原生用法:
# view.py视图类中
from . import models
from rest_framework.generics import ListAPIView
# 分类筛选
from django_filters.rest_framework import DjangoFilterBackend
class FreeCourseListAPIView(ListAPIView):
queryset = models.User.objects.filter(is_delete=False, is_show=True).order_by('-orders').all()
serializer_class = serializer.RegisterModelSerializer
# 分类筛选中要配置DjangoFilterBackend
filter_backends = [DjangoFilterBackend]
# 参与分类筛选的字段,
# course_category 一定是外键
# ?course_category=1 得到course_category=1的数据
# 参与分类筛选的字段,所有字段都可以,但是用于分组的字段更有意义
filter_fields = ['course_category']
filterset_fields = ['course_category']
filter_class = ['course_category']
filterset_class = ['course_category']
六、区间筛选(分类筛选的高级使用)
基于django-filter插件,完成指定区间筛选(一般都是对于数字字段)
# 创建一个filter.py文件
from django_filters.rest_framework.filterset import FilterSet
from django_filters import filters
from . import models
class CourseFilterSet(FilterSet):
max_price = filters.NumberFilter(field_name='price', lookup_expr='lte')
min_price = filters.NumberFilter(field_name='price', lookup_expr='gte')
class Meta:
model = models.Course
fields = ['course_category', 'max_price', 'min_price']
源码分析:
from django_filters.rest_framework import DjangoFilterBackend
-
进入DjangoFilterBackend,找到filter_queryset()方法
-
进入get_filterset()方法
-
进入get_filterset_class()方法
七、分页
同样以上面的群查数据的接口为例:我们先来分析一下分页过滤如何去操作,群查的listAPIView是继承了Mixins.ListModelMixin和GenericAPIView的,我们在将排序和搜索的时候,我们在父类GenericAPIView中没有看到和分页有关的参数配置
那么我们就转移目标去Mixins.ListModelMixin类中去找
-
直接进入到Mixins.ListModelMixin类中,我们会发现list方法内部在群查完得到结果以后,直接开始page配置了
-
进入到paginate_queryset()方法,我们来看一下,它的源码
以上只是找到了分页类的位置在哪里,下面我们真正的来到paginate_queryset()方法
1. 分页过滤中有三种分页
PageNumberPagination :普通分页
LimitOffsetPagination : 显示条数分页,偏移
CursorPagination :游标分页,和普通分页的区别在于,将url中前一页,后一页的直接加密
2. 分析普通分页
测试:
# 在view类视图中我们去配置一下
from rest_framework.generics import ListAPIView
from . import models, serializers
# 普通分页
from rest_framework.pagination import PageNumberPagination
class FreeCourseListAPIView(ListAPIView):
queryset = models.Course.objects.filter(is_delete=False, is_show=True).order_by('-orders').all()
serializer_class = serializers.FreeCourseModelSerializer
# 分页器
pagination_class = PageNumberPagination # 这里我们会发现,返回不了结果
原因如下:
解决如下:
# 通过自定义分页的方法,来配置
# 在当前的apps下创建一个paginations.py的文件
from rest_framework.pagination import PageNumberPagination
# 我们直接来继承子类就好了,只是需要配置它的一些参数即可
class MyPageNumberPagination(PageNumberPagination):
# 默认一页条数
page_size = 2
# 选择哪一页的key
page_query_param = 'page'
# 用户自定义一页条数
page_size_query_param = 'page_size'
# 用户自定义一页最大控制条数
max_page_size = 10
# view.py视图类中
from rest_framework.generics import ListAPIView
from . import models, serializers
# 自定义的普通分页
from .paginations import MyPageNumberPagination
class FreeCourseListAPIView(ListAPIView):
queryset = models.Course.objects.filter(is_delete=False, is_show=True).order_by('-orders').all()
serializer_class = serializers.FreeCourseModelSerializer
# 分页器
pagination_class = MyPageNumberPagination
3. 分析limit限制分页
用法:
# 在创建的paginations.py的文件中,自定义一个限制条数分页
class MyLimitOffsetPagination(LimitOffsetPagination):
# 默认一页条数
default_limit = 2
# 从offset开始往后显示limit条
limit_query_param = 'limit'
offset_query_param = 'offset'
max_limit = 2
# view.py视图类中
from rest_framework.generics import ListAPIView
from . import models, serializers
# 自定义的普通分页
from .paginations import MyLimitOffsetPagination
class FreeCourseListAPIView(ListAPIView):
queryset = models.Course.objects.filter(is_delete=False, is_show=True).order_by('-orders').all()
serializer_class = serializers.FreeCourseModelSerializer
# 分页器
pagination_class = MyLimitOffsetPagination
4. 分析course游标分页
总结:只是将url中做了一层加密,其他的没有不同,只需要知道用法即可,源码流程就参照以上分析,自己走即可
用法:
# 在创建的paginations.py的文件中,自定义一个游标分页
class MyCursorPagination(CursorPagination):
cursor_query_param = 'cursor'
page_size = 2
page_size_query_param = 'page_size'
max_page_size = 2
# ordering = 'id' # 默认排序规则,不能和排序过滤器OrderingFilter共存
# view.py视图类中
from rest_framework.generics import ListAPIView
from . import models, serializers
# 自定义的普通分页
from .paginations import MyCursorPagination
class MyCursorPagination(ListAPIView):
queryset = models.Course.objects.filter(is_delete=False, is_show=True).order_by('-orders').all()
serializer_class = serializers.FreeCourseModelSerializer
# 分页器
pagination_class = MyLimitOffsetPagination