ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

django——django_filters 使用与剖析

2021-05-20 18:35:03  阅读:324  来源: 互联网

标签:filter self queryset django 剖析 filters class filterset


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,都是一个数据库访问,会影响性能。

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

标签:filter,self,queryset,django,剖析,filters,class,filterset
来源: https://www.cnblogs.com/pywjh/p/14790959.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有