@@ -69,6 +69,12 @@ class RQLFilterClass:
69
69
QUERIES_CACHE_SIZE = 20
70
70
"""Default number of cached queries."""
71
71
72
+ Q_CLS = Q
73
+ """Class for building nodes of the query, generated by django."""
74
+
75
+ FILTER_TYPES_CLS = FilterTypes
76
+ """Class for the mapping of model field types to filter types."""
77
+
72
78
def __init__ (self , queryset , instance = None ):
73
79
self .queryset = queryset
74
80
self ._is_distinct = self .DISTINCT
@@ -82,9 +88,13 @@ def __init__(self, queryset, instance=None):
82
88
self ._validate_init ()
83
89
self ._default_init (self ._get_init_filters ())
84
90
91
+ @classmethod
92
+ def _is_valid_model_cls (cls , model ):
93
+ return issubclass (model , Model )
94
+
85
95
def _validate_init (self ):
86
96
e = 'Django model must be set for Filter Class.'
87
- assert self .MODEL and issubclass (self .MODEL , Model ), e
97
+ assert self .MODEL and self . _is_valid_model_cls (self .MODEL ), e
88
98
89
99
e = 'Wrong filter settings type for Filter Class.'
90
100
assert (self .FILTERS is None ) or isinstance (self .FILTERS , iterable_types ), e
@@ -258,7 +268,7 @@ def build_q_for_filter(self, data):
258
268
259
269
base_item = self .get_filter_base_item (filter_name )
260
270
if not base_item :
261
- return Q ()
271
+ return self . Q_CLS ()
262
272
263
273
if base_item .get ('distinct' ):
264
274
self ._is_distinct = True
@@ -311,7 +321,7 @@ def build_q_for_filter(self, data):
311
321
return self ._build_django_q (filter_item , django_lookup , filter_lookup , typed_value )
312
322
313
323
# filter has different DB field 'sources'
314
- q = Q ()
324
+ q = self . Q_CLS ()
315
325
for item in filter_item :
316
326
item_q = self ._build_django_q (item , django_lookup , filter_lookup , typed_value )
317
327
if filter_lookup == FilterLookups .NE :
@@ -432,7 +442,7 @@ def _build_q_for_search(self, operator, str_value):
432
442
433
443
unquoted_value = self .remove_quotes (str_value )
434
444
if not unquoted_value :
435
- return Q ()
445
+ return self . Q_CLS ()
436
446
437
447
if not unquoted_value .startswith (RQL_ANY_SYMBOL ):
438
448
unquoted_value = '*' + unquoted_value
@@ -449,7 +459,7 @@ def _build_q_for_search(self, operator, str_value):
449
459
return q
450
460
451
461
def _build_q_for_extended_search (self , str_value ):
452
- q = Q ()
462
+ q = self . Q_CLS ()
453
463
extended_search_filter_lookup = FilterLookups .I_LIKE
454
464
455
465
for django_orm_route in self .EXTENDED_SEARCH_ORM_ROUTES :
@@ -591,9 +601,9 @@ def _build_filters(self, filters, **kwargs):
591
601
orm_field_name = item .get ('source' , namespace )
592
602
related_orm_route = '{0}{1}__' .format (orm_route , orm_field_name )
593
603
594
- related_model = self ._get_field (
604
+ related_model = self ._get_field_related_model ( self . _get_field (
595
605
_model , orm_field_name , get_related = True ,
596
- ). related_model
606
+ ))
597
607
598
608
qs = item .get ('qs' )
599
609
tree , p_qs = self ._fill_select_tree (
@@ -735,6 +745,14 @@ def _extend_annotations(self):
735
745
736
746
self .annotations .update (dict (extended_annotations ))
737
747
748
+ @classmethod
749
+ def _is_field_supported (cls , field ):
750
+ return isinstance (field , SUPPORTED_FIELD_TYPES )
751
+
752
+ @classmethod
753
+ def _get_field_related_model (cls , field ):
754
+ return field .related_model
755
+
738
756
@classmethod
739
757
def _get_field (cls , base_model , field_name , get_related = False ):
740
758
""" Django ORM field getter.
@@ -750,10 +768,10 @@ def _get_field(cls, base_model, field_name, get_related=False):
750
768
current_field = cls ._get_model_field (current_model , part )
751
769
if index == field_name_parts_length :
752
770
e = 'Unsupported field type: {0}.' .format (field_name )
753
- assert get_related or isinstance (current_field , SUPPORTED_FIELD_TYPES ), e
771
+ assert get_related or cls . _is_field_supported (current_field ), e
754
772
755
773
return current_field
756
- current_model = current_field . related_model
774
+ current_model = cls . _get_field_related_model ( current_field )
757
775
758
776
@staticmethod
759
777
def _get_field_name_parts (field_name ):
@@ -762,6 +780,10 @@ def _get_field_name_parts(field_name):
762
780
763
781
return field_name .split ('.' if '.' in field_name else '__' )
764
782
783
+ @classmethod
784
+ def _is_field_nullable (cls , field ):
785
+ return field .null or cls ._is_pk_field (field )
786
+
765
787
@classmethod
766
788
def _build_mapped_item (cls , field , field_orm_route , ** kwargs ):
767
789
lookups = kwargs .get ('lookups' )
@@ -771,8 +793,8 @@ def _build_mapped_item(cls, field, field_orm_route, **kwargs):
771
793
openapi = kwargs .get ('openapi' )
772
794
hidden = kwargs .get ('hidden' )
773
795
774
- possible_lookups = lookups or FilterTypes .default_field_filter_lookups (field )
775
- if not ( field . null or cls ._is_pk_field (field ) ):
796
+ possible_lookups = lookups or cls . FILTER_TYPES_CLS .default_field_filter_lookups (field )
797
+ if not cls ._is_field_nullable (field ):
776
798
possible_lookups .discard (FilterLookups .NULL )
777
799
778
800
result = {
@@ -924,40 +946,50 @@ def _escape_regex_special_symbols(str_value):
924
946
925
947
@classmethod
926
948
def _convert_value (cls , django_field , str_value , use_repr = False ):
949
+ ft_cls = cls .FILTER_TYPES_CLS
927
950
val = cls .remove_quotes (str_value )
928
- filter_type = FilterTypes .field_filter_type (django_field )
951
+ filter_type = ft_cls .field_filter_type (django_field )
929
952
930
- if filter_type == FilterTypes .FLOAT :
953
+ if filter_type == ft_cls .FLOAT :
931
954
return float (val )
932
955
933
- elif filter_type == FilterTypes .DECIMAL :
934
- if '.' in val :
935
- integer_part , fractional_part = val .split ('.' , 1 )
936
- val = integer_part + '.' + fractional_part [:django_field .decimal_places ]
937
- return decimal .Decimal (val )
956
+ elif filter_type == ft_cls .DECIMAL :
957
+ return cls ._convert_decimal_value (val , django_field )
938
958
939
- elif filter_type == FilterTypes .DATE :
959
+ elif filter_type == ft_cls .DATE :
940
960
return cls ._convert_date_value (val )
941
961
942
- elif filter_type == FilterTypes .DATETIME :
962
+ elif filter_type == ft_cls .DATETIME :
943
963
return cls ._convert_datetime_value (val )
944
964
945
- elif filter_type == FilterTypes .BOOLEAN :
965
+ elif filter_type == ft_cls .BOOLEAN :
946
966
return cls ._convert_boolean_value (val )
947
967
948
968
if val == RQL_EMPTY :
949
- if (filter_type == FilterTypes .INT ) or (not django_field .blank ):
969
+ if (filter_type == ft_cls .INT ) or (not django_field .blank ):
950
970
raise ValueError
951
971
return ''
952
972
953
973
choices = getattr (django_field , 'choices' , None )
954
974
if not choices :
955
- if filter_type == FilterTypes .INT :
975
+ if filter_type == ft_cls .INT :
956
976
return int (val )
957
977
return val
958
978
959
979
return cls ._get_choices_field_db_value (val , choices , filter_type , use_repr )
960
980
981
+ @classmethod
982
+ def _convert_decimal_value (cls , value , field ):
983
+ if '.' in value :
984
+ integer_part , fractional_part = value .split ('.' , 1 )
985
+ value = integer_part + '.' + fractional_part [:cls ._get_decimal_field_precision (field )]
986
+
987
+ return decimal .Decimal (value )
988
+
989
+ @classmethod
990
+ def _get_decimal_field_precision (cls , field ):
991
+ return field .decimal_places
992
+
961
993
@staticmethod
962
994
def _convert_date_value (value ):
963
995
dt = parse_date (value )
@@ -1002,8 +1034,8 @@ def _get_choices_field_db_value(cls, value, choices, filter_type, use_repr):
1002
1034
except StopIteration :
1003
1035
raise ValueError
1004
1036
1005
- @staticmethod
1006
- def _get_choice_class_db_value (value , choices , filter_type , use_repr ):
1037
+ @classmethod
1038
+ def _get_choice_class_db_value (cls , value , choices , filter_type , use_repr ):
1007
1039
if use_repr :
1008
1040
try :
1009
1041
db_value = next (
@@ -1013,7 +1045,7 @@ def _get_choice_class_db_value(value, choices, filter_type, use_repr):
1013
1045
except StopIteration :
1014
1046
raise ValueError
1015
1047
1016
- if filter_type == FilterTypes .INT :
1048
+ if filter_type == cls . FILTER_TYPES_CLS .INT :
1017
1049
db_value = int (value )
1018
1050
else :
1019
1051
db_value = value
@@ -1024,8 +1056,8 @@ def _get_choice_class_db_value(value, choices, filter_type, use_repr):
1024
1056
return db_value
1025
1057
1026
1058
def _build_django_q (self , filter_item , django_lookup , filter_lookup , typed_value ):
1027
- kwargs = {'{0}__{1}' .format (filter_item ['orm_route' ], django_lookup ): typed_value }
1028
- return ~ Q ( ** kwargs ) if filter_lookup == FilterLookups .NE else Q ( ** kwargs )
1059
+ q = self . Q_CLS ( ** {'{0}__{1}' .format (filter_item ['orm_route' ], django_lookup ): typed_value })
1060
+ return ~ q if filter_lookup == FilterLookups .NE else q
1029
1061
1030
1062
@staticmethod
1031
1063
def _get_filter_lookup_by_operator (grammar_operator ):
@@ -1082,9 +1114,10 @@ def _check_dynamic(filter_item, filter_name, filter_route):
1082
1114
e = "{0}: common filters can't have 'field' set." .format (filter_name )
1083
1115
assert not filter_item .get ('custom' , False ) and field is None , e
1084
1116
1085
- @staticmethod
1086
- def _check_search (filter_item , filter_name , field ):
1087
- is_non_string_field_type = FilterTypes .field_filter_type (field ) != FilterTypes .STRING
1117
+ @classmethod
1118
+ def _check_search (cls , filter_item , filter_name , field ):
1119
+ ft_cls = cls .FILTER_TYPES_CLS
1120
+ is_non_string_field_type = ft_cls .field_filter_type (field ) != ft_cls .STRING
1088
1121
1089
1122
e = "{0}: 'search' can be applied only to text filters." .format (filter_name )
1090
1123
assert not (filter_item .get ('search' ) and is_non_string_field_type ), e
0 commit comments