mirror of
https://github.com/ansible/awx.git
synced 2026-01-13 19:10:07 -03:30
AC-728 Added chain__ filter prefix.
This commit is contained in:
parent
f29809a807
commit
112fe089d9
@ -114,6 +114,7 @@ class FieldLookupBackend(BaseFilterBackend):
|
||||
# below is (negate, field, value).
|
||||
and_filters = []
|
||||
or_filters = []
|
||||
chain_filters = []
|
||||
for key, values in request.QUERY_PARAMS.lists():
|
||||
if key in self.RESERVED_NAMES:
|
||||
continue
|
||||
@ -123,11 +124,18 @@ class FieldLookupBackend(BaseFilterBackend):
|
||||
if key.endswith('__int'):
|
||||
key = key[:-5]
|
||||
q_int = True
|
||||
# Custom or__ filter prefix (or__ can precede not__).
|
||||
|
||||
# Custom chain__ and or__ filters, mutually exclusive (both can
|
||||
# precede not__).
|
||||
q_chain = False
|
||||
q_or = False
|
||||
if key.startswith('or__'):
|
||||
if key.startswith('chain__'):
|
||||
key = key[7:]
|
||||
q_chain = True
|
||||
elif key.startswith('or__'):
|
||||
key = key[4:]
|
||||
q_or = True
|
||||
|
||||
# Custom not__ filter prefix.
|
||||
q_not = False
|
||||
if key.startswith('not__'):
|
||||
@ -139,13 +147,15 @@ class FieldLookupBackend(BaseFilterBackend):
|
||||
if q_int:
|
||||
value = int(value)
|
||||
value = self.value_to_python(queryset.model, key, value)
|
||||
if q_or:
|
||||
if q_chain:
|
||||
chain_filters.append((q_not, key, value))
|
||||
elif q_or:
|
||||
or_filters.append((q_not, key, value))
|
||||
else:
|
||||
and_filters.append((q_not, key, value))
|
||||
|
||||
# Now build Q objects for database query filter.
|
||||
if and_filters or or_filters:
|
||||
if and_filters or or_filters or chain_filters:
|
||||
args = []
|
||||
for n, k, v in and_filters:
|
||||
if n:
|
||||
@ -160,8 +170,14 @@ class FieldLookupBackend(BaseFilterBackend):
|
||||
else:
|
||||
q |= Q(**{k:v})
|
||||
args.append(q)
|
||||
for n,k,v in chain_filters:
|
||||
if n:
|
||||
q = ~Q(**{k:v})
|
||||
else:
|
||||
q = Q(**{k:v})
|
||||
queryset = queryset.filter(q)
|
||||
queryset = queryset.filter(*args)
|
||||
return queryset
|
||||
return queryset.distinct()
|
||||
except (FieldError, FieldDoesNotExist, ValueError), e:
|
||||
raise ParseError(e.args[0])
|
||||
except ValidationError, e:
|
||||
|
||||
@ -106,6 +106,7 @@ class APIView(views.APIView):
|
||||
'docstring': type(self).__doc__ or '',
|
||||
'new_in_13': getattr(self, 'new_in_13', False),
|
||||
'new_in_14': getattr(self, 'new_in_14', False),
|
||||
'new_in_15': getattr(self, 'new_in_15', False),
|
||||
}
|
||||
|
||||
def get_description(self, html=False):
|
||||
|
||||
@ -83,6 +83,20 @@ with `or__`:
|
||||
?or__field=value&or__field=othervalue
|
||||
?or__not__field=value&or__field=othervalue
|
||||
|
||||
(_New in AWX 1.5_) The default AND filtering applies all filters simultaneously
|
||||
to each related object being filtered across database relationships. The chain
|
||||
filter instead applies filters separately for each related object. To use,
|
||||
prefix the query string parameter with `chain__`:
|
||||
|
||||
?chain__related__field=value&chain__related__field2=othervalue
|
||||
?chain__not__related__field=value&chain__related__field2=othervalue
|
||||
|
||||
If the first query above were written as
|
||||
`?related__field=value&related__field2=othervalue`, it would return only the
|
||||
primary objects where the *same* related object satisfied both conditions. As
|
||||
written using the chain filter, it would return the intersection of primary
|
||||
objects matching each condition.
|
||||
|
||||
Field lookups may also be used for more advanced queries, by appending the
|
||||
lookup to the field name:
|
||||
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
{% if new_in_13 %}> _New in AWX 1.3_{% endif %}
|
||||
{% if new_in_14 %}> _New in AWX 1.4_{% endif %}
|
||||
{% if new_in_15 %}> _New in AWX 1.5_{% endif %}
|
||||
|
||||
@ -28,10 +28,11 @@ class UsersTest(BaseTest):
|
||||
def setUp(self):
|
||||
super(UsersTest, self).setUp()
|
||||
self.setup_users()
|
||||
self.organizations = self.make_organizations(self.super_django_user, 1)
|
||||
self.organizations = self.make_organizations(self.super_django_user, 2)
|
||||
self.organizations[0].admins.add(self.normal_django_user)
|
||||
self.organizations[0].users.add(self.other_django_user)
|
||||
self.organizations[0].users.add(self.normal_django_user)
|
||||
self.organizations[1].users.add(self.other_django_user)
|
||||
|
||||
def test_only_super_user_or_org_admin_can_add_users(self):
|
||||
url = reverse('api:user_list')
|
||||
@ -560,6 +561,19 @@ class UsersTest(BaseTest):
|
||||
self.assertTrue(qs.count())
|
||||
self.check_get_list(url, self.super_django_user, qs)
|
||||
|
||||
# Verify difference between normal AND filter vs. filtering with
|
||||
# chain__ prefix.
|
||||
url = '%s?organizations__name__startswith=org0&organizations__name__startswith=org1' % base_url
|
||||
qs = base_qs.filter(Q(organizations__name__startswith='org0'),
|
||||
Q(organizations__name__startswith='org1'))
|
||||
self.assertFalse(qs.count())
|
||||
self.check_get_list(url, self.super_django_user, qs)
|
||||
url = '%s?chain__organizations__name__startswith=org0&chain__organizations__name__startswith=org1' % base_url
|
||||
qs = base_qs.filter(organizations__name__startswith='org0')
|
||||
qs = qs.filter(organizations__name__startswith='org1')
|
||||
self.assertTrue(qs.count())
|
||||
self.check_get_list(url, self.super_django_user, qs)
|
||||
|
||||
# Filter by related organization not present.
|
||||
url = '%s?organizations=None' % base_url
|
||||
qs = base_qs.filter(organizations=None)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user