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

原文地址:https://www.cnblogs.com/George1994/p/9739042.html

时间: 2024-08-29 08:32:01

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

Django settings源码解析

Django settings源码 Django中有两个配置文件 局部配置:配置文件settings.py,即项目同名文件夹下的settings.py文件 全局配置:django内部全局的配置文件settings.py,需要导入才能看到 from django.conf import settings # 是一个对象,单例模式 from django.conf import global_settings # 真正的默认配置文件 特点: 先加载全局配置,再加载局部配置,以局部优先 源码解析 点进

Django APIView源码解析

APIView用法: 在Django之 CBV和FBV中,我们是分析的from django.views import View下的执行流程,以下是代码 from django.views import View class IndexView(View): def get(self,request, *args, **kwargs): return HttpResponse("ok") def dispatch(self, request, *args, **kwargs): ret

Django 【第二十五篇】Django admin源码解析

一.admin的源码流程 首先可以确定的是:路由关系一定对应一个视图函数 a.当点击运行的时候,会先找到每一个app中的admin.py文件,并执行 b.执行urls.py admin.site是什么? admin.site,urls    返回的是一个元组,里面的第一个元素是一个列表 django-admin的源码流程 我们自己生成的动态的访问url ====================================初级版========================= from dj

Django官方源码解析

Django官方文档解析 标签(空格分隔): Django 创建一个应用 python manage.py startapp polls 上述操作会创建出一个polls目录,其目录结构大致如下: polls/ __init__.py admin.py apps.py migrations/ __init__.py models.py tests.py views.py 创建一个视图 在polls目录中打开views.py,并输入如下代码: from django.http import Http

django源码解析之BigIntegerField (一)

要分析django的源码,来更深入的学习django,是一个不错的方法,可惜需要大量的时间. 所以,能分析多少就是多少吧. 本次源码分析以1.4.16为基础. 用sublime 打开下载的源码,使用 Find in Folder,查找BigIntegerField 在其中可以看到这样的代码: 1 1005 def formfield(self, **kwargs): 2 1006: defaults = {'min_value': -BigIntegerField.MAX_BIGINT - 1,

Volley 源码解析(转)

项目:Volley,分析者:grumoon,校对者:Trinea 本文为 Android 开源项目源码解析 中 Volley 部分项目地址:Volley,分析的版本:35ce778,Demo 地址:Volley Demo分析者:grumoon,校对者:huxian99.Trinea,校对状态:完成 1. 功能介绍 1.1. Volley Volley 是 Google 推出的 Android 异步网络请求框架和图片加载框架.在 Google I/O 2013 大会上发布. 名字由来:a burs

【转】Java HashMap 源码解析(好文章)

- .fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wrapper iframe, .fluid-width-video-wrapper object, .fluid-width-video-wrapper embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } [

Volley 源码解析<转>

Volley 源码解析 1. 功能介绍 1.1. Volley Volley 是 Google 推出的 Android 异步网络请求框架和图片加载框架.在 Google I/O 2013 大会上发布. 名字由来:a burst or emission of many things or a large amount at once发布演讲时候的配图 从名字由来和配图中无数急促的火箭可以看出 Volley 的特点:特别适合数据量小,通信频繁的网络操作.(个人认为 Android 应用中绝大多数的网

ArrayList源码解析

ArrayList源码解析 ArrayList简介: ArrayList 是list接口的一个常用实现类.它的对象可以认为是一维数组的"类版本".我们很快就可以看到,ArrayList 对象可以看做是一维数组的改良版本.类似于数组,ArrayList 对象支持元素的随机访问:也就是说,只要给出元素的索引,任何元素的访问时间都是常数.但是同数组不同的是,ArrayList 对象的大小在程序执行的过程中可以自动进行调整,并且ArrayList对象具有在任何索引位置插入和删除对象的方法,而数