diff --git a/wagtail/search/backends/base.py b/wagtail/search/backends/base.py index ae27ab7fd8..1c5ca6622f 100644 --- a/wagtail/search/backends/base.py +++ b/wagtail/search/backends/base.py @@ -1,5 +1,7 @@ from warnings import warn +from django.db.models.functions.datetime import Extract as ExtractDate +from django.db.models.functions.datetime import ExtractYear from django.db.models.lookups import Lookup from django.db.models.query import QuerySet from django.db.models.sql.where import SubqueryConstraint, WhereNode @@ -88,7 +90,16 @@ class BaseSearchQueryCompiler: def _get_filters_from_where_node(self, where_node, check_only=False): # Check if this is a leaf node if isinstance(where_node, Lookup): - field_attname = where_node.lhs.target.attname + if isinstance(where_node.lhs, ExtractDate): + if isinstance(where_node.lhs, ExtractYear): + field_attname = where_node.lhs.lhs.target.attname + else: + raise FilterError( + 'Cannot apply filter on search results: "' + where_node.lhs.lookup_name + + '" queries are not supported.' + ) + else: + field_attname = where_node.lhs.target.attname lookup = where_node.lookup_name value = where_node.rhs diff --git a/wagtail/search/tests/elasticsearch_common_tests.py b/wagtail/search/tests/elasticsearch_common_tests.py index e3196ce67d..e28c70b3f5 100644 --- a/wagtail/search/tests/elasticsearch_common_tests.py +++ b/wagtail/search/tests/elasticsearch_common_tests.py @@ -176,6 +176,19 @@ class ElasticsearchCommonSearchBackendTests(BackendTests): results = self.backend.search(MATCH_ALL, models.Book)[110:] self.assertEqual(len(results), 54) + def test_search_with_date_filter(self): + after_1900 = models.Book.objects.filter(publication_date__year__gt=1900) + + results = self.backend.search(MATCH_ALL, after_1900) + self.assertEqual(len(after_1900), len(results)) + + # Filtering by date not supported, should throw a FilterError + from wagtail.search.backends.base import FilterError + + in_jan = models.Book.objects.filter(publication_date__month=1) + with self.assertRaises(FilterError): + self.backend.search(MATCH_ALL, in_jan) + # Elasticsearch always does prefix matching on `partial_match` fields, # even when we don’t use `Prefix`. @unittest.expectedFailure diff --git a/wagtail/search/tests/test_elasticsearch5_backend.py b/wagtail/search/tests/test_elasticsearch5_backend.py index 253bb298f4..68dfab18a7 100644 --- a/wagtail/search/tests/test_elasticsearch5_backend.py +++ b/wagtail/search/tests/test_elasticsearch5_backend.py @@ -333,6 +333,17 @@ class TestElasticsearch5SearchQuery(TestCase): expected_result = {'match_phrase': {'title': "Hello world"}} self.assertDictEqual(query_compiler.get_inner_query(), expected_result) + def test_year_filter(self): + # Create a query + query_compiler = self.query_compiler_class(models.Book.objects.filter(publication_date__year__gt=1900), "Hello") + + # Check it + expected_result = {'bool': {'filter': [ + {'match': {'content_type': 'searchtests.Book'}}, + {'range': {'publication_date_filter': {'gt': 1900}}} + ], 'must': {'multi_match': {'query': 'Hello', 'fields': ['_all', '_partials']}}}} + self.assertDictEqual(query_compiler.get_query(), expected_result) + class TestElasticsearch5SearchResults(TestCase): fixtures = ['search'] diff --git a/wagtail/search/tests/test_elasticsearch6_backend.py b/wagtail/search/tests/test_elasticsearch6_backend.py index 9b252041c4..f91325134e 100644 --- a/wagtail/search/tests/test_elasticsearch6_backend.py +++ b/wagtail/search/tests/test_elasticsearch6_backend.py @@ -333,6 +333,17 @@ class TestElasticsearch6SearchQuery(TestCase): expected_result = {'match_phrase': {'title': "Hello world"}} self.assertDictEqual(query_compiler.get_inner_query(), expected_result) + def test_year_filter(self): + # Create a query + query_compiler = self.query_compiler_class(models.Book.objects.filter(publication_date__year=1900), "Hello") + + # Check it + expected_result = {'bool': {'filter': [ + {'match': {'content_type': 'searchtests.Book'}}, + {'term': {'publication_date_filter': 1900}} + ], 'must': {'multi_match': {'query': 'Hello', 'fields': ['_all_text', '_edgengrams']}}}} + self.assertDictEqual(query_compiler.get_query(), expected_result) + class TestElasticsearch6SearchResults(TestCase): fixtures = ['search'] diff --git a/wagtail/search/tests/test_elasticsearch7_backend.py b/wagtail/search/tests/test_elasticsearch7_backend.py index 54e1c2f9c6..129ff43df3 100644 --- a/wagtail/search/tests/test_elasticsearch7_backend.py +++ b/wagtail/search/tests/test_elasticsearch7_backend.py @@ -333,6 +333,17 @@ class TestElasticsearch7SearchQuery(TestCase): expected_result = {'match_phrase': {'title': "Hello world"}} self.assertDictEqual(query_compiler.get_inner_query(), expected_result) + def test_year_filter(self): + # Create a query + query_compiler = self.query_compiler_class(models.Book.objects.filter(publication_date__year__lt=1900), "Hello") + + # Check it + expected_result = {'bool': {'filter': [ + {'match': {'content_type': 'searchtests.Book'}}, + {'range': {'publication_date_filter': {'lt': 1900}}} + ], 'must': {'multi_match': {'query': 'Hello', 'fields': ['_all_text', '_edgengrams']}}}} + self.assertDictEqual(query_compiler.get_query(), expected_result) + class TestElasticsearch7SearchResults(TestCase): fixtures = ['search']