5
5
import re
6
6
from collections import defaultdict
7
7
from datetime import datetime
8
+ from itertools import chain
8
9
from typing import Set
9
10
from uuid import uuid4
10
11
@@ -58,6 +59,13 @@ class RQLFilterClass:
58
59
"""A list or tuple of filters definitions."""
59
60
60
61
EXTENDED_SEARCH_ORM_ROUTES = ()
62
+ """List of additional Django ORM fields for search."""
63
+
64
+ MAX_ORDERING_LENGTH_IN_QUERY = 5
65
+ """Max allowed number of provided ordering filters in query ordering expression."""
66
+
67
+ ALLOWED_ORDERING_PERMUTATIONS_IN_QUERY = None
68
+ """Property to specify a set of allowed ordering permutations (default `None`)."""
61
69
62
70
DISTINCT = False
63
71
"""If True, a `SELECT DISTINCT` will always be executed (default `False`)."""
@@ -107,6 +115,16 @@ def _validate_init(self):
107
115
e = 'Extended search ORM routes must be iterable.'
108
116
assert isinstance (self .EXTENDED_SEARCH_ORM_ROUTES , iterable_types ), e
109
117
118
+ e = 'Max ordering length must be integer.'
119
+ assert isinstance (self .MAX_ORDERING_LENGTH_IN_QUERY , int ), e
120
+
121
+ perms = self .ALLOWED_ORDERING_PERMUTATIONS_IN_QUERY
122
+ if perms :
123
+ e = 'Allowed ordering permutations must be a set of tuples of string filter names.'
124
+ assert isinstance (perms , set ), e
125
+ assert all (isinstance (t , tuple ) for t in perms ), e
126
+ assert all (isinstance (s , str ) for s in chain .from_iterable (perms )), e
127
+
110
128
def _get_init_filters (self ):
111
129
return self .FILTERS
112
130
@@ -120,8 +138,10 @@ def _default_init(self, filters):
120
138
self .select_tree = {}
121
139
self .default_exclusions = set ()
122
140
self .annotations = {}
141
+ self .allowed_ordering_permutations = None
123
142
124
143
self ._build_filters (filters )
144
+ self ._validate_and_store_allowed_ordering_permutations ()
125
145
self ._extend_annotations ()
126
146
127
147
def _init_from_class (self , instance ):
@@ -132,6 +152,7 @@ def _init_from_class(self, instance):
132
152
'select_tree' ,
133
153
'default_exclusions' ,
134
154
'annotations' ,
155
+ 'allowed_ordering_permutations' ,
135
156
)
136
157
for attr in copied_attributes :
137
158
setattr (self , attr , getattr (instance , attr ))
@@ -542,19 +563,29 @@ def __apply_field_optimizations(self, qs, data, node):
542
563
def _apply_ordering (self , qs , properties ):
543
564
if len (properties ) == 0 :
544
565
return qs
545
- elif len (properties ) > 1 :
566
+
567
+ if len (properties ) > 1 :
546
568
raise RQLFilterParsingError (details = {
547
569
'error' : 'Bad ordering filter: query can contain only one ordering operation.' ,
548
570
})
549
571
572
+ if len (properties [0 ]) > self .MAX_ORDERING_LENGTH_IN_QUERY :
573
+ raise RQLFilterParsingError (details = {
574
+ 'error' : 'Bad ordering filter: max allowed number is {n}.' .format (
575
+ n = self .MAX_ORDERING_LENGTH_IN_QUERY ,
576
+ ),
577
+ })
578
+
550
579
ordering_fields = []
580
+ perm = []
551
581
for prop in properties [0 ]:
552
582
filter_name , sign = self ._get_filter_name_with_sign_for_ordering (prop )
553
583
if filter_name not in self .ordering_filters :
554
584
raise RQLFilterParsingError (details = {
555
585
'error' : 'Bad ordering filter: {0}.' .format (filter_name ),
556
586
})
557
587
588
+ perm .append ('{0}{1}' .format (sign , filter_name ))
558
589
filters = self .filters [filter_name ]
559
590
if not isinstance (filters , list ):
560
591
filters = [filters ]
@@ -565,6 +596,12 @@ def _apply_ordering(self, qs, properties):
565
596
ordering_name = self ._get_filter_ordering_name (filter_item , filter_name )
566
597
ordering_fields .append ('{0}{1}' .format (sign , ordering_name ))
567
598
599
+ perms = self .allowed_ordering_permutations
600
+ if perms and tuple (perm ) not in perms :
601
+ raise RQLFilterParsingError (details = {
602
+ 'error' : 'Bad ordering filter: permutation not allowed.' ,
603
+ })
604
+
568
605
return qs .order_by (* ordering_fields )
569
606
570
607
@staticmethod
@@ -766,6 +803,20 @@ def _extend_annotations(self):
766
803
767
804
self .annotations .update (dict (extended_annotations ))
768
805
806
+ def _validate_and_store_allowed_ordering_permutations (self ):
807
+ perms = self .ALLOWED_ORDERING_PERMUTATIONS_IN_QUERY
808
+ if perms :
809
+ for s in chain .from_iterable (perms ):
810
+ filter_name = s [1 :] if s and s [0 ] in ('+' , '-' ) else s
811
+
812
+ e = 'Wrong configuration of allowed ordering permutations: {n}.' .format (n = s )
813
+ assert filter_name in self .ordering_filters , e
814
+
815
+ self .allowed_ordering_permutations = {
816
+ tuple (s [1 :] if s [0 ] == '+' else s for s in t )
817
+ for t in perms
818
+ }
819
+
769
820
@classmethod
770
821
def _is_field_supported (cls , field ):
771
822
return isinstance (field , SUPPORTED_FIELD_TYPES )
0 commit comments