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,到这里就简单的介绍完毕了。

Django-Filter源码解析一的更多相关文章

  1. django -admin 源码解析

    admin源码解析 单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.当你希望在整个系统中,某个类只能出现一个实例时,单 ...

  2. Django settings源码解析

    Django settings源码 Django中有两个配置文件 局部配置:配置文件settings.py,即项目同名文件夹下的settings.py文件 全局配置:django内部全局的配置文件se ...

  3. Django APIView源码解析

    APIView使用:luffy项目中关于APIView的使用 在Django之 CBV和FBV中,我们是分析的from django.views import View下的执行流程,以下是代码 fro ...

  4. ReactiveCocoa源码解析(五) SignalProtocol的observe()、Map、Filter延展实现

    上篇博客我们对Signal的基本实现以及Signal的面向协议扩展进行了介绍, 详细内容请移步于<Signal中的静态属性静态方法以及面向协议扩展>.并且聊了Signal的所有的g功能扩展 ...

  5. ReactiveSwift源码解析(五) SignalProtocol的observe()、Map、Filter延展实现

    上篇博客我们对Signal的基本实现以及Signal的面向协议扩展进行了介绍, 详细内容请移步于<Signal中的静态属性静态方法以及面向协议扩展>.并且聊了Signal的所有的g功能扩展 ...

  6. 源码解析Django CBV的本质

    Django CBV模式的源码解析 通常来说,http请求的本质就是基于Socket Django的视图函数,可以基于FBV模式,也可以基于CBV模式. 基于FBV的模式就是在Django的路由映射表 ...

  7. Django生命周期 URL ----> CBV 源码解析-------------- 及rest_framework APIView 源码流程解析

    一.一个请求来到Django 的生命周期   FBV 不讨论 CBV: 请求被代理转发到uwsgi: 开始Django的流程: 首先经过中间件process_request (session等) 然后 ...

  8. django之admin源码解析

    解析admin的源码 第一步:项目启动,加载settings文件中的 INSTALLED_APPS 里边有几个app就加载几个,按照注册顺序来执行. 第二步:其中加载的是admin.py,加载每一个a ...

  9. Django框架 之 admin管理工具(源码解析)

    浏览目录 单例模式 admin执行流程 admin源码解析 单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.当你希望在 ...

随机推荐

  1. ThinkPHP框架学习(一)

    这几天呢,断断续续地在看孙叔华老师的ThinkPHP教程,期间还做了一些其他事情,出去办了点事,总结总结下一学期规划等等,不知不觉间又过去了大半个星期.现在呢,看完了一天的教程,在这里,还是希望稍微总 ...

  2. header()跳转

    if ($toNews == 1) { header('Location:/ucenter/pageMailBox/2'); exit; } PHP跳转页面,用 header() 函数 定义和用法 h ...

  3. django+mysql安装和设置

    之前我们已经用sqlite建立了第一个web app.今天来学习如何在django中使用MySQL. 首先需要安装MySQL,到官网下载安装包:https://dev.mysql.com/downlo ...

  4. 洛谷 P2089 烤鸡

    看了前面大佬的代码,发现这道题的解题思路都大同小异. 首先肯定要定义一个变量累加方案数量,因为方案数量要最先输出,所以所有方案要先储存下来.个人不喜欢太多数组,就只定义一个字符串. 然后我们发现只有1 ...

  5. Madgwick IMU Filter

    论文链接:http://202.114.96.204/cache/13/03/x-io.co.uk/35c82431852f2aa7d0feede9dc138626/madgwick_internal ...

  6. C/C++杂记:深入理解数据成员指针、函数成员指针

    1. 数据成员指针 对于普通指针变量来说,其值是它所指向的地址,0表示空指针. 而对于数据成员指针变量来说,其值是数据成员所在地址相对于对象起始地址的偏移值,空指针用-1表示.例: 代码示例: str ...

  7. 查询orcale运行的SQL语句记录

    select c.* from V$SQL c where c.MODULE='ukhis.exe' order by last_active_time desc

  8. C#操作Mongo进行数据读写

    C#操作MongoHelp类 using System; using System.Collections.Generic; using System.Linq; using System.Web; ...

  9. node+webpack环境搭建 vue.js 2.0 基础学习笔记

    npm install -g vue //全局安装vue npm install -g webpack //全局安装webpack npm install -g vue-cli //全局安装vue-c ...

  10. 【论文阅读】Batch Feature Erasing for Person Re-identification and Beyond

    转载请注明出处:https://www.cnblogs.com/White-xzx/ 原文地址:https://arxiv.org/abs/1811.07130 如有不准确或错误的地方,欢迎交流~ [ ...