• 过滤源码分析


    排序源码分析

    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']
    
  • 相关阅读:
    Hibernate中使用Spring Data JPA
    Spring Boot入门——全局异常处理
    Spring Boot入门——Redis
    Spring Boot入门——集成Mybatis
    Spring Boot入门——JDBCTemplate使用及其相关问题解决
    Spring Boot连接Mysql数据库问题解决
    Spring Boot入门——JPA
    Spring Boot入门——tomcat配置
    Spring Boot 配置文件
    启动图案配置
  • 原文地址:https://www.cnblogs.com/chanyuli/p/11986943.html
Copyright © 2020-2023  润新知