@@ -20,58 +20,99 @@ def related(filterset, filter_name):
20
20
21
21
class FilterSetMetaclass (filterset .FilterSetMetaclass ):
22
22
def __new__ (cls , name , bases , attrs ):
23
- new_class = super ( FilterSetMetaclass , cls ). __new__ ( cls , name , bases , attrs )
23
+ attrs [ 'auto_filters' ] = cls . get_auto_filters ( bases , attrs )
24
24
25
- new_class .auto_filters = [
26
- name for name , f in new_class .declared_filters .items ()
27
- if isinstance (f , filters .AutoFilter )]
28
- new_class .related_filters = [
29
- name for name , f in new_class .declared_filters .items ()
30
- if isinstance (f , filters .RelatedFilter )]
25
+ new_class = super ().__new__ (cls , name , bases , attrs )
31
26
32
- # see: :meth:`rest_framework_filters.filters.RelatedFilter.bind`
33
- for name in new_class .related_filters :
34
- new_class . declared_filters [ name ]. bind ( new_class )
27
+ new_class . related_filters = OrderedDict ([
28
+ ( name , f ) for name , f in new_class .declared_filters . items ()
29
+ if isinstance ( f , filters . BaseRelatedFilter )] )
35
30
36
- # If model is defined, process auto filters
31
+ # See: :meth:`rest_framework_filters.filters.RelatedFilter.bind`
32
+ for name , f in new_class .related_filters .items ():
33
+ f .bind (new_class )
34
+
35
+ # Only expand when model is defined. Model may be undefined for mixins.
37
36
if new_class ._meta .model is not None :
38
- cls .expand_auto_filters (new_class )
37
+ for name , f in new_class .auto_filters .items ():
38
+ expanded = cls .expand_auto_filter (new_class , name , f )
39
+ new_class .base_filters .update (expanded )
40
+
41
+ for name , f in new_class .related_filters .items ():
42
+ expanded = cls .expand_auto_filter (new_class , name , f )
43
+ new_class .base_filters .update (expanded )
39
44
40
45
return new_class
41
46
42
47
@classmethod
43
- def expand_auto_filters (cls , new_class ):
44
- """
45
- Resolve `AutoFilter`s into their per-lookup filters. `AutoFilter`s are
46
- a declarative alternative to the `Meta.fields` dictionary syntax, and
47
- use the same machinery internally.
48
+ def get_auto_filters (cls , bases , attrs ):
49
+ # Auto filters are specially handled since they aren't an actual filter
50
+ # subclass and aren't handled by the declared filter machinery. Note
51
+ # that this is a nearly identical copy of `get_declared_filters`.
52
+ auto_filters = [
53
+ (filter_name , attrs .pop (filter_name ))
54
+ for filter_name , obj in list (attrs .items ())
55
+ if isinstance (obj , filters .AutoFilter )
56
+ ]
57
+
58
+ # Default the `filter.field_name` to the attribute name on the filterset
59
+ for filter_name , f in auto_filters :
60
+ if getattr (f , 'field_name' , None ) is None :
61
+ f .field_name = filter_name
62
+
63
+ auto_filters .sort (key = lambda x : x [1 ].creation_counter )
64
+
65
+ # merge auto filters from base classes
66
+ for base in reversed (bases ):
67
+ if hasattr (base , 'auto_filters' ):
68
+ auto_filters = [
69
+ (name , f ) for name , f
70
+ in base .auto_filters .items ()
71
+ if name not in attrs
72
+ ] + auto_filters
73
+
74
+ return OrderedDict (auto_filters )
75
+
76
+ @classmethod
77
+ def expand_auto_filter (cls , new_class , filter_name , f ):
78
+ """Resolve an ``AutoFilter`` into its per-lookup filters.
79
+
80
+ This method name is slightly inaccurate since it handles both
81
+ :class:`rest_framework_filters.filters.AutoFilter` and
82
+ :class:`rest_framework_filters.filters.BaseRelatedFilter`, as well as
83
+ their subclasses, which all support per-lookup filter generation.
84
+
85
+ Args:
86
+ new_class: The ``FilterSet`` class to generate filters for.
87
+ filter_name: The attribute name of the filter on the ``FilterSet``.
88
+ f: The filter instance.
89
+
90
+ Returns:
91
+ A named map of generated filter objects.
48
92
"""
49
- # get reference to opts/declared filters
50
- orig_meta , orig_declared = new_class ._meta , new_class .declared_filters
93
+ expanded = OrderedDict ()
51
94
52
- # override opts/declared filters w/ copies
95
+ # get reference to opts/declared filters so originals aren't modified
96
+ orig_meta , orig_declared = new_class ._meta , new_class .declared_filters
53
97
new_class ._meta = copy .deepcopy (new_class ._meta )
54
- new_class .declared_filters = new_class .declared_filters .copy ()
55
-
56
- for name in new_class .auto_filters :
57
- f = new_class .declared_filters [name ]
98
+ new_class .declared_filters = {}
58
99
59
- # Remove auto filters from declared_filters so that they *are* overwritten
60
- # RelatedFilter is an exception, and should *not* be overwritten
61
- if not isinstance (f , filters .RelatedFilter ):
62
- del new_class .declared_filters [name ]
100
+ # Use meta.fields to generate auto filters
101
+ new_class ._meta .fields = {f .field_name : f .lookups or []}
102
+ for gen_name , gen_f in new_class .get_filters ().items ():
103
+ # get_filters() generates param names from the model field name, so
104
+ # replace the field name with the param name from the filerset
105
+ gen_name = gen_name .replace (f .field_name , filter_name , 1 )
63
106
64
- # Use meta.fields to generate auto filters
65
- new_class ._meta .fields = {f .field_name : f .lookups or []}
66
- for gen_name , gen_f in new_class .get_filters ().items ():
67
- # get_filters() generates param names from the model field name
68
- # Replace the field name with the parameter name from the filerset
69
- gen_name = gen_name .replace (f .field_name , name , 1 )
70
- new_class .base_filters [gen_name ] = gen_f
107
+ # do not overwrite declared filters
108
+ if gen_name not in orig_declared :
109
+ expanded [gen_name ] = gen_f
71
110
72
111
# restore reference to opts/declared filters
73
112
new_class ._meta , new_class .declared_filters = orig_meta , orig_declared
74
113
114
+ return expanded
115
+
75
116
76
117
class SubsetDisabledMixin :
77
118
"""
@@ -95,6 +136,7 @@ def __init__(self, data=None, queryset=None, *, relationship=None, **kwargs):
95
136
96
137
@classmethod
97
138
def get_fields (cls ):
139
+ # Extend the 'Meta.fields' dict syntax to allow '__all__' field lookups.
98
140
fields = super (FilterSet , cls ).get_fields ()
99
141
100
142
for name , lookups in fields .items ():
0 commit comments