diff --git a/awx/api/views.py b/awx/api/views.py index b082fb16d0..d31b46e916 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -1924,8 +1924,8 @@ class HostList(ListCreateAPIView): qs = super(HostList, self).get_queryset() filter_string = self.request.query_params.get('host_filter', None) if filter_string: - filter_q = DynamicFilter.query_from_string(filter_string) - qs = qs.filter(filter_q) + filter_qs = DynamicFilter.query_from_string(filter_string) + qs &= filter_qs return qs def list(self, *args, **kwargs): diff --git a/awx/main/managers.py b/awx/main/managers.py index 2bb1ebece4..fc99cbd7a4 100644 --- a/awx/main/managers.py +++ b/awx/main/managers.py @@ -41,7 +41,7 @@ class HostManager(models.Manager): # If we don't disable this, a filter of {'inventory': self.instance} gets automatically # injected by the related object mapper. self.core_filters = {} - return qs.filter(q) + return qs & q return qs diff --git a/awx/main/tests/unit/utils/test_filters.py b/awx/main/tests/unit/utils/test_filters.py index 5147e4fbc2..b2f034d782 100644 --- a/awx/main/tests/unit/utils/test_filters.py +++ b/awx/main/tests/unit/utils/test_filters.py @@ -1,6 +1,7 @@ # Python import pytest +import mock # AWX from awx.main.utils.filters import DynamicFilter @@ -9,6 +10,18 @@ from awx.main.utils.filters import DynamicFilter from django.db.models import Q +class mockObjects: + def filter(self, *args, **kwargs): + return Q(*args, **kwargs) + + +class mockHost: + def __init__(self): + print("Host mock created") + self.objects = mockObjects() + + +@mock.patch('awx.main.utils.filters.get_host_model', return_value=mockHost()) class TestDynamicFilterQueryFromString(): @pytest.mark.parametrize("filter_string,q_expected", [ ('facts__facts__blank=""', Q(**{u"facts__facts__blank": u""})), @@ -22,7 +35,7 @@ class TestDynamicFilterQueryFromString(): #('"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): + def test_query_generated(self, mock_get_host_model, filter_string, q_expected): q = DynamicFilter.query_from_string(filter_string) assert unicode(q) == unicode(q_expected) @@ -30,7 +43,7 @@ class TestDynamicFilterQueryFromString(): 'ansible_facts__facts__facts__blank=' 'ansible_facts__a__b__c__ space =ggg', ]) - def test_invalid_filter_strings(self, filter_string): + def test_invalid_filter_strings(self, mock_get_host_model, filter_string): with pytest.raises(RuntimeError) as e: DynamicFilter.query_from_string(filter_string) assert e.value.message == u"Invalid query " + filter_string @@ -39,7 +52,7 @@ class TestDynamicFilterQueryFromString(): (u'(a=abc\u1F5E3def)', Q(**{u"a": u"abc\u1F5E3def"})), (u'(ansible_facts__a=abc\u1F5E3def)', Q(**{u"ansible_facts__contains": {u"a": u"abc\u1F5E3def"}})), ]) - def test_unicode(self, filter_string, q_expected): + def test_unicode(self, mock_get_host_model, filter_string, q_expected): q = DynamicFilter.query_from_string(filter_string) assert unicode(q) == unicode(q_expected) @@ -53,7 +66,7 @@ class TestDynamicFilterQueryFromString(): ('(a=b) and not (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"})), ('a=b or a=d or a=e or a=z and b=h and b=i and b=j and b=k', Q(**{u"a": u"b"}) | Q(**{u"a": u"d"}) | Q(**{u"a": u"e"}) | Q(**{u"a": u"z"}) & Q(**{u"b": u"h"}) & Q(**{u"b": u"i"}) & Q(**{u"b": u"j"}) & Q(**{u"b": u"k"})) ]) - def test_boolean_parenthesis(self, filter_string, q_expected): + def test_boolean_parenthesis(self, mock_get_host_model, filter_string, q_expected): q = DynamicFilter.query_from_string(filter_string) assert unicode(q) == unicode(q_expected) @@ -73,7 +86,7 @@ class TestDynamicFilterQueryFromString(): #('"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): + def test_contains_query_generated(self, mock_get_host_model, filter_string, q_expected): q = DynamicFilter.query_from_string(filter_string) assert unicode(q) == unicode(q_expected) @@ -83,7 +96,7 @@ class TestDynamicFilterQueryFromString(): #('"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): + def test_contains_query_generated_unicode(self, mock_get_host_model, filter_string, q_expected): q = DynamicFilter.query_from_string(filter_string) assert unicode(q) == unicode(q_expected) @@ -91,7 +104,7 @@ class TestDynamicFilterQueryFromString(): ('ansible_facts__a=null', Q(**{u"ansible_facts__contains": {u"a": u"null"}})), ('ansible_facts__c="null"', Q(**{u"ansible_facts__contains": {u"c": u"\"null\""}})), ]) - def test_contains_query_generated_null(self, filter_string, q_expected): + def test_contains_query_generated_null(self, mock_get_host_model, filter_string, q_expected): q = DynamicFilter.query_from_string(filter_string) assert unicode(q) == unicode(q_expected) diff --git a/awx/main/utils/filters.py b/awx/main/utils/filters.py index 9d1e5a7aa4..03ede25ea6 100644 --- a/awx/main/utils/filters.py +++ b/awx/main/utils/filters.py @@ -8,8 +8,7 @@ from pyparsing import ( CharsNotIn, ) -from django.db import models - +import django __all__ = ['DynamicFilter'] @@ -32,6 +31,10 @@ def string_to_type(t): return t +def get_host_model(): + return django.apps.apps.get_model('main', 'host') + + class DynamicFilter(object): SEARCHABLE_RELATIONSHIP = 'ansible_facts' @@ -41,7 +44,10 @@ class DynamicFilter(object): k, v = self._extract_key_value(t) k, v = self._json_path_to_contains(k, v) kwargs[k] = v - self.result = models.Q(**kwargs) + + # Avoid import circular dependency + Host = get_host_model() + self.result = Host.objects.filter(**kwargs) def strip_quotes_traditional_logic(self, v): if type(v) is unicode and v.startswith('"') and v.endswith('"'):