Django Filter源码解析
最近在看Django-FIlter项目的源码,学习一下别人的开发思想;
整体介绍
首先,我从其中一个测试用例作为入口,开始了debug之路,一点一点的断点,分析它的执行顺序,如图:
ok,下面从代码的层面进行分析:
- url
url(r‘^books/$‘, FilterView.as_view(model=Book)),
- 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‘
- 基础过滤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)
- 接下来就分成三件事: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
- 接下来就是重头戏,开始过滤了!下面会被调用是因为调用了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.filters
(self.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-10-29 03:32:29