过滤源码分析
排序源码分析
ListAPIView是视图家族的工具视图类,因为继承了ListModelMixin类,所以有了list群查方法。而排序就是在这个list方法里面进行的。
ListModelMixin
class ListModelMixin:
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
#看名字也知道是这一步完成的过滤
queryset = self.filter_queryset(self.get_queryset())
#这一步完成的是获取分页的页数
page = self.paginate_queryset(queryset)
#如果没有分页直接做返回,如果有的话就在这里面再做处理
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
这里的filter_queryset点不过去,所以如果我们想要给自己的类加上过滤条件,就要进入GenericAPIView(ListAPIView也继承了这个),发现了一个类属性filter_backends = api_settings.DEFAULT_FILTER_BACKENDS ,表示在filter_backends 里有一些类,类里面有一些方法,直接定位,找到一个filters.py
进入filters.py文件,这个文件里有一个排序类OrderingFilter,一个搜索类SearchFilter,和一个基类BaseFilterBackend,排序类和搜索类继承这个基类,并且三个类都有 filter_queryset 方法,这就很明显要我们继承这个类重写这个方法就好了。
先分析一下OrderingFilter的 filter_queryset方法,发现他是传进去一个queryset,返回一个queryset,那么肯定是在这里面给过滤了,在这里面把他的数据量变少就好了。怎么做的?queryset有一个特点,那就是他可以继续使用queryset的方法,可以继续加filter!
看一下 OrderingFilter 的filter_queryset 是怎么写的
def filter_queryset(self, request, queryset, view):
ordering = self.get_ordering(request, queryset, view)
if ordering:
#这一句就完成了排序,所以就看ordering是怎么拿到的
return queryset.order_by(*ordering)
return queryset
看一下get_ordering拿到的是什么东西赋值给 ordering
def get_ordering(self, request, queryset, view):
"""
Ordering is set by a comma delimited ?ordering=... query parameter.
The `ordering` query parameter can be overridden by setting
the `ordering_param` value on the OrderingFilter or by
specifying an `ORDERING_PARAM` value in the API settings.
"""
#query_params表示前端发送的请求所带的参数,想要在这个参数里面获取ordering_param表示的值,所以去看要去找到ordering_param这个属性值,结果在配置文件中找到了'ORDERING_PARAM': 'ordering',所以这里的ordering_param代表的就是ordering,我们只需要在请求中附带参数,用“ordering=xxx”可以了。
params = request.query_params.get(self.ordering_param)
if params:
#这里表示请求中可以传多个排序条件,通过逗号
fields = [param.strip() for param in params.split(',')]
#为了防止我们乱传一些ordering=asdfasdfa这样的东西,这个方法实现了排除不符合条件的排序条件,进入这个方法看一下是怎么做的
ordering = self.remove_invalid_fields(queryset, fields, view, request)
if ordering:
return ordering
# No ordering was included, or all the ordering fields were invalid
return self.get_default_ordering(view)
remove_invalid_fields
def remove_invalid_fields(self, queryset, fields, view, request):
#用这个方法来获取允许的排序条件。
valid_fields = [item[0] for item in self.get_valid_fields(queryset, view, {'request': request})]
return [term for term in fields if term.lstrip('-') in valid_fields and ORDER_PATTERN.match(term)]
进入get_valid_fields方法
def get_valid_fields(self, queryset, view, context={}):
#通过反射从我们自己的视图类中获取'ordering_fields'
valid_fields = getattr(view, 'ordering_fields', self.ordering_fields)
所以很明白了,我们需要在自己的视图类中配置 ordering_fields=[ ],比如id ,price等等,这样请求参数传的不符合的数据就会被抛掉。
比如:
from django_filters.rest_framework import DjangoFilterBackend
from .filters import CourseFilterSet
from .filters import LimitFilter
class FreeCourseListAPIView(ListAPIView):
queryset = models.Course.objects.filter(is_delete=False, is_show=True).order_by('-orders').all()
serializer_class = serializers.FreeCourseModelSerializer
# 配置过滤器类
filter_backends = [OrderingFilter, LimitFilter] # LimitFilter自定义过滤器
# 参与排序的字段: ordering=-price,id
ordering_fields = ['price', 'id']
filters.py
from rest_framework.filters import BaseFilterBackend
class LimitFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
limit = request.query_params.get('limit')
try:
return queryset[:int(limit)]
except:
return queryset
搜索源码分析
和上面的一样,从SearchFilter进去找,
看到这个方法
def get_search_fields(self, view, request):
"""
Search fields are obtained from the view, but the request is always
passed to this method. Sub-classes can override this method to
dynamically change the search fields based on request content.
"""
#从视图里面拿search_fields
return getattr(view, 'search_fields', None)
所以只要在视图类里配置这个search_fields就可以了。
class FreeCourseListAPIView(ListAPIView):
queryset = models.Course.objects.filter(is_delete=False, is_show=True).order_by('-orders').all()
serializer_class = serializers.FreeCourseModelSerializer
# 配置过滤器类
# filter_backends = [OrderingFilter, LimitFilter] # LimitFilter自定义过滤器
filter_backends = [OrderingFilter, SearchFilter, DjangoFilterBackend]
# 参与排序的字段: ordering=-price,id
ordering_fields = ['price', 'id', 'students']
# 参与搜索的字段: search=python (name或brief字段中带python就ok)
search_fields = ['name', 'brief']
分页器源码分析
class ListModelMixin:
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
#看名字也知道是这一步完成的过滤
queryset = self.filter_queryset(self.get_queryset())
#这一步完成的是获取分页的页数
page = self.paginate_queryset(queryset)
#如果没有分页直接做返回,如果有的话就在这里面再做处理
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
直接去找到pagination.py文件
里面有四个类, BasePagination基类,CursorPagination根据游标分类 , LimitOffsetPagination根据偏移分页 ,PageNumberPagination基础分页。看一下PageNumberPagination,有一些配置,我们只需要设置这些配置就可以了。
page_size = api_settings.PAGE_SIZE
django_paginator_class = DjangoPaginator
# Client can control the page using this query parameter.
page_query_param = 'page'
page_query_description = _('A page number within the paginated result set.')
# Client can control the page size using this query parameter.
# Default is 'None'. Set to eg 'page_size' to enable usage.
page_size_query_param = None
page_size_query_description = _('Number of results to return per page.')
# Set to an integer to limit the maximum page size the client may request.
# Only relevant if 'page_size_query_param' has also been set.
max_page_size = None
有经验了,知道一定是在视图类里面配置。
views
from .paginations import CoursePageNumberPagination, CourseLimitOffsetPagination, CourseCursorPagination
# 分类筛选:django-filter:filter_backends配置DjangoFilterBackend,再在filter_fields中配置分组筛选的字段
from django_filters.rest_framework import DjangoFilterBackend
from .filters import CourseFilterSet
class FreeCourseListAPIView(ListAPIView):
queryset = models.Course.objects.filter(is_delete=False, is_show=True).order_by('-orders').all()
serializer_clas s = serializers.FreeCourseModelSerializer
# 配置过滤器类
# filter_backends = [OrderingFilter, LimitFilter] # LimitFilter自定义过滤器
filter_backends = [OrderingFilter, SearchFilter, DjangoFilterBackend]
# 参与排序的字段: ordering=-price,id
ordering_fields = ['price', 'id', 'students']
# 参与搜索的字段: search=python (name字段中带python就ok)
search_fields = ['name', 'brief']
# 分页器
pagination_class = CoursePageNumberPagination
# pagination_class = CourseLimitOffsetPagination
# pagination_class = CourseCursorPagination
分页器Pagination
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination
class CoursePageNumberPagination(PageNumberPagination):
# 默认一页条数
page_size = 2
# 选择哪一页的key
page_query_param = 'page'
# 用户自定义一页条数
page_size_query_param = 'page_size'
# 用户自定义一页最大控制条数
max_page_size = 10
class CourseLimitOffsetPagination(LimitOffsetPagination):
# 默认一页条数
default_limit = 2
# 从offset开始往后显示limit条
limit_query_param = 'limit'
offset_query_param = 'offset'
max_limit = 2
class CourseCursorPagination(CursorPagination):
cursor_query_param = 'cursor'
page_size = 2
page_size_query_param = 'page_size'
max_page_size = 2
# ordering = 'id' # 默认排序规则,不能和排序过滤器OrderingFilter共存
注意:分页后的结果和没有分页的结果是不一样的,分页后返回的是字典,有下一页的路由,上一页的路由,和result,分页后的结果是把没有分页的结果丢进result返回的
分类筛选过滤器
普通使用
分类筛选 drf 和 django 都干不了,需要安装一个插件
pip install django-filters
插个插件可以解决前后端不分离的分类筛选,也可以完成前后端分离的筛选
用法:
from .paginations import CoursePageNumberPagination, CourseLimitOffsetPagination, CourseCursorPagination
# 分类筛选:django-filter:filter_backends配置DjangoFilterBackend,再在filter_fields中配置分组筛选的字段
from django_filters.rest_framework import DjangoFilterBackend
from .filters import CourseFilterSet
class FreeCourseListAPIView(ListAPIView):
queryset = models.Course.objects.filter(is_delete=False, is_show=True).order_by('-orders').all()
serializer_clas s = serializers.FreeCourseModelSerializer
# 配置过滤器类
# filter_backends = [OrderingFilter, LimitFilter] # LimitFilter自定义过滤器
filter_backends = [OrderingFilter, SearchFilter, DjangoFilterBackend]
# 参与排序的字段: ordering=-price,id
ordering_fields = ['price', 'id', 'students']
# 参与搜索的字段: search=python (name字段中带python就ok)
search_fields = ['name', 'brief']
# 参与分类筛选的字段:所有字段都可以,但是用于分组的字段更有意义
filter_fields = ['course_category']
分类筛选:django-filter:filter_backends配置DjangoFilterBackend,再在filter_fields中配置分组筛选的字段,就完成了分类筛选的基本使用
高级使用
区间筛选过滤器
我们说过的,过滤器就是把数据给减少了返回,所以他一定是走了 filter_queryset 方法的,看一下 django-filter 里面,找到这个方法
进入DjangoFilterBackend,找到 django-filter
def filter_queryset(self, request, queryset, view):
filterset = self.get_filterset(request, queryset, view)
#如果为空,就直接返回原来的queryset,就等于啥也没过滤
if filterset is None:
return queryset
if not filterset.is_valid() and self.raise_exception:
raise utils.translate_validation(filterset.errors)
return filterset.qs
进去看一下get_filterset方法,是怎么获取filterset的
def get_filterset(self, request, queryset, view):
#这里一定不为空,不然的话就返回none了,然后前面那个方法就原样返回queryset了,等于什么也没做。所以进去看一下get_filterset_class方法
filterset_class = self.get_filterset_class(view, queryset)
if filterset_class is None:
return None
kwargs = self.get_filterset_kwargs(request, queryset, view)
return filterset_class(**kwargs)
进入get_filterset_class
def get_filterset_class(self, view, queryset=None):
"""
Return the `FilterSet` class used to filter the queryset.
"""
#从视图类中找filterset_class和filterset_fields,我们在视图类中配置的是filter_fields,所以这两个都是none
filterset_class = getattr(view, 'filterset_class', None)
filterset_fields = getattr(view, 'filterset_fields', None)
# TODO: remove assertion in 2.1
#这里又判断了,如果filterset_class是空,有filter_class也行,但是这个也没有
if filterset_class is None and hasattr(view, 'filter_class'):
utils.deprecate(
"`%s.filter_class` attribute should be renamed `filterset_class`."
% view.__class__.__name__)
filterset_class = getattr(view, 'filter_class', None)
# TODO: remove assertion in 2.1
#这里才是我们能进去的地方
if filterset_fields is None and hasattr(view, 'filter_fields'):
utils.deprecate(
"`%s.filter_fields` attribute should be renamed `filterset_fields`."
% view.__class__.__name__)
#把我们在视图类里配置的filter_fields反射出来给了filterset_fields
filterset_fields = getattr(view, 'filter_fields', None)
#空,不走
“1” if filterset_class:
filterset_model = filterset_class._meta.model
# FilterSets do not need to specify a Meta class
if filterset_model and queryset is not None:
assert issubclass(queryset.model, filterset_model), \
'FilterSet model %s does not match queryset model %s' % \
(filterset_model, queryset.model)
return filterset_class
#满足,走这个
if filterset_fields and queryset is not None:
#看一下这个filterset_base,ctrl+左键一点,跑上面找到了一个filterset_base = filterset.FilterSet,然后看一下他导入的filterset,是从哪里导入的,结果是 from . import filters, filterset,这个 . ,看一下目录,他代表的就是django_filters下的rest_framwork,那么filterset就找到了
MetaBase = getattr(self.filterset_base, 'Meta', object)
#所以这里的AutoFilterSet类就相当于继承了filterset.FilterSet
“2” class AutoFilterSet(self.filterset_base):
class Meta(MetaBase):
#这里能找到他的类是什么
model = queryset.model
fi elds = filterset_fields
return AutoFilterSet
return None
所以看上面的“1”处,如果你给的是类,他就返回一个类,如果你给的是一个字段,他也
把你格式化成类返回了(看“2”),所以我们把这个AutoFilterSet拉出来,放在自己写的过滤器里就好了
filters
# 基于django-filter插件,完成指定区间筛选(一般都是对应数字字段)
from django_filters.rest_framework.filterset import FilterSet
from . import models
#这个名字可以改
class CourseFilterSet(FilterSet):
class Meta:
model = models.Course
fields = ['course_category']
写完后,前面的普通使用的filter_fields = ['course_category']也可以写成filterset_class=CourseFilterSet
问题:这样好像变得更麻烦了,我为什么不直接写个字段就好呢?
答案:因为后面的方式我们可以自定义字段,比如实现区间筛选。
from django_filters.rest_framework.filterset import FilterSet
from django_filters import filters
from . import models
class CourseFilterSet(FilterSet):
#field_name表示我这个自定义字段跟price这个字段有关系,lookup_expr表示筛选规则,其实这个内部就是做了基于双下划綫的规则
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']