Add AND filter to related search.

Signed-off-by: Yunfan Zhang <yz322@duke.edu>
This commit is contained in:
Yunfan Zhang 2018-08-01 12:20:27 -04:00 committed by Jared Tabor
parent 6b64ef8f64
commit b191f6cfc3
No known key found for this signature in database
GPG Key ID: CC50E67C506270C9
3 changed files with 77 additions and 8 deletions

View File

@ -4,6 +4,7 @@
# Python
import re
import json
from functools import reduce
# Django
from django.core.exceptions import FieldError, ValidationError
@ -238,7 +239,11 @@ class FieldLookupBackend(BaseFilterBackend):
or_filters = []
chain_filters = []
role_filters = []
search_filters = []
search_filters = {}
# Can only have two values: 'AND', 'OR'
# If 'AND' is used, an iterm must satisfy all condition to show up in the results.
# If 'OR' is used, an item just need to satisfy one condition to appear in results.
search_filter_relation = 'OR'
for key, values in request.query_params.lists():
if key in self.RESERVED_NAMES:
continue
@ -262,11 +267,13 @@ class FieldLookupBackend(BaseFilterBackend):
# Search across related objects.
if key.endswith('__search'):
if values and ',' in values[0]:
search_filter_relation = 'AND'
values = reduce(lambda list1, list2: list1 + list2, [i.split(',') for i in values])
for value in values:
search_value, new_keys = self.value_to_python(queryset.model, key, force_text(value))
assert isinstance(new_keys, list)
for new_key in new_keys:
search_filters.append((new_key, search_value))
search_filters[search_value] = new_keys
continue
# Custom chain__ and or__ filters, mutually exclusive (both can
@ -355,11 +362,18 @@ class FieldLookupBackend(BaseFilterBackend):
else:
q |= Q(**{k:v})
args.append(q)
if search_filters:
if search_filters and search_filter_relation == 'OR':
q = Q()
for k,v in search_filters:
q |= Q(**{k:v})
for term, constrains in search_filters.iteritems():
for constrain in constrains:
q |= Q(**{constrain: term})
args.append(q)
elif search_filters and search_filter_relation == 'AND':
for term, constrains in search_filters.iteritems():
q_chain = Q()
for constrain in constrains:
q_chain |= Q(**{constrain: term})
queryset = queryset.filter(q_chain)
for n,k,v in chain_filters:
if n:
q = ~Q(**{k:v})

View File

@ -0,0 +1,54 @@
# Python
import pytest
import json
# Django Rest Framework
from rest_framework.test import APIRequestFactory
# AWX
from awx.api.views import HostList
from awx.main.models import Host, Group, Inventory
from awx.api.versioning import reverse
@pytest.mark.django_db
class TestSearchFilter:
def test_related_research_filter_relation(self, admin):
inv = Inventory.objects.create(name="inv")
group1 = Group.objects.create(name="g1", inventory=inv)
group2 = Group.objects.create(name="g2", inventory=inv)
host1 = Host.objects.create(name="host1", inventory=inv)
host2 = Host.objects.create(name="host2", inventory=inv)
host3 = Host.objects.create(name="host3", inventory=inv)
host1.groups.add(group1)
host2.groups.add(group1)
host2.groups.add(group2)
host3.groups.add(group2)
host1.save()
host2.save()
host3.save()
# Login the client
factory = APIRequestFactory()
# Actually test the endpoint.
host_list_url = reverse('api:host_list')
# Test if the OR releation works.
request = factory.get(host_list_url, data={'groups__search': ['g1', 'g2']})
request.user = admin
response = HostList.as_view()(request)
response.render()
result = json.loads(response.content)
assert result['count'] == 3
expected_hosts = ['host1', 'host2', 'host3']
for i in result['results']:
expected_hosts.remove(i['name'])
assert not expected_hosts
# Test if the AND relation works.
request = factory.get(host_list_url, data={'groups__search': ['g1,g2']})
request.user = admin
response = HostList.as_view()(request)
response.render()
result = json.loads(response.content)
assert result['count'] == 1
assert result['results'][0]['name'] == 'host2'

View File

@ -80,15 +80,16 @@ function QuerysetService ($q, Rest, ProcessErrors, $rootScope, Wait, DjangoSearc
},
// like encodeQueryset, but return an actual unstringified API-consumable http param object
encodeQuerysetObject(params) {
console.log(params);
return _.reduce(params, (obj, value, key) => {
const encodedTerms = this.encodeTerms(value, key);
console.log(encodedTerms);
for (let encodedIndex in encodedTerms) {
const [encodedKey, encodedValue] = encodedTerms[encodedIndex];
obj[encodedKey] = obj[encodedKey] || [];
obj[encodedKey].push(encodedValue);
}
console.log(obj);
return obj;
}, {});
},