zoukankan      html  css  js  c++  java
  • django——django_filters 使用与剖析

    django_filters 使用与剖析

    默认已经配置好环境

    使用

    模型如下:

    # models.py
    class PriceOrderModel(ModelBase):
        """询价单"""
        code = models.CharField("编号", max_length=256, null=True, blank=True, unique=True)
        state = models.CharField("状态", max_length=20, choices=[
                      ("discussion", "洽谈中"),("cancel", "取消"),("ordered", "已转订单")
                  ], default='discussion')
        date = models.DateField(default=timezone.localdate, verbose_name='日期')
        volume = models.FloatField("体积", null=True, blank=True)
        amount = models.FloatField("金额", null=True, blank=True)
        total = models.IntegerField("条目数", null=True, blank=True)
        create_user = models.ForeignKey(Users, on_delete=models.CASCADE, related_name="price_orders", verbose_name="创建人", null=True, blank=True)
    

    过滤类如下:

    # my_filter.py
    import django_filters
    from order.models import PriceOrderModel
    
    
    class PriceOrderModelFilter(django_filters.FilterSet):
        company = django_filters.CharFilter(field_name='create_user__company', lookup_expr='icontains', label='客户')
        product = django_filters.CharFilter(method='search_cargos', label='品名')
        brand = django_filters.CharFilter(method='search_brand', label='品牌')
        start_date = django_filters.DateFilter(field_name='date', lookup_expr='gte', label='开始时间')  # 大于等于
        end_date = django_filters.DateFilter(field_name='date', lookup_expr='lte', label='结束时间')  # 小于等于
    
        def search_brand(self, queryset, field, value):
          	# do somethings 
            return queryset.filter(xxxxxx)
    
        class Meta:
            model = PriceOrderModel
            fields = ['type', 'state']
    

    视图类:

    # views.py
    from rest_framework import generics
    from django_filters.rest_framework import DjangoFilterBackend
    
    
    class PriceOrderListView(generics.ListCreateAPIView):
        permission_classes = (IsAuthenticated, IsActivePermission)
        authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
        queryset = PriceOrderModel.objects.all()
        serializer_class = PriceOrderModelSerializer
        filter_backends = [DjangoFilterBackend]
        filter_class = PriceOrderModelFilter
    

    这样通过路由就可以访问了

    源码剖析

    首先ListCreateAPIView继承了如下:

    class ListCreateAPIView(mixins.ListModelMixin,
                            mixins.CreateModelMixin,
                            GenericAPIView):
    

    GenericAPIView类中,实现了一个叫做filter_queryset的方法

    class GenericAPIView(views.APIView):  
    		# .....
        def filter_queryset(self, queryset):
            """
            Given a queryset, filter it with whichever filter backend is in use.
    
            You are unlikely to want to override this method, although you may need
            to call it either from a list view, or from a custom `get_object`
            method if you want to apply the configured filtering backend to the
            default queryset.
            """
            for backend in list(self.filter_backends):
                queryset = backend().filter_queryset(self.request, queryset, self)
            return queryset
    

    这个方法会根据你定义的filter_backends,去执行filter_queryset方法

    filter_backends定义的是DjangoFilterBackend,所以要去找DjangoFilterBackendfilter_queryset方法

    class DjangoFilterBackend(metaclass=RenameAttributes):
         def filter_queryset(self, request, queryset, view):
            filterset = self.get_filterset(request, queryset, view) # ①
            if filterset is None:  # ②
                return queryset
    
            if not filterset.is_valid() and self.raise_exception:  # ③
                raise utils.translate_validation(filterset.errors)
            return filterset.qs
    
    1. get_filterset方法①:

      1. def get_filterset(self, request, queryset, view):
          	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)
        
        1. get_filterset_class方法①.1

          1. 这个方法内容有点多,简言之

          2. # 拿到自定义的PriceOrderModelFilter
            filterset_class = getattr(view, 'filter_class', None)  
            
          3. # 拿到PriceOrderModelFilter,在queryset有数据集的时候,返回PriceOrderModelFilter
            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
            # 。。。。。
            return None
            
        2. 这样get_filterset_class方法就拿到了PriceOrderModelFilter

        3. # 这一行就不解释了
          if filterset_class is None:
              return None
          
        4. 然后执行get_filterset_kwargs方法

          1. # 封装请求参数
            def get_filterset_kwargs(self, request, queryset, view):
                return {
                    'data': request.query_params,
                    'queryset': queryset,
                    'request': request,
                }
            
          2. data就是筛选的请求url

          3. <QueryDict: {'type': ['exists'], 'state': ['discussion'], 'company': [''], 'product': [''], 'brand': [''], 'start_date': ['2021-5-1'], 'end_date': ['2021-5-20']}>
            
          4. queryset就是还没有被筛选的数据集

          5. request就是restframework再封装的请求

        5. 最后拿到实例化后的PriceOrderModelFilter对象

      2. 拿到PriceOrderModelFilter对象②

    # 没有filter实例对象就直接返回所有的数据  ②
    if filterset is None:
      	return queryset
    
    # 这一步内容比较多,只挑部分有意义的讲
    if not filterset.is_valid() and self.raise_exception:  # ③
    
    1. 大概流程就是会将PriceOrderModelFilter里面的内容转化成django的form类

    2. 通过django的form校验,对url请求的参数进行校验

    3. 有错误就报错,没有就往下走

    4. 讲一下将PriceOrderModelFilter的内容转化为form的细节

      1. # 会对url的customer键,对模型的客户公司(create_user__company)进行搜索,按照`icontains`(忽略大小写的包含搜索,也就是忽略大小写的模糊搜索)搜索
        customer = django_filters.CharFilter(field_name='customer__company', lookup_expr='icontains', label='客户')
        # 自定义方法,最后返回对应模型的queryset数据集就行
        product = django_filters.CharFilter(method='search_cargos', label='品名')
        # 对date字段,进行范围搜索gte开始时间lte结束时间
        start_date = django_filters.DateFilter(field_name='date', lookup_expr='gte', label='开始时间')
            end_date = django_filters.DateFilter(field_name='date', lookup_expr='lte', label='结束时间')
        
      #  Meta下的fields,这里定义的字段,会进行精准搜索
      class Meta:
        	model = PriceOrderModel
        	fields = ['type', 'state'] 
      
    5. 最后执行filterset.qs

      1. # django_filters/restframework/backends.py
        # _qs防止重复搜索
        @property
        def qs(self):
            if not hasattr(self, '_qs'):
                qs = self.queryset.all()
                if self.is_bound:
                    # ensure form validation before filtering
                    self.errors
                    qs = self.filter_queryset(qs)
                    self._qs = qs
            return self._qs
        
      2. 重点来看filter_queryset方法

        1. # django_filters/filterset.py
          def filter_queryset(self, queryset):
              """
              Filter the queryset with the underlying form's `cleaned_data`. You must
              call `is_valid()` or `errors` before calling this method.
          
              This method should be overridden if additional filtering needs to be
              applied to the queryset before it is cached.
              """
              for name, value in self.form.cleaned_data.items():
                  queryset = self.filters[name].filter(queryset, value)
                  assert isinstance(queryset, models.QuerySet), 
                      "Expected '%s.%s' to return a QuerySet, but got a %s instead." 
                      % (type(self).__name__, name, type(queryset).__name__)
              return queryset
          
        2. 去前面转化好的form里面,获取cleaned_data,然后开始遍历

        3. 通过遍历,拿到每一个django_filters.Filed,再执行filter方法

        4. 这里的filter方法

          1. # django_filters/filters.py
            def filter(self, qs, value):
                if value in EMPTY_VALUES:
                    return qs
                if self.distinct:
                    qs = qs.distinct()
                lookup = '%s__%s' % (self.field_name, self.lookup_expr) # ✨✨✨
                qs = self.get_method(qs)(**{lookup: value})  # ⭕️
                return qs
            
          2. # ✨✨✨
            self.field_name = field_name
            self.lookup_expr = lookup_expr
            
          3. # django_filters/filters.py
            # ⭕️看是要过滤还是排除
            def get_method(self, qs):
                """Return filter method based on whether we're excluding
                   or simply filtering.
                """
                return qs.exclude if self.exclude else qs.filter
            
          4. (**{lookup: value})如果看不明白的话,可以点击参考

        5. 这样就把可筛选的数据集筛选出来了

    6. 建议搜索的字段别太多,每一次的filter,都是一个数据库访问,会影响性能。


    做了一个流程图
    django_filters流程图

    创作不易,转载请标明出处和附带链接

  • 相关阅读:
    hdfs 复制路径下所有文件
    吐槽scala
    scala
    spark 你要喧宾夺主么?好好干。
    信赖域算法
    scala shuffle
    自动梯度求解 反向传播算法的另外一种视角
    spark 2.0 Vector toBreeze
    自己玩的git
    js判断浏览器
  • 原文地址:https://www.cnblogs.com/pywjh/p/14790959.html
Copyright © 2011-2022 走看看