diff --git a/awx/main/tests/unit/utils/test_filters.py b/awx/main/tests/unit/utils/test_filters.py index 22444ab1b9..0e267047cd 100644 --- a/awx/main/tests/unit/utils/test_filters.py +++ b/awx/main/tests/unit/utils/test_filters.py @@ -5,6 +5,7 @@ from unittest import mock # AWX from awx.main.utils.filters import SmartFilter, ExternalLoggerEnabled +from awx.main.models import Host # Django from django.db.models import Q @@ -199,7 +200,6 @@ class TestSmartFilterQueryFromString(): @pytest.mark.parametrize("filter_string,q_expected", [ - ('search=foo', Q(Q(**{u"name__icontains": u"foo"}) | Q(**{ u"description__icontains": u"foo"}))), ('group__search=foo', Q(Q(**{u"group__name__icontains": u"foo"}) | Q(**{u"group__description__icontains": u"foo"}))), ('search=foo and group__search=foo', Q( Q(**{u"name__icontains": u"foo"}) | Q(**{ u"description__icontains": u"foo"}), @@ -207,15 +207,35 @@ class TestSmartFilterQueryFromString(): ('search=foo or ansible_facts__a=null', Q(Q(**{u"name__icontains": u"foo"}) | Q(**{u"description__icontains": u"foo"})) | Q(**{u"ansible_facts__contains": {u"a": None}})), - ('search=foo or ansible_facts__a="null"', - Q(Q(**{u"name__icontains": u"foo"}) | Q(**{u"description__icontains": u"foo"})) | - Q(**{u"ansible_facts__contains": {u"a": u"\"null\""}})), ]) def test_search_related_fields(self, mock_get_host_model, filter_string, q_expected): q = SmartFilter.query_from_string(filter_string) assert str(q) == str(q_expected) +class TestSmartFilterQueryFromStringNoDB(): + @pytest.mark.parametrize("filter_string,q_expected", [ + ('ansible_facts__a="true" and ansible_facts__b="true" and ansible_facts__c="true"', + (Q(**{u"ansible_facts__contains": {u"a": u"true"}}) & + Q(**{u"ansible_facts__contains": {u"b": u"true"}}) & + Q(**{u"ansible_facts__contains": {u"c": u"true"}}))), + ('ansible_facts__a="true" or ansible_facts__b="true" or ansible_facts__c="true"', + (Q(**{u"ansible_facts__contains": {u"a": u"true"}}) | + Q(**{u"ansible_facts__contains": {u"b": u"true"}}) | + Q(**{u"ansible_facts__contains": {u"c": u"true"}}))), + ('search=foo', + Q(Q(**{ u"description__icontains": u"foo"}) | Q(**{u"name__icontains": u"foo"}))), + ('search=foo and ansible_facts__a="null"', + Q(Q(**{u"description__icontains": u"foo"}) | Q(**{u"name__icontains": u"foo"})) & + Q(**{u"ansible_facts__contains": {u"a": u"\"null\""}})), + ('name=foo or name=bar and name=foobar', + Q(name="foo") | Q(name="bar") & Q(name="foobar")) + ]) + def test_does_not_invoke_db(self, filter_string, q_expected): + q = SmartFilter.query_from_string(filter_string) + assert str(q.query) == str(Host.objects.filter(q_expected).query) + + ''' #('"facts__quoted_val"="f\"oo"', 1), #('facts__facts__arr[]="foo"', 1), diff --git a/awx/main/utils/filters.py b/awx/main/utils/filters.py index 8cda6f5c28..7cb3b58bb4 100644 --- a/awx/main/utils/filters.py +++ b/awx/main/utils/filters.py @@ -297,7 +297,11 @@ class SmartFilter(object): self.result = None i = 2 while i < len(t[0]): - if not self.result: + ''' + Do NOT observe self.result. It will cause the sql query to be executed. + We do not want that. We only want to build the query. + ''' + if isinstance(self.result, type(None)): self.result = t[0][0].result right = t[0][i].result self.result = self.execute_logic(self.result, right)