1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
import operator
import functools
from django.db.models import Q
def _verify_parameters(g, mandatory_parameters):
miss = []
for mp in mandatory_parameters:
if not mp in g:
miss.append(mp)
if len(miss):
return miss
return None
def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs):
import urllib
url = reverse(view, kwargs=kwargs)
params = {}
for i in g:
params[i] = g[i]
for i in mandatory_parameters:
if not i in params:
params[i] = mandatory_parameters[i]
return redirect(url + "?%s" % urllib.urlencode(params), *args, **kwargs)
FIELD_SEPARATOR = ":"
VALUE_SEPARATOR = "!"
DESCENDING = "-"
def __get_q_for_val(name, value):
if "OR" in value:
return functools.reduce(operator.or_, map(lambda x: __get_q_for_val(name, x), [ x for x in value.split("OR") ]))
if "AND" in value:
return functools.reduce(operator.and_, map(lambda x: __get_q_for_val(name, x), [ x for x in value.split("AND") ]))
if value.startswith("NOT"):
kwargs = { name : value.strip("NOT") }
return ~Q(**kwargs)
else:
kwargs = { name : value }
return Q(**kwargs)
def _get_filtering_query(filter_string):
search_terms = filter_string.split(FIELD_SEPARATOR)
keys = search_terms[0].split(VALUE_SEPARATOR)
values = search_terms[1].split(VALUE_SEPARATOR)
querydict = dict(zip(keys, values))
return functools.reduce(operator.and_, map(lambda x: __get_q_for_val(x, querydict[x]), [k for k in querydict]))
# we check that the input comes in a valid form that we can recognize
def _validate_input(input, model):
invalid = None
if input:
input_list = input.split(FIELD_SEPARATOR)
# Check we have only one colon
if len(input_list) != 2:
invalid = "We have an invalid number of separators: " + input + " -> " + str(input_list)
return None, invalid
# Check we have an equal number of terms both sides of the colon
if len(input_list[0].split(VALUE_SEPARATOR)) != len(input_list[1].split(VALUE_SEPARATOR)):
invalid = "Not all arg names got values"
return None, invalid + str(input_list)
# Check we are looking for a valid field
valid_fields = model._meta.get_all_field_names()
for field in input_list[0].split(VALUE_SEPARATOR):
if not functools.reduce(lambda x, y: x or y, map(lambda x: field.startswith(x), [ x for x in valid_fields ])):
return None, (field, [ x for x in valid_fields ])
return input, invalid
# uses search_allowed_fields in orm/models.py to create a search query
# for these fields with the supplied input text
def _get_search_results(search_term, queryset, model):
search_objects = []
for st in search_term.split(" "):
q_map = map(lambda x: Q(**{x+'__icontains': st}),
model.search_allowed_fields)
search_objects.append(functools.reduce(operator.or_, q_map))
search_object = functools.reduce(operator.and_, search_objects)
queryset = queryset.filter(search_object)
return queryset
# function to extract the search/filter/ordering parameters from the request
# it uses the request and the model to validate input for the filter and orderby values
def get_search_tuple(request, model):
ordering_string, invalid = _validate_input(request.GET.get('orderby', ''), model)
if invalid:
raise BaseException("Invalid ordering model:" + str(model) + str(invalid))
filter_string, invalid = _validate_input(request.GET.get('filter', ''), model)
if invalid:
raise BaseException("Invalid filter " + str(invalid))
search_term = request.GET.get('search', '')
return (filter_string, search_term, ordering_string)
# returns a lazy-evaluated queryset for a filter/search/order combination
def params_to_queryset(model, queryset, filter_string, search_term, ordering_string):
if filter_string:
filter_query = _get_filtering_query(filter_string)
queryset = queryset.filter(filter_query)
else:
queryset = queryset.all()
if search_term:
queryset = _get_search_results(search_term, queryset, model)
if ordering_string and queryset:
column, order = ordering_string.split(':')
if order.lower() == DESCENDING:
column = '-' + column
# insure only distinct records (e.g. from multiple search hits) are returned
return queryset.distinct()
|