@@ -69,6 +69,8 @@ class FilterSet(six.with_metaclass(FilterSetMetaclass, filterset.FilterSet)):
6969 }
7070
7171 def __init__ (self , * args , ** kwargs ):
72+ self ._related_filterset_cache = kwargs .pop ('cache' , {})
73+
7274 super (FilterSet , self ).__init__ (* args , ** kwargs )
7375
7476 for name , filter_ in six .iteritems (self .filters ):
@@ -86,14 +88,14 @@ def get_filters(self):
8688 """
8789 requested_filters = OrderedDict ()
8890
89- # filter out any filters not included in the request data
91+ # Add plain lookup filters if match. ie, `username__icontains`
9092 for filter_key , filter_value in six .iteritems (self .filters ):
9193 if filter_key in self .data :
9294 requested_filters [filter_key ] = filter_value
9395
94- # build a map of potential {rel: [ filter]} pairs
96+ # build a map of potential {rel: { filter: value}} data
9597 related_data = OrderedDict ()
96- for filter_key in self .data :
98+ for filter_key , value in six . iteritems ( self .data ) :
9799 if filter_key not in self .filters :
98100
99101 # skip non lookup/related keys
@@ -102,28 +104,66 @@ def get_filters(self):
102104
103105 rel_name , filter_key = filter_key .split (LOOKUP_SEP , 1 )
104106
105- related_data .setdefault (rel_name , [] )
106- related_data [rel_name ]. append ( filter_key )
107+ related_data .setdefault (rel_name , OrderedDict () )
108+ related_data [rel_name ][ filter_key ] = value
107109
108- # walk the related lookup data. If the rel is a RelatedFilter,
109- # then instantiate its filterset and append its filters
110+ # walk the related lookup data. If the filter is a RelatedFilter,
111+ # then instantiate its filterset and append its filters.
110112 for rel_name , rel_data in related_data .items ():
111113 related_filter = self .filters .get (rel_name , None )
112114
113115 # skip non-`RelatedFilter`s
114116 if not isinstance (related_filter , filters .RelatedFilter ):
115117 continue
116118
117- filterset = related_filter .filterset (data = rel_data )
118- rel_filters = filterset .get_filters ()
119+ # get known filter names
120+ filterset_class = related_filter .filterset
121+ filter_names = [filterset_class .get_filter_name (param ) for param in rel_data .keys ()]
122+
123+ # attempt to retrieve related filterset subset from the cache
124+ key = self .cache_key (filterset_class , filter_names )
125+ subset_class = self .cache_get (key )
126+
127+ # otherwise build and insert it into the cache
128+ if subset_class is None :
129+ subset_class = related_filter .get_filterset_subset (filter_names )
130+ self .cache_set (key , subset_class )
119131
132+ # initialize and copy filters
133+ filterset = subset_class (data = rel_data )
134+ rel_filters = filterset .get_filters ()
120135 for filter_key , filter_value in six .iteritems (rel_filters ):
136+ # modify filter name to account for relationship
121137 rel_filter_key = LOOKUP_SEP .join ([rel_name , filter_key ])
122138 filter_value .name = LOOKUP_SEP .join ([related_filter .name , filter_value .name ])
123139 requested_filters [rel_filter_key ] = filter_value
124140
125141 return requested_filters
126142
143+ @classmethod
144+ def get_filter_name (cls , param ):
145+ """
146+ Get the filter name for the request data parameter.
147+ """
148+ # Attempt to match against filters with lookups first. (username__endswith)
149+ if param in cls .base_filters :
150+ return param
151+
152+ # Fallback to matching against relationships. (author__username__endswith)
153+ param = param .split (LOOKUP_SEP , 1 )[0 ]
154+ f = cls .base_filters .get (param , None )
155+ if isinstance (f , filters .RelatedFilter ):
156+ return param
157+
158+ def cache_key (self , filterset , filter_names ):
159+ return '%sSubset-%s' % (filterset .__name__ , '-' .join (sorted (filter_names )), )
160+
161+ def cache_get (self , key ):
162+ return self ._related_filterset_cache .get (key )
163+
164+ def cache_set (self , key , value ):
165+ self ._related_filterset_cache [key ] = value
166+
127167 @property
128168 def qs (self ):
129169 available_filters = self .filters
0 commit comments