过滤源码分析

排序源码分析

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']
posted @ 2019-12-05 08:29  chanyuli  阅读(586)  评论(0编辑  收藏  举报