mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-04-11 02:20:59 +00:00
Allow operator to be specified when searching
This commit is contained in:
parent
3449139e76
commit
bc0760d23d
8 changed files with 60 additions and 10 deletions
|
|
@ -197,12 +197,12 @@ class PageQuerySet(MP_NodeQuerySet):
|
|||
"""
|
||||
return self.exclude(self.public_q())
|
||||
|
||||
def search(self, query_string, fields=None, backend='default'):
|
||||
def search(self, query_string, fields=None, operator=None, backend='default'):
|
||||
"""
|
||||
This runs a search query on all the pages in the QuerySet
|
||||
"""
|
||||
search_backend = get_search_backend(backend)
|
||||
return search_backend.search(query_string, self, fields=fields)
|
||||
return search_backend.search(query_string, self, fields=fields, operator=operator)
|
||||
|
||||
def unpublish(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@ from wagtail.wagtailsearch.backends import get_search_backend
|
|||
|
||||
|
||||
class DocumentQuerySet(models.QuerySet):
|
||||
def search(self, query_string, fields=None, backend='default'):
|
||||
def search(self, query_string, fields=None, operator=None, backend='default'):
|
||||
"""
|
||||
This runs a search query on all the documents in the QuerySet
|
||||
"""
|
||||
search_backend = get_search_backend(backend)
|
||||
return search_backend.search(query_string, self, fields=fields)
|
||||
return search_backend.search(query_string, self, fields=fields, operator=operator)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
|
|
|
|||
|
|
@ -43,12 +43,12 @@ class SourceImageIOError(IOError):
|
|||
|
||||
|
||||
class ImageQuerySet(models.QuerySet):
|
||||
def search(self, query_string, fields=None, backend='default'):
|
||||
def search(self, query_string, fields=None, operator=None, backend='default'):
|
||||
"""
|
||||
This runs a search query on all the images in the QuerySet
|
||||
"""
|
||||
search_backend = get_search_backend(backend)
|
||||
return search_backend.search(query_string, self, fields=fields)
|
||||
return search_backend.search(query_string, self, fields=fields, operator=operator)
|
||||
|
||||
|
||||
def get_upload_to(instance, filename):
|
||||
|
|
|
|||
|
|
@ -16,10 +16,13 @@ class FieldError(Exception):
|
|||
|
||||
|
||||
class BaseSearchQuery(object):
|
||||
def __init__(self, queryset, query_string, fields=None):
|
||||
DEFAULT_OPERATOR = 'or'
|
||||
|
||||
def __init__(self, queryset, query_string, fields=None, operator=None):
|
||||
self.queryset = queryset
|
||||
self.query_string = query_string
|
||||
self.fields = fields
|
||||
self.operator = operator or self.DEFAULT_OPERATOR
|
||||
|
||||
def _get_searchable_field(self, field_attname):
|
||||
# Get field
|
||||
|
|
@ -200,7 +203,7 @@ class BaseSearch(object):
|
|||
def delete(self, obj):
|
||||
raise NotImplementedError
|
||||
|
||||
def search(self, query_string, model_or_queryset, fields=None, filters=None, prefetch_related=None):
|
||||
def search(self, query_string, model_or_queryset, fields=None, filters=None, prefetch_related=None, operator=None):
|
||||
# Find model/queryset
|
||||
if isinstance(model_or_queryset, QuerySet):
|
||||
model = model_or_queryset.model
|
||||
|
|
@ -226,6 +229,12 @@ class BaseSearch(object):
|
|||
for prefetch in prefetch_related:
|
||||
queryset = queryset.prefetch_related(prefetch)
|
||||
|
||||
# Check operator
|
||||
if operator is not None:
|
||||
operator = operator.lower()
|
||||
if operator not in ['or', 'and']:
|
||||
raise ValueError("operator must be either 'or' or 'and'")
|
||||
|
||||
# Search
|
||||
search_query = self.search_query_class(queryset, query_string, fields=fields)
|
||||
search_query = self.search_query_class(queryset, query_string, fields=fields, operator=operator)
|
||||
return self.search_results_class(self, search_query)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ from wagtail.wagtailsearch.backends.base import BaseSearch, BaseSearchQuery, Bas
|
|||
|
||||
|
||||
class DBSearchQuery(BaseSearchQuery):
|
||||
DEFAULT_OPERATOR = 'and'
|
||||
|
||||
def _process_lookup(self, field, lookup, value):
|
||||
return models.Q(**{field.get_attname(self.queryset.model) + '__' + lookup: value})
|
||||
|
||||
|
|
@ -52,7 +54,10 @@ class DBSearchQuery(BaseSearchQuery):
|
|||
# Filter on this field
|
||||
term_query |= models.Q(**{'%s__icontains' % field_name: term})
|
||||
|
||||
q &= term_query
|
||||
if self.operator == 'or':
|
||||
q |= term_query
|
||||
elif self.operator == 'and':
|
||||
q &= term_query
|
||||
|
||||
return q
|
||||
|
||||
|
|
|
|||
|
|
@ -156,6 +156,8 @@ class ElasticSearchMapping(object):
|
|||
|
||||
|
||||
class ElasticSearchQuery(BaseSearchQuery):
|
||||
DEFAULT_OPERATOR = 'or'
|
||||
|
||||
def _process_lookup(self, field, lookup, value):
|
||||
# Get the name of the field in the index
|
||||
field_index_name = field.get_index_name(self.queryset.model)
|
||||
|
|
@ -254,6 +256,9 @@ class ElasticSearchQuery(BaseSearchQuery):
|
|||
fields[0]: self.query_string,
|
||||
}
|
||||
}
|
||||
|
||||
if self.operator != 'or':
|
||||
query['match']['operator'] = self.operator
|
||||
else:
|
||||
query = {
|
||||
'multi_match': {
|
||||
|
|
@ -261,6 +266,9 @@ class ElasticSearchQuery(BaseSearchQuery):
|
|||
'fields': fields,
|
||||
}
|
||||
}
|
||||
|
||||
if self.operator != 'or':
|
||||
query['multi_match']['operator'] = self.operator
|
||||
else:
|
||||
query = {
|
||||
'match_all': {}
|
||||
|
|
|
|||
|
|
@ -76,6 +76,18 @@ class BackendTests(WagtailTestUtils):
|
|||
results = self.backend.search("World", models.SearchTest)
|
||||
self.assertEqual(set(results), {self.testa, self.testd.searchtest_ptr})
|
||||
|
||||
def test_operator_or(self):
|
||||
# All records that match any term should be returned
|
||||
results = self.backend.search("Hello world", models.SearchTest, operator='or')
|
||||
|
||||
self.assertEqual(set(results), {self.testa, self.testb, self.testc.searchtest_ptr, self.testd.searchtest_ptr})
|
||||
|
||||
def test_operator_and(self):
|
||||
# Records must match all search terms to be returned
|
||||
results = self.backend.search("Hello world", models.SearchTest, operator='and')
|
||||
|
||||
self.assertEqual(set(results), {self.testa})
|
||||
|
||||
def test_callable_indexed_field(self):
|
||||
results = self.backend.search("Callable", models.SearchTest)
|
||||
self.assertEqual(set(results), {self.testa, self.testb, self.testc.searchtest_ptr, self.testd.searchtest_ptr})
|
||||
|
|
|
|||
|
|
@ -201,6 +201,14 @@ class TestElasticSearchQuery(TestCase):
|
|||
expected_result = {'filtered': {'filter': {'prefix': {'content_type': 'searchtests_searchtest'}}, 'query': {'match_all': {}}}}
|
||||
self.assertDictEqual(query.to_es(), expected_result)
|
||||
|
||||
def test_and_operator(self):
|
||||
# Create a query
|
||||
query = self.ElasticSearchQuery(models.SearchTest.objects.all(), "Hello", operator='and')
|
||||
|
||||
# Check it
|
||||
expected_result = {'filtered': {'filter': {'prefix': {'content_type': 'searchtests_searchtest'}}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials'], 'operator': 'and'}}}}
|
||||
self.assertDictEqual(query.to_es(), expected_result)
|
||||
|
||||
def test_filter(self):
|
||||
# Create a query
|
||||
query = self.ElasticSearchQuery(models.SearchTest.objects.filter(title="Test"), "Hello")
|
||||
|
|
@ -252,6 +260,14 @@ class TestElasticSearchQuery(TestCase):
|
|||
expected_result = {'filtered': {'filter': {'prefix': {'content_type': 'searchtests_searchtest'}}, 'query': {'match': {'title': 'Hello'}}}}
|
||||
self.assertDictEqual(query.to_es(), expected_result)
|
||||
|
||||
def test_fields_with_and_operator(self):
|
||||
# Create a query
|
||||
query = self.ElasticSearchQuery(models.SearchTest.objects.all(), "Hello", fields=['title'], operator='and')
|
||||
|
||||
# Check it
|
||||
expected_result = {'filtered': {'filter': {'prefix': {'content_type': 'searchtests_searchtest'}}, 'query': {'match': {'title': 'Hello', 'operator': 'and'}}}}
|
||||
self.assertDictEqual(query.to_es(), expected_result)
|
||||
|
||||
def test_exact_lookup(self):
|
||||
# Create a query
|
||||
query = self.ElasticSearchQuery(models.SearchTest.objects.filter(title__exact="Test"), "Hello")
|
||||
|
|
|
|||
Loading…
Reference in a new issue