diff --git a/rest_framework/filters.py b/rest_framework/filters.py index 53d49ae45..7989ace34 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -85,6 +85,9 @@ class SearchFilter(BaseFilterBackend): opts = queryset.model._meta if search_field[0] in self.lookup_prefixes: search_field = search_field[1:] + # Annotated fields do not need to be distinct + if isinstance(queryset, models.QuerySet) and search_field in queryset.query.annotations: + return False parts = search_field.split(LOOKUP_SEP) for part in parts: field = opts.get_field(part) diff --git a/tests/test_filters.py b/tests/test_filters.py index 39a96f994..088d25436 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -5,6 +5,7 @@ import datetime import pytest from django.core.exceptions import ImproperlyConfigured from django.db import models +from django.db.models.functions import Concat, Upper from django.test import TestCase from django.test.utils import override_settings from django.utils.six.moves import reload_module @@ -329,6 +330,38 @@ class SearchFilterToManyTests(TestCase): assert len(response.data) == 1 +class SearchFilterAnnotatedSerializer(serializers.ModelSerializer): + title_text = serializers.CharField() + + class Meta: + model = SearchFilterModel + fields = ('title', 'text', 'title_text') + + +class SearchFilterAnnotatedFieldTests(TestCase): + @classmethod + def setUpTestData(cls): + SearchFilterModel.objects.create(title='abc', text='def') + SearchFilterModel.objects.create(title='ghi', text='jkl') + + def test_search_in_annotated_field(self): + class SearchListView(generics.ListAPIView): + queryset = SearchFilterModel.objects.annotate( + title_text=Upper( + Concat(models.F('title'), models.F('text')) + ) + ).all() + serializer_class = SearchFilterAnnotatedSerializer + filter_backends = (filters.SearchFilter,) + search_fields = ('title_text',) + + view = SearchListView.as_view() + request = factory.get('/', {'search': 'ABCDEF'}) + response = view(request) + assert len(response.data) == 1 + assert response.data[0]['title_text'] == 'ABCDEF' + + class OrderingFilterModel(models.Model): title = models.CharField(max_length=20, verbose_name='verbose title') text = models.CharField(max_length=100)