zoukankan      html  css  js  c++  java
  • Django-Filter源码解析一

    Django Filter源码解析

    最近在看Django-FIlter项目的源码,学习一下别人的开发思想;

    整体介绍

    首先,我从其中一个测试用例作为入口,开始了debug之路,一点一点的断点,分析它的执行顺序,如图:

    ok,下面从代码的层面进行分析:

    1. url

      url(r'^books/$', FilterView.as_view(model=Book)),

    2. view函数,这里的实现方式应该是借鉴了Django中自带的ListView,其同样的继承了MultipleObjectTemplateResponseMixin, BaseListView,继承的好处在于可以复用其已经封装好的方法,最终可以简单的实现展示,详情可以看

      class FilterView(MultipleObjectTemplateResponseMixin, BaseFilterView):
         """
        Render some list of objects with filter, set by `self.model` or
        `self.queryset`.
        `self.queryset` can actually be any iterable of items, not just a queryset.
        """
         template_name_suffix = '_filter'

    1. 基础过滤view,这里做的就是类似BaseListView的功能,获取计算出来的查询集,将结果渲染后返回;

      class BaseFilterView(FilterMixin, MultipleObjectMixin, View):
         """
        显示对象的过滤功能的基view,实现的方式类似BaseListView
        """
         def get(self, request, *args, **kwargs):
             # 获取过滤的类
             filterset_class = self.get_filterset_class()
             # 传入类,构造参数,返回类的对象
             self.filterset = self.get_filterset(filterset_class)

             # 重新赋值MultipleObjectMixin中的object_list
             if self.filterset.is_valid() or not self.get_strict():
                 self.object_list = self.filterset.qs
             else:
                 self.object_list = self.filterset.queryset.none()

             context = self.get_context_data(filter=self.filterset,
                                             object_list=self.object_list)
             return self.render_to_response(context)
    2. 接下来就分成三件事:a.获取过滤类,b.根据过滤类获取过滤对象,c.过滤,下面的代码就做到了前面两步;

      class FilterMixin(metaclass=FilterMixinRenames):
         """
        A mixin that provides a way to show and handle a FilterSet in a request.
        提供控制过滤的方法
        """

         def get_filterset_class(self):
             """
            Returns the filterset class to use in this view
            返回过滤类
            """
             if self.filterset_class:  # 避免重复创建
                 return self.filterset_class
             elif self.model:
                 # 使用了工厂模式
                 return filterset_factory(model=self.model, fields=self.filterset_fields)
             else:
                 msg = "'%s' must define 'filtserset_class' or 'model'"
                 raise ImproperlyConfigured(msg % self.__class__.__name__)

         def get_filterset(self, filterset_class):
             """
            Returns an instance of the filterset to be used in this view.
            """
             kwargs = self.get_filterset_kwargs(filterset_class)
             return filterset_class(**kwargs)
         
      def filterset_factory(model, fields=ALL_FIELDS):
         # 根据model生成相对应的FilterSet,比如model是Book,那么就会生成BookFilterSet的实例
         meta = type(str('Meta'), (object,), {'model': model, 'fields': fields})
         # 使用type进行创建类,并且继承了FilterSet类
         filterset = type(str('%sFilterSet' % model._meta.object_name),
                          (FilterSet,), {'Meta': meta})
         return filterset
    3. 接下来就是重头戏,开始过滤了!下面会被调用是因为调用了FilterSet中的qs方法;

    class BaseFilterSet(object):
       # ...
       def __init__(self, data=None, queryset=None, *, request=None, prefix=None):
           # 如果没传进来则在全部的基础进行过滤
           if queryset is None:
               queryset = self._meta.model._default_manager.all()

           model = queryset.model
    # ...
           self.filters = copy.deepcopy(self.base_filters)

           # propagate the model and filterset to the filters
           for filter_ in self.filters.values():
               filter_.model = model
               filter_.parent = self
       

    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会在每次执行后的queryset上继续执行,达到过滤的效果
               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

       @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

    或许你会疑惑self.filtersself.base_filters)里面的内容是什么,其实就是每个需要过滤的数据库字段到具体的Filter的映射,那这个是哪里进行计算赋值的呢?其实是被元类给拦截了,下面则会把该的内容是从类的get_filters方法中获取得到的,

    class FilterSet(BaseFilterSet, metaclass=FilterSetMetaclass):
       pass

    class FilterSetMetaclass(type):
       # 元类,FilterSet创建时最终会创建FilterSetMetaclass的实例
       def __new__(cls, name, bases, attrs):
    ...
           
           new_class = super().__new__(cls, name, bases, attrs)
           new_class._meta = FilterSetOptions(getattr(new_class, 'Meta', None))
           new_class.base_filters = new_class.get_filters()  # 会被
           

    class BaseFilterSet(object):
       # ...
       
    @classmethod
       def get_filters(cls):
           """
          Get all filters for the filterset. This is the combination of declared and
          generated filters.
          获取到每个需要过滤的数据库字段到Filter的映射
          比如:{title: CharFilter}
          """

           # No model specified - skip filter generation
           if not cls._meta.model:
               return cls.declared_filters.copy()

           # Determine the filters that should be included on the filterset.
           filters = OrderedDict()
           fields = cls.get_fields()
           undefined = []

           for field_name, lookups in fields.items():
               field = get_model_field(cls._meta.model, field_name)

               # warn if the field doesn't exist.
               if field is None:
                   undefined.append(field_name)

               for lookup_expr in lookups:
                   filter_name = cls.get_filter_name(field_name, lookup_expr)

                   # If the filter is explicitly declared on the class, skip generation
                   if filter_name in cls.declared_filters:
                       filters[filter_name] = cls.declared_filters[filter_name]
                       continue

                   if field is not None:
                       filters[filter_name] = cls.filter_for_field(field, field_name, lookup_expr)

           # filter out declared filters
           undefined = [f for f in undefined if f not in cls.declared_filters]
           if undefined:
               raise TypeError(
                   "'Meta.fields' contains fields that are not defined on this FilterSet: "
                   "%s" % ', '.join(undefined)
              )

           # Add in declared filters. This is necessary since we don't enforce adding
           # declared filters to the 'Meta.fields' option
           filters.update(cls.declared_filters)
           return filters
       
       @classmethod
       def filter_for_field(cls, field, field_name, lookup_expr='exact'):
           field, lookup_type = resolve_field(field, lookup_expr)

           default = {
               'field_name': field_name,
               'lookup_expr': lookup_expr,
          }

           filter_class, params = cls.filter_for_lookup(field, lookup_type)
           default.update(params)

           assert filter_class is not None, (
               "%s resolved field '%s' with '%s' lookup to an unrecognized field "
               "type %s. Try adding an override to 'Meta.filter_overrides'. See: "
               "https://django-filter.readthedocs.io/en/master/ref/filterset.html"
               "#customise-filter-generation-with-filter-overrides"
          ) % (cls.__name__, field_name, lookup_expr, field.__class__.__name__)

           return filter_class(**default)

       @classmethod
       def filter_for_lookup(cls, field, lookup_type):
           """
          过滤
          :param field:
          :param lookup_type:
          :return:
          """
           DEFAULTS = dict(cls.FILTER_DEFAULTS)
           if hasattr(cls, '_meta'):
               DEFAULTS.update(cls._meta.filter_overrides)

           data = try_dbfield(DEFAULTS.get, field.__class__) or {}
           filter_class = data.get('filter_class')
           params = data.get('extra', lambda field: {})(field)

           # if there is no filter class, exit early
           if not filter_class:
               return None, {}

           # perform lookup specific checks
           if lookup_type == 'exact' and getattr(field, 'choices', None):
               return ChoiceFilter, {'choices': field.choices}

           if lookup_type == 'isnull':
               data = try_dbfield(DEFAULTS.get, models.BooleanField)

               filter_class = data.get('filter_class')
               params = data.get('extra', lambda field: {})(field)
               return filter_class, params

           if lookup_type == 'in':
               class ConcreteInFilter(BaseInFilter, filter_class):
                   pass
               ConcreteInFilter.__name__ = cls._csv_filter_class_name(
                   filter_class, lookup_type
              )

               return ConcreteInFilter, params

           if lookup_type == 'range':
               class ConcreteRangeFilter(BaseRangeFilter, filter_class):
                   pass
               ConcreteRangeFilter.__name__ = cls._csv_filter_class_name(
                   filter_class, lookup_type
              )

               return ConcreteRangeFilter, params

           return filter_class, params

    具体的数据库字段类型对应的Filter如下,上面也就是根据这些来找到对应的Filter,发现没,是BaseFilterSet类的FILTER_DEFAULTS变量

    FILTER_FOR_DBFIELD_DEFAULTS = {
       models.AutoField:                   {'filter_class': NumberFilter},
       models.CharField:                   {'filter_class': CharFilter},
       models.TextField:                   {'filter_class': CharFilter},
       models.BooleanField:               {'filter_class': BooleanFilter},
       models.DateField:                   {'filter_class': DateFilter},
       models.DateTimeField:               {'filter_class': DateTimeFilter},
       models.TimeField:                   {'filter_class': TimeFilter},
       models.DurationField:               {'filter_class': DurationFilter},
       models.DecimalField:               {'filter_class': NumberFilter},
       models.SmallIntegerField:           {'filter_class': NumberFilter},
       models.IntegerField:               {'filter_class': NumberFilter},
       models.PositiveIntegerField:       {'filter_class': NumberFilter},
       models.PositiveSmallIntegerField:   {'filter_class': NumberFilter},
       models.FloatField:                 {'filter_class': NumberFilter},
       models.NullBooleanField:           {'filter_class': BooleanFilter},
       models.SlugField:                   {'filter_class': CharFilter},
       models.EmailField:                 {'filter_class': CharFilter},
       models.FilePathField:               {'filter_class': CharFilter},
       models.URLField:                   {'filter_class': CharFilter},
       models.GenericIPAddressField:       {'filter_class': CharFilter},
       models.CommaSeparatedIntegerField: {'filter_class': CharFilter},
       models.UUIDField:                   {'filter_class': UUIDFilter},

       # Forward relationships
       models.OneToOneField: {
           'filter_class': ModelChoiceFilter,
           'extra': lambda f: {
               'queryset': remote_queryset(f),
               'to_field_name': f.remote_field.field_name,
               'null_label': settings.NULL_CHOICE_LABEL if f.null else None,
          }
      },
       models.ForeignKey: {
           'filter_class': ModelChoiceFilter,
           'extra': lambda f: {
               'queryset': remote_queryset(f),
               'to_field_name': f.remote_field.field_name,
               'null_label': settings.NULL_CHOICE_LABEL if f.null else None,
          }
      },
       models.ManyToManyField: {
           'filter_class': ModelMultipleChoiceFilter,
           'extra': lambda f: {
               'queryset': remote_queryset(f),
          }
      },

       # Reverse relationships
       OneToOneRel: {
           'filter_class': ModelChoiceFilter,
           'extra': lambda f: {
               'queryset': remote_queryset(f),
               'null_label': settings.NULL_CHOICE_LABEL if f.null else None,
          }
      },
       ManyToOneRel: {
           'filter_class': ModelMultipleChoiceFilter,
           'extra': lambda f: {
               'queryset': remote_queryset(f),
          }
      },
       ManyToManyRel: {
           'filter_class': ModelMultipleChoiceFilter,
           'extra': lambda f: {
               'queryset': remote_queryset(f),
          }
      },
    }

    ok,到这里就简单的介绍完毕了。

  • 相关阅读:
    WPF之窗体说明
    WPF之基本概念
    WPF学习之button
    写一本”错误百出”的C语言学习教程(一)
    JSP的工作原理-还是没理解--多看点再写。
    Java将中文转换成unicode字符。
    postgres 导出数据到csv 文件
    python小试身手-文件重命名,文件复制和压缩(.gz)
    python 环境安装 mark下。
    JRE,JVM,JDK的区别---粘自百度知道、
  • 原文地址:https://www.cnblogs.com/George1994/p/9739042.html
Copyright © 2011-2022 走看看