@@ -154,6 +154,8 @@ class FieldFilter(object):
154154 fields = []
155155 # The list of allowed qualifiers
156156 allowed_qualifiers = []
157+ # The mapping of allowed chain qualifiers to the relevant Field
158+ allowed_chain_qualifiers = {}
157159
158160 def __init__ (self , field ):
159161 self .field = field
@@ -193,12 +195,65 @@ def check_qualifier(self, qualifier):
193195 .format (qualifier , self .__class__ .__name__ , self .field_description ()))
194196
195197
198+ # This returns a (cached) filterclass for a field class.
199+ def get_field_filter (self , field_class , reset = False ):
200+ f = not reset and getattr (self , '_field_filters' , None )
201+
202+ if not f :
203+ f = {}
204+ for field_filter_cls in FieldFilter .__subclasses__ ():
205+ for field_cls in field_filter_cls .fields :
206+ if f .get (field_cls ):
207+ raise ValueError ('Field-Filter mapping conflict: {} vs {}' .format (field_filter_cls .name , field_cls .name ))
208+ else :
209+ f [field_cls ] = field_filter_cls
210+
211+ self ._field_filters = f
212+
213+ return f .get (field_class )
214+
215+
216+
217+ def get_q (self , qualifiers , value , invert , partial = '' ):
218+ i = 0
219+ field_filter = self
220+
221+ # First we try to handle chain qualifiers
222+ while (
223+ # If its not the last qualifier it has to be a chain qualifier
224+ i < len (qualifiers ) - 1 or
225+ # For the last one we check if it is in chain qualifiers
226+ (i < len (qualifiers ) and qualifiers [i ] in field_filter .allowed_chain_qualifiers )
227+ ):
228+ chain_qualifier = qualifiers [i ]
229+ i += 1
230+
231+ field_cls = field_filter .allowed_chain_qualifiers [chain_qualifier ]
232+ if field_cls is None :
233+ raise BinderRequestError (
234+ 'Qualifier {} not supported for type {} ({}).'
235+ .format (chain_qualifier , field_filter .__class__ .__name__ , field_filter .field_description ())
236+ )
196237
197- def get_q ( self , qualifier , value , invert , partial = '' ):
198- self .check_qualifier ( qualifier )
199- qualifier , cleaned_value = self .clean_qualifier ( qualifier , value )
238+ field = field_cls ()
239+ field . model = self .field . model
240+ field . name = self .field . name + ':' + chain_qualifier
200241
201- suffix = '__' + qualifier if qualifier else ''
242+ field_filter_cls = self .get_field_filter (field_cls )
243+ field_filter = field_filter_cls (field )
244+
245+ try :
246+ qualifier = qualifiers [i ]
247+ except IndexError :
248+ qualifier = None
249+
250+ field_filter .check_qualifier (qualifier )
251+ qualifier , cleaned_value = field_filter .clean_qualifier (qualifier , value )
252+
253+ if 0 <= i < len (qualifiers ):
254+ qualifiers [i ] = qualifier
255+
256+ suffix = '' .join ('__' + qualifier for qualifier in qualifiers )
202257 if invert :
203258 return ~ Q (** {partial + self .field .name + suffix : cleaned_value })
204259 else :
@@ -265,6 +320,7 @@ class DateTimeFieldFilter(FieldFilter):
265320 fields = [models .DateTimeField ]
266321 # Maybe allow __startswith? And __year etc?
267322 allowed_qualifiers = [None , 'in' , 'gt' , 'gte' , 'lt' , 'lte' , 'range' , 'isnull' ]
323+ allowed_chain_qualifiers = {'date' : models .DateField }
268324
269325 def clean_value (self , qualifier , v ):
270326 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 ):
@@ -286,6 +342,7 @@ def clean_qualifier(self, qualifier, value):
286342 else :
287343 value_type = type (cleaned_value )
288344
345+ # [TODO] Support for chained qualifiers is added, still needed for backwards compat
289346 if issubclass (value_type , date ) and not issubclass (value_type , datetime ):
290347 if qualifier is None :
291348 qualifier = 'date'
@@ -348,6 +405,7 @@ def clean_value(self, qualifier, v):
348405class TextFieldFilter (FieldFilter ):
349406 fields = [models .CharField , models .TextField ]
350407 allowed_qualifiers = [None , 'in' , 'iexact' , 'contains' , 'icontains' , 'startswith' , 'istartswith' , 'endswith' , 'iendswith' , 'exact' , 'isnull' ]
408+ allowed_chain_qualifiers = {'unaccent' : models .TextField }
351409
352410 # Always valid(?)
353411 def clean_value (self , qualifier , v ):
@@ -368,22 +426,6 @@ class ArrayFieldFilter(FieldFilter):
368426 fields = [ArrayField ]
369427 allowed_qualifiers = [None , 'contains' , 'contained_by' , 'overlap' , 'isnull' ]
370428
371- # Some copy/pasta involved....
372- def get_field_filter (self , field_class , reset = False ):
373- f = not reset and getattr (self , '_field_filter' , None )
374-
375- if not f :
376- f = None
377- for field_filter_cls in FieldFilter .__subclasses__ ():
378- for field_cls in field_filter_cls .fields :
379- if field_cls == field_class :
380- f = field_filter_cls
381- break
382- self ._field_filter = f
383-
384- return f
385-
386-
387429 def clean_value (self , qualifier , v ):
388430 Filter = self .get_field_filter (self .field .base_field .__class__ )
389431 filter = Filter (self .field .base_field )
0 commit comments