diff --git a/awx/main/fields.py b/awx/main/fields.py index aeb5101dc9..3f1dd3db76 100644 --- a/awx/main/fields.py +++ b/awx/main/fields.py @@ -323,9 +323,9 @@ unicode_spaces_other = unicode_spaces + [u'(', u')', u'=', u'"'] def string_to_type(t): - if t == 'true': + if t == u'true': return True - elif t == 'false': + elif t == u'false': return False if re.search('^[-+]?[0-9]+$',t): @@ -342,7 +342,6 @@ class DynamicFilterField(models.TextField): class BoolOperand(object): def __init__(self, t): - #print("Got t %s" % t) kwargs = dict() k, v = self._extract_key_value(t) k, v = self._json_path_to_contains(k, v) @@ -371,13 +370,13 @@ class DynamicFilterField(models.TextField): contains_count = 0 for i, piece in enumerate(pieces): 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 flag_first_arr_found = True 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: - assembled_k += '%s__' % piece + assembled_k += u'%s__' % piece elif flag_first_arr_found is True: new_kv = dict() if piece.endswith('[]'): @@ -401,9 +400,20 @@ class DynamicFilterField(models.TextField): last_kv = new_kv contains_count += 1 - if contains_count == 1 and isinstance(assembled_v, basestring): - assembled_v = '"' + assembled_v + '"' - elif contains_count > 1: + ''' + Explicit quotes are kept up till this point. + 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: last_v.append(v) if type(last_v) is dict: @@ -430,10 +440,11 @@ class DynamicFilterField(models.TextField): # value # ="something" 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 "" elif t_len > (v_offset + 1): - v = "" + v = u"" # no "" else: v = string_to_type(t[v_offset]) @@ -481,6 +492,8 @@ class DynamicFilterField(models.TextField): * handle optional value quoted: a.b.c="" ''' + filter_string = unicode(filter_string) + atom = CharsNotIn(unicode_spaces_other) atom_inside_quotes = CharsNotIn(u'"') atom_quoted = Literal('"') + Optional(atom_inside_quotes) + Literal('"') diff --git a/awx/main/tests/unit/test_fields.py b/awx/main/tests/unit/test_fields.py index f203d42d4d..699d049a8d 100644 --- a/awx/main/tests/unit/test_fields.py +++ b/awx/main/tests/unit/test_fields.py @@ -12,20 +12,20 @@ from django.db.models import Q class TestDynamicFilterFieldFilterStringToQ(): @pytest.mark.parametrize("filter_string,q_expected", [ - ('facts__facts__blank=""', Q(facts__facts__blank="")), - ('"facts__facts__ space "="f"', Q(**{ "facts__facts__ space ": "f"})), - ('"facts__facts__ e "=no_quotes_here', Q(**{ "facts__facts__ e ": "no_quotes_here"})), - ('a__b__c=3', Q(**{ "a__b__c": 3})), - ('a__b__c=3.14', Q(**{ "a__b__c": 3.14})), - ('a__b__c=true', Q(**{ "a__b__c": True})), - ('a__b__c=false', Q(**{ "a__b__c": False})), - ('a__b__c="true"', Q(**{ "a__b__c": "true"})), - #('"a__b\"__c"="true"', Q(**{ "a__b\"__c": "true"})), - #('a__b\"__c="true"', Q(**{ "a__b\"__c": "true"})), + ('facts__facts__blank=""', Q(**{u"facts__facts__blank": u""})), + ('"facts__facts__ space "="f"', Q(**{u"facts__facts__ space ": u"f"})), + ('"facts__facts__ e "=no_quotes_here', Q(**{u"facts__facts__ e ": u"no_quotes_here"})), + ('a__b__c=3', Q(**{u"a__b__c": 3})), + ('a__b__c=3.14', Q(**{u"a__b__c": 3.14})), + ('a__b__c=true', Q(**{u"a__b__c": True})), + ('a__b__c=false', Q(**{u"a__b__c": False})), + ('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_query_generated(self, filter_string, q_expected): 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", [ 'facts__facts__blank=' @@ -40,39 +40,50 @@ class TestDynamicFilterFieldFilterStringToQ(): ]) def test_unicode(self, filter_string, q_expected): 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)', Q(**{"a": "b"})), - ('a=b and c=d', Q(**{"a": "b"}) & Q(**{"c": "d"})), - ('(a=b and c=d)', Q(**{"a": "b"}) & Q(**{"c": "d"})), - ('a=b or c=d', Q(**{"a": "b"}) | Q(**{"c": "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 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)', Q(**{u"a": u"b"})), + ('a=b and c=d', Q(**{u"a": u"b"}) & Q(**{u"c": u"d"})), + ('(a=b and c=d)', Q(**{u"a": u"b"}) & Q(**{u"c": u"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(**{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(**{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): 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[]=3', Q(**{ "a__b__c__contains": 3})), - ('a__b__c[]=3.14', Q(**{ "a__b__c__contains": 3.14})), - ('a__b__c[]=true', Q(**{ "a__b__c__contains": True})), - ('a__b__c[]=false', Q(**{ "a__b__c__contains": False})), - ('a__b__c[]="true"', Q(**{ "a__b__c__contains": "\"true\""})), - ('a__b__c[]="hello world"', Q(**{ "a__b__c__contains": "\"hello world\""})), - ('a__b__c[]__d[]="foobar"', Q(**{ "a__b__c__contains": [{"d": ["foobar"]}]})), - ('a__b__c[]__d="foobar"', Q(**{ "a__b__c__contains": [{"d": "foobar"}]})), - ('a__b__c[]__d__e="foobar"', Q(**{ "a__b__c__contains": [{"d": {"e": "foobar"}}]})), - ('a__b__c[]__d__e[]="foobar"', Q(**{ "a__b__c__contains": [{"d": {"e": ["foobar"]}}]})), - ('a__b__c[]__d__e__f[]="foobar"', Q(**{ "a__b__c__contains": [{"d": {"e": {"f": ["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"="true"', Q(**{ "a__b\"__c": "true"})), - #('a__b\"__c="true"', Q(**{ "a__b\"__c": "true"})), + ('a__b__c[]=3', Q(**{u"a__b__c__contains": 3})), + ('a__b__c[]=3.14', Q(**{u"a__b__c__contains": 3.14})), + ('a__b__c[]=true', Q(**{u"a__b__c__contains": True})), + ('a__b__c[]=false', Q(**{u"a__b__c__contains": False})), + ('a__b__c[]="true"', Q(**{u"a__b__c__contains": u"\"true\""})), + ('a__b__c[]="hello world"', Q(**{u"a__b__c__contains": u"\"hello world\""})), + ('a__b__c[]__d[]="foobar"', Q(**{u"a__b__c__contains": [{u"d": [u"foobar"]}]})), + ('a__b__c[]__d="foobar"', Q(**{u"a__b__c__contains": [{u"d": u"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(**{u"a__b__c__contains": [{u"d": {u"e": [u"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(**{ 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(**{u"a__b\"__c": "true"})), + #('a__b\"__c="true"', Q(**{u"a__b\"__c": "true"})), ]) def test_contains_query_generated(self, filter_string, q_expected): 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) + '''