Merge pull request #5990 from chrismeyersfsu/feature-jsonsearch

host filtering; handle unicode and python types
This commit is contained in:
Chris Meyers
2017-04-11 09:40:26 -04:00
committed by GitHub
2 changed files with 69 additions and 45 deletions

View File

@@ -323,9 +323,9 @@ unicode_spaces_other = unicode_spaces + [u'(', u')', u'=', u'"']
def string_to_type(t): def string_to_type(t):
if t == 'true': if t == u'true':
return True return True
elif t == 'false': elif t == u'false':
return False return False
if re.search('^[-+]?[0-9]+$',t): if re.search('^[-+]?[0-9]+$',t):
@@ -342,7 +342,6 @@ class DynamicFilterField(models.TextField):
class BoolOperand(object): class BoolOperand(object):
def __init__(self, t): def __init__(self, t):
#print("Got t %s" % t)
kwargs = dict() kwargs = dict()
k, v = self._extract_key_value(t) k, v = self._extract_key_value(t)
k, v = self._json_path_to_contains(k, v) k, v = self._json_path_to_contains(k, v)
@@ -371,13 +370,13 @@ class DynamicFilterField(models.TextField):
contains_count = 0 contains_count = 0
for i, piece in enumerate(pieces): for i, piece in enumerate(pieces):
if flag_first_arr_found is False and piece.endswith('[]'): if flag_first_arr_found is False and piece.endswith('[]'):
assembled_k += '%s__contains' % (piece[0:-2]) assembled_k += u'%s__contains' % (piece[0:-2])
contains_count += 1 contains_count += 1
flag_first_arr_found = True flag_first_arr_found = True
elif flag_first_arr_found is False and i == len(pieces) - 1: elif flag_first_arr_found is False and i == len(pieces) - 1:
assembled_k += '%s' % piece assembled_k += u'%s' % piece
elif flag_first_arr_found is False: elif flag_first_arr_found is False:
assembled_k += '%s__' % piece assembled_k += u'%s__' % piece
elif flag_first_arr_found is True: elif flag_first_arr_found is True:
new_kv = dict() new_kv = dict()
if piece.endswith('[]'): if piece.endswith('[]'):
@@ -401,9 +400,20 @@ class DynamicFilterField(models.TextField):
last_kv = new_kv last_kv = new_kv
contains_count += 1 contains_count += 1
if contains_count == 1 and isinstance(assembled_v, basestring): '''
assembled_v = '"' + assembled_v + '"' Explicit quotes are kept up till this point.
elif contains_count > 1: They will be kept if there is ONLY ONE [] in the key search.
This is because django filter + postgres expect strings to be
quoted "\"hello_world\"". If, instead, there are many [] in a
filter key then we can remove the " and ".
'''
if contains_count != 1:
if type(v) is unicode and v.startswith('"') and v.endswith('"'):
v = v[1:-1]
if contains_count == 0:
assembled_v = v
if contains_count > 1:
if type(last_v) is list: if type(last_v) is list:
last_v.append(v) last_v.append(v)
if type(last_v) is dict: if type(last_v) is dict:
@@ -430,10 +440,11 @@ class DynamicFilterField(models.TextField):
# value # value
# ="something" # ="something"
if t_len > (v_offset + 2) and t[v_offset] == "\"" and t[v_offset + 2] == "\"": if t_len > (v_offset + 2) and t[v_offset] == "\"" and t[v_offset + 2] == "\"":
v = t[v_offset + 1] v = u'"' + unicode(t[v_offset + 1]) + u'"'
#v = t[v_offset + 1]
# empty "" # empty ""
elif t_len > (v_offset + 1): elif t_len > (v_offset + 1):
v = "" v = u""
# no "" # no ""
else: else:
v = string_to_type(t[v_offset]) v = string_to_type(t[v_offset])
@@ -481,6 +492,8 @@ class DynamicFilterField(models.TextField):
* handle optional value quoted: a.b.c="" * handle optional value quoted: a.b.c=""
''' '''
filter_string = unicode(filter_string)
atom = CharsNotIn(unicode_spaces_other) atom = CharsNotIn(unicode_spaces_other)
atom_inside_quotes = CharsNotIn(u'"') atom_inside_quotes = CharsNotIn(u'"')
atom_quoted = Literal('"') + Optional(atom_inside_quotes) + Literal('"') atom_quoted = Literal('"') + Optional(atom_inside_quotes) + Literal('"')

View File

@@ -12,20 +12,20 @@ from django.db.models import Q
class TestDynamicFilterFieldFilterStringToQ(): class TestDynamicFilterFieldFilterStringToQ():
@pytest.mark.parametrize("filter_string,q_expected", [ @pytest.mark.parametrize("filter_string,q_expected", [
('facts__facts__blank=""', Q(facts__facts__blank="")), ('facts__facts__blank=""', Q(**{u"facts__facts__blank": u""})),
('"facts__facts__ space "="f"', Q(**{ "facts__facts__ space ": "f"})), ('"facts__facts__ space "="f"', Q(**{u"facts__facts__ space ": u"f"})),
('"facts__facts__ e "=no_quotes_here', Q(**{ "facts__facts__ e ": "no_quotes_here"})), ('"facts__facts__ e "=no_quotes_here', Q(**{u"facts__facts__ e ": u"no_quotes_here"})),
('a__b__c=3', Q(**{ "a__b__c": 3})), ('a__b__c=3', Q(**{u"a__b__c": 3})),
('a__b__c=3.14', Q(**{ "a__b__c": 3.14})), ('a__b__c=3.14', Q(**{u"a__b__c": 3.14})),
('a__b__c=true', Q(**{ "a__b__c": True})), ('a__b__c=true', Q(**{u"a__b__c": True})),
('a__b__c=false', Q(**{ "a__b__c": False})), ('a__b__c=false', Q(**{u"a__b__c": False})),
('a__b__c="true"', Q(**{ "a__b__c": "true"})), ('a__b__c="true"', Q(**{u"a__b__c": u"true"})),
#('"a__b\"__c"="true"', Q(**{ "a__b\"__c": "true"})), #('"a__b\"__c"="true"', Q(**{u"a__b\"__c": "true"})),
#('a__b\"__c="true"', Q(**{ "a__b\"__c": "true"})), #('a__b\"__c="true"', Q(**{u"a__b\"__c": "true"})),
]) ])
def test_query_generated(self, filter_string, q_expected): def test_query_generated(self, filter_string, q_expected):
q = DynamicFilterField.filter_string_to_q(filter_string) q = DynamicFilterField.filter_string_to_q(filter_string)
assert str(q) == str(q_expected) assert unicode(q) == unicode(q_expected)
@pytest.mark.parametrize("filter_string", [ @pytest.mark.parametrize("filter_string", [
'facts__facts__blank=' 'facts__facts__blank='
@@ -40,39 +40,50 @@ class TestDynamicFilterFieldFilterStringToQ():
]) ])
def test_unicode(self, filter_string, q_expected): def test_unicode(self, filter_string, q_expected):
q = DynamicFilterField.filter_string_to_q(filter_string) q = DynamicFilterField.filter_string_to_q(filter_string)
assert str(q) == str(q_expected) assert unicode(q) == unicode(q_expected)
@pytest.mark.parametrize("filter_string,q_expected", [ @pytest.mark.parametrize("filter_string,q_expected", [
('(a=b)', Q(**{"a": "b"})), ('(a=b)', Q(**{u"a": u"b"})),
('a=b and c=d', Q(**{"a": "b"}) & Q(**{"c": "d"})), ('a=b and c=d', Q(**{u"a": u"b"}) & Q(**{u"c": u"d"})),
('(a=b and c=d)', Q(**{"a": "b"}) & Q(**{"c": "d"})), ('(a=b and c=d)', Q(**{u"a": u"b"}) & Q(**{u"c": u"d"})),
('a=b or c=d', Q(**{"a": "b"}) | Q(**{"c": "d"})), ('a=b or c=d', Q(**{u"a": u"b"}) | Q(**{u"c": u"d"})),
('(a=b and c=d) or (e=f)', (Q(**{"a": "b"}) & Q(**{"c": "d"})) | (Q(**{"e": "f"}))), ('(a=b and c=d) or (e=f)', (Q(**{u"a": u"b"}) & Q(**{u"c": u"d"})) | (Q(**{u"e": u"f"}))),
('(a=b) and (c=d or (e=f and (g=h or i=j))) or (y=z)', Q(**{"a": "b"}) & (Q(**{"c": "d"}) | (Q(**{"e": "f"}) & (Q(**{"g": "h"}) | Q(**{"i": "j"})))) | Q(**{"y": "z"})) ('(a=b) and (c=d or (e=f and (g=h or i=j))) or (y=z)', Q(**{u"a": u"b"}) & (Q(**{u"c": u"d"}) | (Q(**{u"e": u"f"}) & (Q(**{u"g": u"h"}) | Q(**{u"i": u"j"})))) | Q(**{u"y": u"z"}))
]) ])
def test_boolean_parenthesis(self, filter_string, q_expected): def test_boolean_parenthesis(self, filter_string, q_expected):
q = DynamicFilterField.filter_string_to_q(filter_string) q = DynamicFilterField.filter_string_to_q(filter_string)
assert str(q) == str(q_expected) assert unicode(q) == unicode(q_expected)
@pytest.mark.parametrize("filter_string,q_expected", [ @pytest.mark.parametrize("filter_string,q_expected", [
('a__b__c[]=3', Q(**{ "a__b__c__contains": 3})), ('a__b__c[]=3', Q(**{u"a__b__c__contains": 3})),
('a__b__c[]=3.14', Q(**{ "a__b__c__contains": 3.14})), ('a__b__c[]=3.14', Q(**{u"a__b__c__contains": 3.14})),
('a__b__c[]=true', Q(**{ "a__b__c__contains": True})), ('a__b__c[]=true', Q(**{u"a__b__c__contains": True})),
('a__b__c[]=false', Q(**{ "a__b__c__contains": False})), ('a__b__c[]=false', Q(**{u"a__b__c__contains": False})),
('a__b__c[]="true"', Q(**{ "a__b__c__contains": "\"true\""})), ('a__b__c[]="true"', Q(**{u"a__b__c__contains": u"\"true\""})),
('a__b__c[]="hello world"', Q(**{ "a__b__c__contains": "\"hello world\""})), ('a__b__c[]="hello world"', Q(**{u"a__b__c__contains": u"\"hello world\""})),
('a__b__c[]__d[]="foobar"', Q(**{ "a__b__c__contains": [{"d": ["foobar"]}]})), ('a__b__c[]__d[]="foobar"', Q(**{u"a__b__c__contains": [{u"d": [u"foobar"]}]})),
('a__b__c[]__d="foobar"', Q(**{ "a__b__c__contains": [{"d": "foobar"}]})), ('a__b__c[]__d="foobar"', Q(**{u"a__b__c__contains": [{u"d": u"foobar"}]})),
('a__b__c[]__d__e="foobar"', Q(**{ "a__b__c__contains": [{"d": {"e": "foobar"}}]})), ('a__b__c[]__d__e="foobar"', Q(**{u"a__b__c__contains": [{u"d": {u"e": u"foobar"}}]})),
('a__b__c[]__d__e[]="foobar"', Q(**{ "a__b__c__contains": [{"d": {"e": ["foobar"]}}]})), ('a__b__c[]__d__e[]="foobar"', Q(**{u"a__b__c__contains": [{u"d": {u"e": [u"foobar"]}}]})),
('a__b__c[]__d__e__f[]="foobar"', Q(**{ "a__b__c__contains": [{"d": {"e": {"f": ["foobar"]}}}]})), ('a__b__c[]__d__e__f[]="foobar"', Q(**{u"a__b__c__contains": [{u"d": {u"e": {u"f": [u"foobar"]}}}]})),
('(a__b__c[]__d__e__f[]="foobar") and (a__b__c[]__d__e[]="foobar")', Q(**{ "a__b__c__contains": [{"d": {"e": {"f": ["foobar"]}}}]}) & Q(**{ "a__b__c__contains": [{"d": {"e": ["foobar"]}}]})), ('(a__b__c[]__d__e__f[]="foobar") and (a__b__c[]__d__e[]="foobar")', Q(**{ u"a__b__c__contains": [{u"d": {u"e": {u"f": [u"foobar"]}}}]}) & Q(**{u"a__b__c__contains": [{u"d": {u"e": [u"foobar"]}}]})),
#('"a__b\"__c"="true"', Q(**{ "a__b\"__c": "true"})), #('"a__b\"__c"="true"', Q(**{u"a__b\"__c": "true"})),
#('a__b\"__c="true"', Q(**{ "a__b\"__c": "true"})), #('a__b\"__c="true"', Q(**{u"a__b\"__c": "true"})),
]) ])
def test_contains_query_generated(self, filter_string, q_expected): def test_contains_query_generated(self, filter_string, q_expected):
q = DynamicFilterField.filter_string_to_q(filter_string) q = DynamicFilterField.filter_string_to_q(filter_string)
assert str(q) == str(q_expected) assert unicode(q) == unicode(q_expected)
@pytest.mark.parametrize("filter_string,q_expected", [
#('a__b__c[]="true"', Q(**{u"a__b__c__contains": u"\"true\""})),
('a__b__c="true"', Q(**{u"a__b__c": u"true"})),
#('"a__b\"__c"="true"', Q(**{u"a__b\"__c": "true"})),
#('a__b\"__c="true"', Q(**{u"a__b\"__c": "true"})),
])
def test_contains_query_generated_unicode(self, filter_string, q_expected):
q = DynamicFilterField.filter_string_to_q(filter_string)
assert unicode(q) == unicode(q_expected)
''' '''