22from __future__ import unicode_literals
33
44from copy import copy
5+ from collections import OrderedDict
56
67try :
78 from django .db .models .constants import LOOKUP_SEP
@@ -45,13 +46,12 @@ def __init__(self, *args, **kwargs):
4546
4647 for name , filter_ in six .iteritems (self .filters ):
4748 if isinstance (filter_ , filters .RelatedFilter ):
48- # Populate our FilterSet fields with the fields we've stored
49- # in RelatedFilter.
5049 filter_ .setup_filterset ()
51- self . populate_from_filterset ( filter_ . filterset , filter_ , name )
50+
5251 # Add an 'isnull' filter to allow checking if the relation is empty.
5352 isnull_filter = filters .BooleanFilter (name = ("%s%sisnull" % (filter_ .name , LOOKUP_SEP )))
5453 self .filters ['%s%s%s' % (filter_ .name , LOOKUP_SEP , 'isnull' )] = isnull_filter
54+
5555 elif isinstance (filter_ , filters .AllLookupsFilter ):
5656 # Populate our FilterSet fields with all the possible
5757 # filters for the AllLookupsFilter field.
@@ -66,6 +66,62 @@ def __init__(self, *args, **kwargs):
6666 f = self .fix_filter_field (f )
6767 self .filters ["%s%s%s" % (name , LOOKUP_SEP , lookup_type )] = f
6868
69+ def get_filters (self ):
70+ """
71+ Build a set of filters based on the requested data. The resulting set
72+ will walk `RelatedFilter`s to recursively build the set of filters.
73+ """
74+ requested_filters = OrderedDict ()
75+
76+ # filter out any filters not included in the request data
77+ for filter_key , filter_value in six .iteritems (self .filters ):
78+ if filter_key in self .data :
79+ requested_filters [filter_key ] = filter_value
80+
81+ # build a map of potential {rel: [filter]} pairs
82+ related_data = OrderedDict ()
83+ for filter_key in self .data :
84+ if filter_key not in self .filters :
85+
86+ # skip non lookup/related keys
87+ if LOOKUP_SEP not in filter_key :
88+ continue
89+
90+ rel_name , filter_key = filter_key .split (LOOKUP_SEP , 1 )
91+
92+ related_data .setdefault (rel_name , [])
93+ related_data [rel_name ].append (filter_key )
94+
95+ # walk the related lookup data. If the rel is a RelatedFilter,
96+ # then instantiate its filterset and append its filters
97+ for rel_name , rel_data in related_data .items ():
98+ related_filter = self .filters .get (rel_name , None )
99+
100+ # skip non-`RelatedFilter`s
101+ if not isinstance (related_filter , filters .RelatedFilter ):
102+ continue
103+
104+ filterset = related_filter .filterset (data = rel_data )
105+ rel_filters = filterset .get_filters ()
106+
107+ for filter_key , filter_value in six .iteritems (rel_filters ):
108+ rel_filter_key = LOOKUP_SEP .join ([rel_name , filter_key ])
109+ filter_value .name = LOOKUP_SEP .join ([related_filter .name , filter_value .name ])
110+ requested_filters [rel_filter_key ] = filter_value
111+
112+ return requested_filters
113+
114+ @property
115+ def qs (self ):
116+ available_filters = self .filters
117+ requested_filters = self .get_filters ()
118+
119+ self .filters = requested_filters
120+ qs = super (FilterSet , self ).qs
121+ self .filters = available_filters
122+
123+ return qs
124+
69125 def fix_filter_field (self , f ):
70126 """
71127 Fix the filter field based on the lookup type.
@@ -76,38 +132,3 @@ def fix_filter_field(self, f):
76132 if lookup_type == 'in' and type (f ) in [filters .NumberFilter ]:
77133 return filters .InSetNumberFilter (name = ("%s%sin" % (f .name , LOOKUP_SEP )))
78134 return f
79-
80- def populate_from_filterset (self , filterset , filter_ , name ):
81- """
82- Populate `filters` with filters provided on `filterset`.
83- """
84- def _should_skip ():
85- for name , filter_ in six .iteritems (self .filters ):
86- if filter_value == filter_ :
87- return True
88- # Avoid infinite recursion on recursive relations. If the queryset and
89- # class are the same, then we assume that we've already added this
90- # filter previously along the lookup chain, e.g.
91- # a__b__a <-- the last 'a' there.
92- if (isinstance (filter_ , filters .RelatedFilter ) and
93- isinstance (filter_value , filters .RelatedFilter )):
94- if filter_value .extra .get ('queryset' , None ) == filter_ .extra .get ('queryset' ):
95- return True
96- return False
97-
98- for (filter_key , filter_value ) in filterset .base_filters .items ():
99- if _should_skip ():
100- continue
101-
102- filter_value = copy (filter_value )
103-
104- # Guess on the field to join on, if applicable
105- if not getattr (filter_value , 'parent_relation' , None ):
106- filter_value .parent_relation = filterset ._meta .model .__name__ .lower ()
107-
108- # We use filter_.name -- which is the internal name, to do the actual query
109- filter_name = filter_value .name
110- filter_value .name = '%s%s%s' % (filter_ .name , LOOKUP_SEP , filter_name )
111- # and then we use the /given/ name keyword as the actual querystring lookup, and
112- # the filter's name in the related class (filter_key).
113- self .filters ['%s%s%s' % (name , LOOKUP_SEP , filter_key )] = filter_value
0 commit comments