diff --git a/docs/topics/search/searching.rst b/docs/topics/search/searching.rst index 9f061b9dba..66683c405f 100644 --- a/docs/topics/search/searching.rst +++ b/docs/topics/search/searching.rst @@ -138,7 +138,6 @@ Here's an example of using the "operator" keyword argument: # Only "hello world" returned as that's the only item that contains both terms [] - For page, image and document models, the ``operator`` keyword argument is also supported on the QuerySet's ``search`` method: .. code-block:: python @@ -149,6 +148,24 @@ For page, image and document models, the ``operator`` keyword argument is also s [, , ] +Custom ordering +^^^^^^^^^^^^^^^ + +.. versionadded:: 1.2 + +By default, search results are ordered by relevance, if the backend supports it. To preserve the QuerySet's existing ordering, the ``order_by_relevance`` keyword argument needs to be set to ``False`` on the ``search()`` method. + +For example: + +.. code-block:: python + + # Get a list of events ordered by date + >>> EventPage.objects.order_by('date').search("Event", order_by_relevance=False) + + # Events ordered by date + [, , ] + + .. _wagtailsearch_frontend_views: diff --git a/wagtail/tests/search/models.py b/wagtail/tests/search/models.py index d5f6465766..4f087c5b7c 100644 --- a/wagtail/tests/search/models.py +++ b/wagtail/tests/search/models.py @@ -43,6 +43,9 @@ class SearchTest(models.Model, index.Indexed): # Return the child if there is one, otherwise return self return child or self + def __str__(self): + return self.title + class SearchTestChild(SearchTest): subtitle = models.CharField(max_length=255, null=True, blank=True) diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 71efb3975d..146bcfceda 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -235,8 +235,10 @@ class PageManager(models.Manager): def not_public(self): return self.get_queryset().not_public() - def search(self, query_string, fields=None, backend='default'): - return self.get_queryset().search(query_string, fields=fields, backend=backend) + def search(self, query_string, fields=None, + operator=None, order_by_relevance=True, backend='default'): + return self.get_queryset().search(query_string, fields=fields, + operator=operator, order_by_relevance=order_by_relevance, backend=backend) def specific(self): return self.get_queryset().specific() diff --git a/wagtail/wagtailcore/query.py b/wagtail/wagtailcore/query.py index a1e1a89790..ac9d4d7dbb 100644 --- a/wagtail/wagtailcore/query.py +++ b/wagtail/wagtailcore/query.py @@ -7,10 +7,10 @@ from django.apps import apps from treebeard.mp_tree import MP_NodeQuerySet -from wagtail.wagtailsearch.backends import get_search_backend +from wagtail.wagtailsearch.queryset import SearchableQuerySetMixin -class PageQuerySet(MP_NodeQuerySet): +class PageQuerySet(SearchableQuerySetMixin, MP_NodeQuerySet): def live_q(self): return Q(live=True) @@ -197,13 +197,6 @@ class PageQuerySet(MP_NodeQuerySet): """ return self.exclude(self.public_q()) - 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, operator=operator) - def unpublish(self): """ This unpublishes all pages in the QuerySet diff --git a/wagtail/wagtailcore/tests/test_page_queryset.py b/wagtail/wagtailcore/tests/test_page_queryset.py index c9913bc9c5..aac13f3f1b 100644 --- a/wagtail/wagtailcore/tests/test_page_queryset.py +++ b/wagtail/wagtailcore/tests/test_page_queryset.py @@ -322,6 +322,46 @@ class TestPageQuerySet(TestCase): self.assertTrue(pages.filter(id=event.id).exists()) +class TestPageQuerySetSearch(TestCase): + fixtures = ['test.json'] + + def test_search(self): + pages = EventPage.objects.search('moon', fields=['location']) + + self.assertEqual(pages.count(), 2) + self.assertIn(Page.objects.get(url_path='/home/events/tentative-unpublished-event/').specific, pages) + self.assertIn(Page.objects.get(url_path='/home/events/someone-elses-event/').specific, pages) + + def test_operators(self): + results = EventPage.objects.search("moon ponies", operator='and') + + self.assertEqual(list(results), [ + Page.objects.get(url_path='/home/events/tentative-unpublished-event/').specific + ]) + + results = EventPage.objects.search("moon ponies", operator='or') + sorted_results = sorted(results, key=lambda page: page.url_path) + self.assertEqual(sorted_results, [ + Page.objects.get(url_path='/home/events/someone-elses-event/').specific, + Page.objects.get(url_path='/home/events/tentative-unpublished-event/').specific, + ]) + + def test_custom_order(self): + pages = EventPage.objects.order_by('url_path').search('moon', fields=['location'], order_by_relevance=False) + + self.assertEqual(list(pages), [ + Page.objects.get(url_path='/home/events/someone-elses-event/').specific, + Page.objects.get(url_path='/home/events/tentative-unpublished-event/').specific, + ]) + + pages = EventPage.objects.order_by('-url_path').search('moon', fields=['location'], order_by_relevance=False) + + self.assertEqual(list(pages), [ + Page.objects.get(url_path='/home/events/tentative-unpublished-event/').specific, + Page.objects.get(url_path='/home/events/someone-elses-event/').specific, + ]) + + class TestSpecificQuery(TestCase): """ Test the .specific() queryset method. This is isolated in its own test case diff --git a/wagtail/wagtaildocs/models.py b/wagtail/wagtaildocs/models.py index 0447864833..56264b6937 100644 --- a/wagtail/wagtaildocs/models.py +++ b/wagtail/wagtaildocs/models.py @@ -16,16 +16,11 @@ from django.utils.encoding import python_2_unicode_compatible from wagtail.wagtailadmin.taggable import TagSearchable from wagtail.wagtailadmin.utils import get_object_usage from wagtail.wagtailsearch import index -from wagtail.wagtailsearch.backends import get_search_backend +from wagtail.wagtailsearch.queryset import SearchableQuerySetMixin -class DocumentQuerySet(models.QuerySet): - 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, operator=operator) +class DocumentQuerySet(SearchableQuerySetMixin, models.QuerySet): + pass @python_2_unicode_compatible diff --git a/wagtail/wagtaildocs/tests.py b/wagtail/wagtaildocs/tests.py index 05cda49dde..dda7ac9f44 100644 --- a/wagtail/wagtaildocs/tests.py +++ b/wagtail/wagtaildocs/tests.py @@ -34,6 +34,26 @@ class TestDocumentQuerySet(TestCase): results = models.Document.objects.search("Test") self.assertEqual(list(results), [document]) + def test_operators(self): + aaa_document = models.Document.objects.create(title="AAA Test document") + zzz_document = models.Document.objects.create(title="ZZZ Test document") + + results = models.Document.objects.search("aaa test", operator='and') + self.assertEqual(list(results), [aaa_document]) + + results = models.Document.objects.search("aaa test", operator='or') + sorted_results = sorted(results, key=lambda doc: doc.title) + self.assertEqual(sorted_results, [aaa_document, zzz_document]) + + def test_custom_ordering(self): + aaa_document = models.Document.objects.create(title="AAA Test document") + zzz_document = models.Document.objects.create(title="ZZZ Test document") + + results = models.Document.objects.order_by('title').search("Test") + self.assertEqual(list(results), [aaa_document, zzz_document]) + results = models.Document.objects.order_by('-title').search("Test") + self.assertEqual(list(results), [zzz_document, aaa_document]) + class TestDocumentPermissions(TestCase): def setUp(self): diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py index 8d3d56bdd3..c842639e3a 100644 --- a/wagtail/wagtailimages/models.py +++ b/wagtail/wagtailimages/models.py @@ -28,7 +28,7 @@ from unidecode import unidecode from wagtail.wagtailcore import hooks from wagtail.wagtailadmin.taggable import TagSearchable from wagtail.wagtailsearch import index -from wagtail.wagtailsearch.backends import get_search_backend +from wagtail.wagtailsearch.queryset import SearchableQuerySetMixin from wagtail.wagtailimages.rect import Rect from wagtail.wagtailimages.exceptions import InvalidFilterSpecError from wagtail.wagtailadmin.utils import get_object_usage @@ -42,13 +42,8 @@ class SourceImageIOError(IOError): pass -class ImageQuerySet(models.QuerySet): - 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, operator=operator) +class ImageQuerySet(SearchableQuerySetMixin, models.QuerySet): + pass def get_upload_to(instance, filename): diff --git a/wagtail/wagtailimages/tests/test_models.py b/wagtail/wagtailimages/tests/test_models.py index 018ac5159e..196e123e9c 100644 --- a/wagtail/wagtailimages/tests/test_models.py +++ b/wagtail/wagtailimages/tests/test_models.py @@ -99,6 +99,38 @@ class TestImageQuerySet(TestCase): results = Image.objects.search("Test") self.assertEqual(list(results), [image]) + def test_operators(self): + aaa_image = Image.objects.create( + title="AAA Test image", + file=get_test_image_file(), + ) + zzz_image = Image.objects.create( + title="ZZZ Test image", + file=get_test_image_file(), + ) + + results = Image.objects.search("aaa test", operator='and') + self.assertEqual(list(results), [aaa_image]) + + results = Image.objects.search("aaa test", operator='or') + sorted_results = sorted(results, key=lambda img: img.title) + self.assertEqual(sorted_results, [aaa_image, zzz_image]) + + def test_custom_ordering(self): + aaa_image = Image.objects.create( + title="AAA Test image", + file=get_test_image_file(), + ) + zzz_image = Image.objects.create( + title="ZZZ Test image", + file=get_test_image_file(), + ) + + results = Image.objects.order_by('title').search("Test") + self.assertEqual(list(results), [aaa_image, zzz_image]) + results = Image.objects.order_by('-title').search("Test") + self.assertEqual(list(results), [zzz_image, aaa_image]) + class TestImagePermissions(TestCase): def setUp(self): diff --git a/wagtail/wagtailsearch/backends/base.py b/wagtail/wagtailsearch/backends/base.py index abf7e56099..29c36b809e 100644 --- a/wagtail/wagtailsearch/backends/base.py +++ b/wagtail/wagtailsearch/backends/base.py @@ -18,11 +18,12 @@ class FieldError(Exception): class BaseSearchQuery(object): DEFAULT_OPERATOR = 'or' - def __init__(self, queryset, query_string, fields=None, operator=None): + def __init__(self, queryset, query_string, fields=None, operator=None, order_by_relevance=True): self.queryset = queryset self.query_string = query_string self.fields = fields self.operator = operator or self.DEFAULT_OPERATOR + self.order_by_relevance = order_by_relevance def _get_searchable_field(self, field_attname): # Get field @@ -203,7 +204,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, operator=None): + def search(self, query_string, model_or_queryset, fields=None, filters=None, prefetch_related=None, operator=None, order_by_relevance=True): # Find model/queryset if isinstance(model_or_queryset, QuerySet): model = model_or_queryset.model @@ -236,5 +237,5 @@ class BaseSearch(object): raise ValueError("operator must be either 'or' or 'and'") # Search - search_query = self.search_query_class(queryset, query_string, fields=fields, operator=operator) + search_query = self.search_query_class(queryset, query_string, fields=fields, operator=operator, order_by_relevance=order_by_relevance) return self.search_results_class(self, search_query) diff --git a/wagtail/wagtailsearch/backends/elasticsearch.py b/wagtail/wagtailsearch/backends/elasticsearch.py index 03da5e2eea..b48b5f5f88 100644 --- a/wagtail/wagtailsearch/backends/elasticsearch.py +++ b/wagtail/wagtailsearch/backends/elasticsearch.py @@ -1,6 +1,8 @@ from __future__ import absolute_import import json +import six +import warnings from django.utils.six.moves.urllib.parse import urlparse @@ -11,6 +13,7 @@ from django.utils.crypto import get_random_string from wagtail.wagtailsearch.backends.base import BaseSearch, BaseSearchQuery, BaseSearchResults from wagtail.wagtailsearch.index import SearchField, FilterField, class_is_indexed +from wagtail.utils.deprecation import RemovedInWagtail14Warning INDEX_SETTINGS = { @@ -245,8 +248,7 @@ class ElasticSearchQuery(BaseSearchQuery): return filter_out - def to_es(self): - # Query + def get_inner_query(self): if self.query_string is not None: fields = self.fields or ['_all', '_partials'] @@ -274,7 +276,9 @@ class ElasticSearchQuery(BaseSearchQuery): 'match_all': {} } - # Filters + return query + + def get_filters(self): filters = [] # Filter by content type @@ -289,35 +293,106 @@ class ElasticSearchQuery(BaseSearchQuery): if queryset_filters: filters.append(queryset_filters) + return filters + + def get_query(self): + inner_query = self.get_inner_query() + filters = self.get_filters() + if len(filters) == 1: - query = { + return { 'filtered': { - 'query': query, + 'query': inner_query, 'filter': filters[0], } } elif len(filters) > 1: - query = { + return { 'filtered': { - 'query': query, + 'query': inner_query, 'filter': { 'and': filters, } } } + else: + return inner_query - return query + def get_sort(self): + # Ordering by relevance is the default in Elasticsearch + if self.order_by_relevance: + return + + # Get queryset and make sure its ordered + if self.queryset.ordered: + order_by_fields = self.queryset.query.order_by + sort = [] + + for order_by_field in order_by_fields: + reverse = False + field_name = order_by_field + + if order_by_field.startswith('-'): + reverse = True + field_name = order_by_field[1:] + + field = self._get_filterable_field(field_name) + field_index_name = field.get_index_name(self.queryset.model) + + sort.append({ + field_index_name: 'desc' if reverse else 'asc' + }) + + return sort + + else: + # Order by pk field + return ['pk'] def __repr__(self): - return json.dumps(self.to_es()) + return json.dumps(self.get_query()) + + def to_es(self): + warnings.warn( + "The ElasticSearchQuery.to_es() method is deprecated. " + "Please use the ElasticSearchQuery.get_query() method instead.", + RemovedInWagtail14Warning, stacklevel=2) + + return self.get_query() class ElasticSearchResults(BaseSearchResults): + def _get_es_body(self, for_count=False): + # If to_es has been overridden, call it and raise a deprecation warning + if isinstance(self.query, ElasticSearchQuery) and six.get_method_function(self.query.to_es) != ElasticSearchQuery.to_es: + warnings.warn( + "The .to_es() method on Elasticsearch query classes is deprecated. " + "Please rename {class_name}.to_es() to {class_name}.get_query()".format( + class_name=self.query.__class__.__name__ + ), + RemovedInWagtail14Warning, stacklevel=2) + + body = { + 'query': self.query.to_es(), + } + else: + body = { + 'query': self.query.get_query() + } + + if not for_count: + sort = self.query.get_sort() + + if sort is not None: + body['sort'] = sort + + return body + def _do_search(self): # Params for elasticsearch query params = dict( index=self.backend.es_index, - body=dict(query=self.query.to_es()), + body=self._get_es_body(), _source=False, fields='pk', from_=self.start, @@ -345,13 +420,10 @@ class ElasticSearchResults(BaseSearchResults): return [results[str(pk)] for pk in pks if results[str(pk)]] def _do_count(self): - # Get query - query = self.query.to_es() - # Get count hit_count = self.backend.es.count( index=self.backend.es_index, - body=dict(query=query), + body=self._get_es_body(for_count=True), )['count'] # Add limits diff --git a/wagtail/wagtailsearch/queryset.py b/wagtail/wagtailsearch/queryset.py new file mode 100644 index 0000000000..a12d37c718 --- /dev/null +++ b/wagtail/wagtailsearch/queryset.py @@ -0,0 +1,12 @@ +from wagtail.wagtailsearch.backends import get_search_backend + + +class SearchableQuerySetMixin(object): + def search(self, query_string, fields=None, + operator=None, order_by_relevance=True, backend='default'): + """ + This runs a search query on all the items in the QuerySet + """ + search_backend = get_search_backend(backend) + return search_backend.search(query_string, self, fields=fields, + operator=operator, order_by_relevance=order_by_relevance) diff --git a/wagtail/wagtailsearch/tests/test_elasticsearch_backend.py b/wagtail/wagtailsearch/tests/test_elasticsearch_backend.py index eddd7ad6f3..ee5f11ac28 100644 --- a/wagtail/wagtailsearch/tests/test_elasticsearch_backend.py +++ b/wagtail/wagtailsearch/tests/test_elasticsearch_backend.py @@ -167,6 +167,38 @@ class TestElasticSearchBackend(BackendTests, TestCase): # Should find the result self.assertEqual(len(results), 1) + def test_custom_ordering(self): + # Reset the index + self.backend.reset_index() + self.backend.add_type(models.SearchTest) + + # Add some test data + # a is more relevant, but b is more recent + a = models.SearchTest() + a.title = "Hello Hello World" + a.live = True + a.published_date = datetime.date(2015, 10, 11) + a.save() + self.backend.add(a) + + b = models.SearchTest() + b.title = "Hello World" + b.live = True + b.published_date = datetime.date(2015, 10, 12) + b.save() + self.backend.add(b) + + # Refresh the index + self.backend.refresh_index() + + # Do a search ordered by relevence + results = self.backend.search("Hello", models.SearchTest.objects.all()) + self.assertEqual(list(results), [a, b]) + + # Do a search ordered by published date + results = self.backend.search("Hello", models.SearchTest.objects.order_by('-published_date'), order_by_relevance=False) + self.assertEqual(list(results), [b, a]) + class TestElasticSearchQuery(TestCase): def assertDictEqual(self, a, b): @@ -191,7 +223,7 @@ class TestElasticSearchQuery(TestCase): # Check it expected_result = {'filtered': {'filter': {'prefix': {'content_type': 'searchtests_searchtest'}}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} - self.assertDictEqual(query.to_es(), expected_result) + self.assertDictEqual(query.get_query(), expected_result) def test_none_query_string(self): # Create a query @@ -199,7 +231,7 @@ class TestElasticSearchQuery(TestCase): # Check it expected_result = {'filtered': {'filter': {'prefix': {'content_type': 'searchtests_searchtest'}}, 'query': {'match_all': {}}}} - self.assertDictEqual(query.to_es(), expected_result) + self.assertDictEqual(query.get_query(), expected_result) def test_and_operator(self): # Create a query @@ -207,7 +239,7 @@ class TestElasticSearchQuery(TestCase): # 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) + self.assertDictEqual(query.get_query(), expected_result) def test_filter(self): # Create a query @@ -215,7 +247,7 @@ class TestElasticSearchQuery(TestCase): # Check it expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'term': {'title_filter': 'Test'}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} - self.assertDictEqual(query.to_es(), expected_result) + self.assertDictEqual(query.get_query(), expected_result) def test_and_filter(self): # Create a query @@ -225,7 +257,7 @@ class TestElasticSearchQuery(TestCase): expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'and': [{'term': {'live_filter': True}}, {'term': {'title_filter': 'Test'}}]}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} # Make sure field filters are sorted (as they can be in any order which may cause false positives) - query = query.to_es() + query = query.get_query() field_filters = query['filtered']['filter']['and'][1]['and'] field_filters[:] = sorted(field_filters, key=lambda f: list(f['term'].keys())[0]) @@ -236,7 +268,7 @@ class TestElasticSearchQuery(TestCase): query = self.ElasticSearchQuery(models.SearchTest.objects.filter(Q(title="Test") | Q(live=True)), "Hello") # Make sure field filters are sorted (as they can be in any order which may cause false positives) - query = query.to_es() + query = query.get_query() field_filters = query['filtered']['filter']['and'][1]['or'] field_filters[:] = sorted(field_filters, key=lambda f: list(f['term'].keys())[0]) @@ -250,7 +282,7 @@ class TestElasticSearchQuery(TestCase): # Check it expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'not': {'term': {'live_filter': True}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} - self.assertDictEqual(query.to_es(), expected_result) + self.assertDictEqual(query.get_query(), expected_result) def test_fields(self): # Create a query @@ -258,7 +290,7 @@ class TestElasticSearchQuery(TestCase): # Check it expected_result = {'filtered': {'filter': {'prefix': {'content_type': 'searchtests_searchtest'}}, 'query': {'match': {'title': 'Hello'}}}} - self.assertDictEqual(query.to_es(), expected_result) + self.assertDictEqual(query.get_query(), expected_result) def test_fields_with_and_operator(self): # Create a query @@ -266,7 +298,7 @@ class TestElasticSearchQuery(TestCase): # Check it expected_result = {'filtered': {'filter': {'prefix': {'content_type': 'searchtests_searchtest'}}, 'query': {'match': {'title': 'Hello', 'operator': 'and'}}}} - self.assertDictEqual(query.to_es(), expected_result) + self.assertDictEqual(query.get_query(), expected_result) def test_exact_lookup(self): # Create a query @@ -274,7 +306,7 @@ class TestElasticSearchQuery(TestCase): # Check it expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'term': {'title_filter': 'Test'}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} - self.assertDictEqual(query.to_es(), expected_result) + self.assertDictEqual(query.get_query(), expected_result) def test_none_lookup(self): # Create a query @@ -282,7 +314,7 @@ class TestElasticSearchQuery(TestCase): # Check it expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'missing': {'field': 'title_filter'}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} - self.assertDictEqual(query.to_es(), expected_result) + self.assertDictEqual(query.get_query(), expected_result) def test_isnull_true_lookup(self): # Create a query @@ -290,7 +322,7 @@ class TestElasticSearchQuery(TestCase): # Check it expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'missing': {'field': 'title_filter'}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} - self.assertDictEqual(query.to_es(), expected_result) + self.assertDictEqual(query.get_query(), expected_result) def test_isnull_false_lookup(self): # Create a query @@ -298,7 +330,7 @@ class TestElasticSearchQuery(TestCase): # Check it expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'not': {'missing': {'field': 'title_filter'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} - self.assertDictEqual(query.to_es(), expected_result) + self.assertDictEqual(query.get_query(), expected_result) def test_startswith_lookup(self): # Create a query @@ -306,7 +338,7 @@ class TestElasticSearchQuery(TestCase): # Check it expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'prefix': {'title_filter': 'Test'}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} - self.assertDictEqual(query.to_es(), expected_result) + self.assertDictEqual(query.get_query(), expected_result) def test_gt_lookup(self): # This also tests conversion of python dates to strings @@ -316,7 +348,7 @@ class TestElasticSearchQuery(TestCase): # Check it expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'range': {'published_date_filter': {'gt': '2014-04-29'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} - self.assertDictEqual(query.to_es(), expected_result) + self.assertDictEqual(query.get_query(), expected_result) def test_lt_lookup(self): # Create a query @@ -324,7 +356,7 @@ class TestElasticSearchQuery(TestCase): # Check it expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'range': {'published_date_filter': {'lt': '2014-04-29'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} - self.assertDictEqual(query.to_es(), expected_result) + self.assertDictEqual(query.get_query(), expected_result) def test_gte_lookup(self): # Create a query @@ -332,7 +364,7 @@ class TestElasticSearchQuery(TestCase): # Check it expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'range': {'published_date_filter': {'gte': '2014-04-29'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} - self.assertDictEqual(query.to_es(), expected_result) + self.assertDictEqual(query.get_query(), expected_result) def test_lte_lookup(self): # Create a query @@ -340,7 +372,7 @@ class TestElasticSearchQuery(TestCase): # Check it expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'range': {'published_date_filter': {'lte': '2014-04-29'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} - self.assertDictEqual(query.to_es(), expected_result) + self.assertDictEqual(query.get_query(), expected_result) def test_range_lookup(self): start_date = datetime.datetime(2014, 4, 29) @@ -351,7 +383,31 @@ class TestElasticSearchQuery(TestCase): # Check it expected_result = {'filtered': {'filter': {'and': [{'prefix': {'content_type': 'searchtests_searchtest'}}, {'range': {'published_date_filter': {'gte': '2014-04-29', 'lte': '2014-08-19'}}}]}, 'query': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} - self.assertDictEqual(query.to_es(), expected_result) + self.assertDictEqual(query.get_query(), expected_result) + + def test_custom_ordering(self): + # Create a query + query = self.ElasticSearchQuery(models.SearchTest.objects.order_by('published_date'), "Hello", order_by_relevance=False) + + # Check it + expected_result = [{'published_date_filter': 'asc'}] + self.assertDictEqual(query.get_sort(), expected_result) + + def test_custom_ordering_reversed(self): + # Create a query + query = self.ElasticSearchQuery(models.SearchTest.objects.order_by('-published_date'), "Hello", order_by_relevance=False) + + # Check it + expected_result = [{'published_date_filter': 'desc'}] + self.assertDictEqual(query.get_sort(), expected_result) + + def test_custom_ordering_multiple(self): + # Create a query + query = self.ElasticSearchQuery(models.SearchTest.objects.order_by('published_date', 'live'), "Hello", order_by_relevance=False) + + # Check it + expected_result = [{'published_date_filter': 'asc'}, {'live_filter': 'asc'}] + self.assertDictEqual(query.get_sort(), expected_result) class TestElasticSearchResults(TestCase): @@ -382,7 +438,8 @@ class TestElasticSearchResults(TestCase): backend = self.ElasticSearch({}) query = mock.MagicMock() query.queryset = models.SearchTest.objects.all() - query.to_es.return_value = 'QUERY' + query.get_query.return_value = 'QUERY' + query.get_sort.return_value = None return self.ElasticSearchResults(backend, query) def construct_search_response(self, results):