From 3baf92cf8230ad3a932986170fd07c8feae7ff2f Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Wed, 4 Mar 2020 13:33:12 +0100 Subject: [PATCH] Fixed #31340 -- Allowed query expressions in SearchQuery.value and __search lookup. --- django/contrib/postgres/search.py | 5 +++-- docs/ref/contrib/postgres/search.txt | 7 ++++++- docs/releases/3.1.txt | 5 +++++ .../migrations/0002_create_test_models.py | 11 +++++++++++ tests/postgres_tests/models.py | 5 +++++ tests/postgres_tests/test_search.py | 14 +++++++++++++- 6 files changed, 43 insertions(+), 4 deletions(-) diff --git a/django/contrib/postgres/search.py b/django/contrib/postgres/search.py index 90b6823575..2b2ae0c321 100644 --- a/django/contrib/postgres/search.py +++ b/django/contrib/postgres/search.py @@ -11,7 +11,7 @@ class SearchVectorExact(Lookup): lookup_name = 'exact' def process_rhs(self, qn, connection): - if not hasattr(self.rhs, 'resolve_expression'): + if not isinstance(self.rhs, (SearchQuery, CombinedSearchQuery)): config = getattr(self.lhs, 'config', None) self.rhs = SearchQuery(self.rhs, config=config) rhs, rhs_params = super().process_rhs(qn, connection) @@ -170,7 +170,8 @@ class SearchQuery(SearchQueryCombinable, Func): self.function = self.SEARCH_TYPES.get(search_type) if self.function is None: raise ValueError("Unknown search_type argument '%s'." % search_type) - value = Value(value) + if not hasattr(value, 'resolve_expression'): + value = Value(value) expressions = (value,) self.config = SearchConfig.from_parameter(config) if self.config is not None: diff --git a/docs/ref/contrib/postgres/search.txt b/docs/ref/contrib/postgres/search.txt index 949d95929e..65d54cfd8d 100644 --- a/docs/ref/contrib/postgres/search.txt +++ b/docs/ref/contrib/postgres/search.txt @@ -35,6 +35,10 @@ query and the vector. To use the ``search`` lookup, ``'django.contrib.postgres'`` must be in your :setting:`INSTALLED_APPS`. +.. versionchanged:: 3.1 + + Support for query expressions was added. + ``SearchVector`` ================ @@ -108,7 +112,8 @@ See :ref:`postgresql-fts-search-configuration` for an explanation of the .. versionchanged:: 3.1 - Support for ``'websearch'`` search type was added. + Support for ``'websearch'`` search type and query expressions in + ``SearchQuery.value`` were added. ``SearchRank`` ============== diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt index 278752db90..de66d7805f 100644 --- a/docs/releases/3.1.txt +++ b/docs/releases/3.1.txt @@ -113,9 +113,14 @@ Minor features * :class:`~django.contrib.postgres.search.SearchQuery` now supports ``'websearch'`` search type on PostgreSQL 11+. +* :class:`SearchQuery.value ` now + supports query expressions. + * The new :class:`~django.contrib.postgres.search.SearchHeadline` class allows highlighting search results. +* :lookup:`search` lookup now supports query expressions. + :mod:`django.contrib.redirects` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/postgres_tests/migrations/0002_create_test_models.py b/tests/postgres_tests/migrations/0002_create_test_models.py index 12d94e348a..ee1463e1eb 100644 --- a/tests/postgres_tests/migrations/0002_create_test_models.py +++ b/tests/postgres_tests/migrations/0002_create_test_models.py @@ -185,6 +185,17 @@ class Migration(migrations.Migration): }, bases=None, ), + migrations.CreateModel( + name='LineSavedSearch', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('line', models.ForeignKey('postgres_tests.Line', on_delete=models.CASCADE)), + ('query', models.CharField(max_length=100)), + ], + options={ + 'required_db_vendor': 'postgresql', + }, + ), migrations.CreateModel( name='AggregateTestModel', fields=[ diff --git a/tests/postgres_tests/models.py b/tests/postgres_tests/models.py index 8528c59da1..e803c989e0 100644 --- a/tests/postgres_tests/models.py +++ b/tests/postgres_tests/models.py @@ -139,6 +139,11 @@ class Line(PostgreSQLModel): return self.dialogue or '' +class LineSavedSearch(PostgreSQLModel): + line = models.ForeignKey('Line', models.CASCADE) + query = models.CharField(max_length=100) + + class RangesModel(PostgreSQLModel): ints = IntegerRangeField(blank=True, null=True) bigints = BigIntegerRangeField(blank=True, null=True) diff --git a/tests/postgres_tests/test_search.py b/tests/postgres_tests/test_search.py index 0e836e896b..b40d672920 100644 --- a/tests/postgres_tests/test_search.py +++ b/tests/postgres_tests/test_search.py @@ -10,7 +10,7 @@ from django.db.models import F from django.test import modify_settings, skipUnlessDBFeature from . import PostgreSQLSimpleTestCase, PostgreSQLTestCase -from .models import Character, Line, Scene +from .models import Character, Line, LineSavedSearch, Scene try: from django.contrib.postgres.search import ( @@ -110,6 +110,18 @@ class SimpleSearchTest(GrailTestData, PostgreSQLTestCase): ) self.assertSequenceEqual(searched, [self.verse2]) + def test_search_with_F_expression(self): + # Non-matching query. + LineSavedSearch.objects.create(line=self.verse1, query='hearts') + # Matching query. + match = LineSavedSearch.objects.create(line=self.verse1, query='elbows') + for query_expression in [F('query'), SearchQuery(F('query'))]: + with self.subTest(query_expression): + searched = LineSavedSearch.objects.filter( + line__dialogue__search=query_expression, + ) + self.assertSequenceEqual(searched, [match]) + @modify_settings(INSTALLED_APPS={'append': 'django.contrib.postgres'}) class SearchVectorFieldTest(GrailTestData, PostgreSQLTestCase):