@@ -153,6 +153,8 @@ class FieldFilter(object):
153153 fields = []
154154 # The list of allowed qualifiers
155155 allowed_qualifiers = []
156+ # The mapping of allowed chain qualifiers to the relevant Field
157+ allowed_chain_qualifiers = {}
156158
157159 def __init__ (self , field ):
158160 self .field = field
@@ -192,12 +194,65 @@ def check_qualifier(self, qualifier):
192194 .format (qualifier , self .__class__ .__name__ , self .field_description ()))
193195
194196
197+ # This returns a (cached) filterclass for a field class.
198+ def get_field_filter (self , field_class , reset = False ):
199+ f = not reset and getattr (self , '_field_filters' , None )
200+
201+ if not f :
202+ f = {}
203+ for field_filter_cls in FieldFilter .__subclasses__ ():
204+ for field_cls in field_filter_cls .fields :
205+ if f .get (field_cls ):
206+ raise ValueError ('Field-Filter mapping conflict: {} vs {}' .format (field_filter_cls .name , field_cls .name ))
207+ else :
208+ f [field_cls ] = field_filter_cls
209+
210+ self ._field_filters = f
211+
212+ return f .get (field_class )
213+
214+
215+
216+ def get_q (self , qualifiers , value , invert , partial = '' ):
217+ i = 0
218+ field_filter = self
219+
220+ # First we try to handle chain qualifiers
221+ while (
222+ # If its not the last qualifier it has to be a chain qualifier
223+ i < len (qualifiers ) - 1 or
224+ # For the last one we check if it is in chain qualifiers
225+ (i < len (qualifiers ) and qualifiers [i ] in field_filter .allowed_chain_qualifiers )
226+ ):
227+ chain_qualifier = qualifiers [i ]
228+ i += 1
229+
230+ field_cls = field_filter .allowed_chain_qualifiers [chain_qualifier ]
231+ if field_cls is None :
232+ raise BinderRequestError (
233+ 'Qualifier {} not supported for type {} ({}).'
234+ .format (chain_qualifier , field_filter .__class__ .__name__ , field_filter .field_description ())
235+ )
195236
196- def get_q ( self , qualifier , value , invert , partial = '' ):
197- self .check_qualifier ( qualifier )
198- qualifier , cleaned_value = self .clean_qualifier ( qualifier , value )
237+ field = field_cls ()
238+ field . model = self .field . model
239+ field . name = self .field . name + ':' + chain_qualifier
199240
200- suffix = '__' + qualifier if qualifier else ''
241+ field_filter_cls = self .get_field_filter (field_cls )
242+ field_filter = field_filter_cls (field )
243+
244+ try :
245+ qualifier = qualifiers [i ]
246+ except IndexError :
247+ qualifier = None
248+
249+ field_filter .check_qualifier (qualifier )
250+ qualifier , cleaned_value = field_filter .clean_qualifier (qualifier , value )
251+
252+ if 0 <= i < len (qualifiers ):
253+ qualifiers [i ] = qualifier
254+
255+ suffix = '' .join ('__' + qualifier for qualifier in qualifiers )
201256 if invert :
202257 return ~ Q (** {partial + self .field .name + suffix : cleaned_value })
203258 else :
@@ -254,6 +309,7 @@ class DateTimeFieldFilter(FieldFilter):
254309 fields = [models .DateTimeField ]
255310 # Maybe allow __startswith? And __year etc?
256311 allowed_qualifiers = [None , 'in' , 'gt' , 'gte' , 'lt' , 'lte' , 'range' , 'isnull' ]
312+ allowed_chain_qualifiers = {'date' : models .DateField }
257313
258314 def clean_value (self , qualifier , v ):
259315 if re .match ('^[0-9]{4}-[0-9]{2}-[0-9]{2}[T ][0-9]{2}:[0-9]{2}:[0-9]{2}([.][0-9]+)?([A-Za-z]+|[+-][0-9]{1,4})$' , v ):
@@ -275,6 +331,7 @@ def clean_qualifier(self, qualifier, value):
275331 else :
276332 value_type = type (cleaned_value )
277333
334+ # [TODO] Support for chained qualifiers is added, still needed for backwards compat
278335 if issubclass (value_type , date ) and not issubclass (value_type , datetime ):
279336 if qualifier is None :
280337 qualifier = 'date'
@@ -337,6 +394,7 @@ def clean_value(self, qualifier, v):
337394class TextFieldFilter (FieldFilter ):
338395 fields = [models .CharField , models .TextField ]
339396 allowed_qualifiers = [None , 'in' , 'iexact' , 'contains' , 'icontains' , 'startswith' , 'istartswith' , 'endswith' , 'iendswith' , 'exact' , 'isnull' ]
397+ allowed_chain_qualifiers = {'unaccent' : models .TextField }
340398
341399 # Always valid(?)
342400 def clean_value (self , qualifier , v ):
@@ -357,22 +415,6 @@ class ArrayFieldFilter(FieldFilter):
357415 fields = [ArrayField ]
358416 allowed_qualifiers = [None , 'contains' , 'contained_by' , 'overlap' , 'isnull' ]
359417
360- # Some copy/pasta involved....
361- def get_field_filter (self , field_class , reset = False ):
362- f = not reset and getattr (self , '_field_filter' , None )
363-
364- if not f :
365- f = None
366- for field_filter_cls in FieldFilter .__subclasses__ ():
367- for field_cls in field_filter_cls .fields :
368- if field_cls == field_class :
369- f = field_filter_cls
370- break
371- self ._field_filter = f
372-
373- return f
374-
375-
376418 def clean_value (self , qualifier , v ):
377419 Filter = self .get_field_filter (self .field .base_field .__class__ )
378420 filter = Filter (self .field .base_field )
0 commit comments