@@ -20,58 +20,99 @@ def related(filterset, filter_name):
2020
2121class FilterSetMetaclass (filterset .FilterSetMetaclass ):
2222 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 )
2424
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 )
3126
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 )] )
3530
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.
3736 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 )
3944
4045 return new_class
4146
4247 @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.
4892 """
49- # get reference to opts/declared filters
50- orig_meta , orig_declared = new_class ._meta , new_class .declared_filters
93+ expanded = OrderedDict ()
5194
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
5397 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 = {}
5899
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 )
63106
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
71110
72111 # restore reference to opts/declared filters
73112 new_class ._meta , new_class .declared_filters = orig_meta , orig_declared
74113
114+ return expanded
115+
75116
76117class SubsetDisabledMixin :
77118 """
@@ -95,6 +136,7 @@ def __init__(self, data=None, queryset=None, *, relationship=None, **kwargs):
95136
96137 @classmethod
97138 def get_fields (cls ):
139+ # Extend the 'Meta.fields' dict syntax to allow '__all__' field lookups.
98140 fields = super (FilterSet , cls ).get_fields ()
99141
100142 for name , lookups in fields .items ():
0 commit comments