在日常使用 django-filter 时,我们常会写出这样的代码:
python
class BacklogFilter(filters.FilterSet):
search_name = filters.CharFilter(method='search_name_filter')
class Meta:
model = SgoDemands
fields = ['id', 'progress']
这时,很多人会产生疑问:
-
Meta.fields里没写search_name,这个过滤器还能生效吗? -
手动定义的过滤器和
Meta.fields自动生成的过滤器,执行顺序 是怎样的? -
如果两者重名,又会怎样?
-
这些是怎么在源码里实现的?
今天这篇文章,我们通过源码逐行分析,彻底搞清楚这些问题。
适用版本:django-filter==2.4.0
一、FilterSet 是怎么构建的?
当我们定义一个类继承 FilterSet 时,Django 会自动触发它的 元类构造逻辑:
python
FilterSetMetaclass.__new__()
创建过程主要分为三步:
python
FilterSetMetaclass.__new__()
├─ get_declared_filters() ← 收集类属性里手动定义的过滤器
├─ get_filters() ← 根据 Meta.fields 生成自动过滤器并合并
└─ 设置 base_filters
最终得到的 base_filters 就是 FilterSet 里所有可用的过滤字段。
二、类属性过滤器的收集:get_declared_filters()
在源码中,get_declared_filters() 的逻辑如下(简化版):
python
@classmethod
def get_declared_filters(cls, bases, attrs):
filters = [
(filter_name, attrs.pop(filter_name))
for filter_name, obj in list(attrs.items())
if isinstance(obj, Filter)
]
for filter_name, f in filters:
if getattr(f, 'field_name', None) is None:
f.field_name = filter_name
filters.sort(key=lambda x: x[1].creation_counter)
base_filters = ...
return OrderedDict(base_filters + filters)
可以看到,它做了三件事:
-
扫描类中的属性;
-
只要是
Filter实例(CharFilter、NumberFilter等)就收集; -
完全不依赖
Meta.fields。
📘 结论 :
只要你在类属性中定义了过滤器,无论是否出现在 Meta.fields,它都会生效。
三、最终过滤器的生成:get_filters()
接下来在 get_filters() 里,Django 会根据 Meta.fields 自动生成过滤器。
源码关键部分如下:
python
@classmethod
def get_filters(cls):
if not cls._meta.model:
return cls.declared_filters.copy()
filters = OrderedDict()
fields = cls.get_fields()
for field_name, lookups in fields.items():
if filter_name in cls.declared_filters:
# 已在类中定义 → 优先使用
filters[filter_name] = cls.declared_filters[filter_name]
continue
filters[filter_name] = cls.filter_for_field(field, field_name, lookup_expr)
# 最后把没出现在 Meta.fields 里的 declared_filters 补上
filters.update(cls.declared_filters)
return filters
这段逻辑非常关键,说明了两点:
-
如果 Meta.fields 与类属性同名 → 优先用类属性的定义;
-
如果类属性没出现在 Meta.fields 中 → 也会在最后被补进去。
✅ 因此,类属性定义的过滤器永远生效,且优先级最高。
四、字段顺序是怎样的?
get_filters() 的执行顺序如下:
-
遍历
Meta.fields,为每个字段生成过滤器(自动生成的在前); -
最后执行
filters.update(cls.declared_filters),把类属性定义的过滤器追加进去。
于是得到以下规律👇
| 情况 | 最终顺序 | 说明 |
|---|---|---|
| 类属性与 Meta.fields 不重名 | 自动生成的在前,类属性在后 | |
| 类属性与 Meta.fields 重名 | 类属性定义的覆盖自动生成的 |
示例 1:不重名
python
class MyFilter(FilterSet):
search_name = CharFilter(...)
class Meta:
model = User
fields = ['id', 'email']
# 最终 filters 顺序:
# ['id', 'email', 'search_name']
示例 2:重名
python
class MyFilter(FilterSet):
email = CharFilter(...) # 与 model 字段同名
class Meta:
model = User
fields = ['id', 'email']
# 最终 filters 顺序:
# ['id', 'email'] ← email 被自定义覆盖
五、源码执行图
python
FilterSetMetaclass.__new__()
│
├─ get_declared_filters() → 收集类属性中的过滤器
│
├─ get_filters()
│ ├─ 根据 Meta.fields 生成自动过滤器
│ ├─ 同名字段使用类属性覆盖
│ └─ 追加未出现在 Meta.fields 的 declared_filters
│
└─ base_filters = 最终可用过滤器
六、最终结论总结表
| 问题 | 答案 |
|---|---|
| 类属性定义但不在 Meta.fields 里的过滤器是否生效? | ✅ 生效 |
| 优先级 | 类属性定义的 > 自动生成的 |
| 顺序 | 自动生成在前,类属性在后 |
| 同名时 | 类属性覆盖自动生成 |
| 实现逻辑 | get_filters() 最后调用 filters.update(cls.declared_filters) |
| 收集类属性方法 | FilterSetMetaclass.get_declared_filters() |
七、写法建议
在实际项目中建议:
-
业务过滤逻辑复杂时 ,尽量用类属性 +
method=; -
简单字段筛选 ,用
Meta.fields自动生成; -
若两者混用,要注意顺序和覆盖关系。
这样可以既保持代码简洁,又能完全控制过滤行为。
八、总结
django-filter的设计理念是「显式优先于隐式」。手动定义的过滤器始终优先且独立于
Meta.fields,不会被覆盖,也无需重复声明。
一句话记住:
✅ "写在类上的一定生效,
Meta.fields只是辅助。"
📘 参考版本
-
Django Filter 官方源码:
django-filter==2.4.0 -
核心类位置:
pythondjango_filters/filterset.py → FilterSetMetaclass、get_declared_filters()、get_filters()