From f02b7f1329012aafca3851df3340b719c28d4586 Mon Sep 17 00:00:00 2001 From: Ryan P Kilby Date: Mon, 10 Jul 2017 14:20:23 -0400 Subject: [PATCH 1/2] Add failing test for #4655 --- tests/test_filters.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/test_filters.py b/tests/test_filters.py index b2de80998..f803d0957 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -645,6 +645,48 @@ class SearchFilterM2MTests(TestCase): ) +class Blog(models.Model): + name = models.CharField(max_length=20) + + +class Entry(models.Model): + blog = models.ForeignKey(Blog, on_delete=models.CASCADE) + headline = models.CharField(max_length=120) + pub_date = models.DateField(null=True) + + +class BlogSerializer(serializers.ModelSerializer): + class Meta: + model = Blog + fields = '__all__' + + +class SearchFilterToManyTests(TestCase): + + @classmethod + def setUpTestData(cls): + b1 = Blog.objects.create(name='Blog 1') + b2 = Blog.objects.create(name='Blog 2') + + Entry.objects.create(blog=b1, headline='Something about Lennon', pub_date=datetime.date(1979, 1, 1)) + Entry.objects.create(blog=b1, headline='Another thing about Lennon', pub_date=datetime.date(1979, 6, 1)) + + Entry.objects.create(blog=b2, headline='Something unrelated', pub_date=datetime.date(1979, 1, 1)) + Entry.objects.create(blog=b2, headline='Retrospective on Lennon', pub_date=datetime.date(1990, 6, 1)) + + def test_multiple_filter_conditions(self): + class SearchListView(generics.ListAPIView): + queryset = Blog.objects.all() + serializer_class = BlogSerializer + filter_backends = (filters.SearchFilter,) + search_fields = ('=name', 'entry__headline', '=entry__pub_date__year') + + view = SearchListView.as_view() + request = factory.get('/', {'search': 'Lennon,1979'}) + response = view(request) + assert len(response.data) == 1 + + class OrderingFilterModel(models.Model): title = models.CharField(max_length=20, verbose_name='verbose title') text = models.CharField(max_length=100) From d1cfec8d871a473f4c1e4b45ead959b07d813988 Mon Sep 17 00:00:00 2001 From: Ryan P Kilby Date: Mon, 10 Jul 2017 14:24:58 -0400 Subject: [PATCH 2/2] Fix SearchFilter to-many behavior by ANDing cond's --- rest_framework/filters.py | 4 +++- tests/test_filters.py | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index bdab97b58..63ebf05ef 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -140,12 +140,14 @@ class SearchFilter(BaseFilterBackend): ] base = queryset + conditions = [] for search_term in search_terms: queries = [ models.Q(**{orm_lookup: search_term}) for orm_lookup in orm_lookups ] - queryset = queryset.filter(reduce(operator.or_, queries)) + conditions.append(reduce(operator.or_, queries)) + queryset = queryset.filter(reduce(operator.and_, conditions)) if self.must_call_distinct(queryset, search_fields): # Filtering against a many-to-many field requires us to diff --git a/tests/test_filters.py b/tests/test_filters.py index f803d0957..6df0a3169 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -5,6 +5,7 @@ import unittest import warnings from decimal import Decimal +import django import pytest from django.conf.urls import url from django.core.exceptions import ImproperlyConfigured @@ -668,12 +669,15 @@ class SearchFilterToManyTests(TestCase): b1 = Blog.objects.create(name='Blog 1') b2 = Blog.objects.create(name='Blog 2') + # Multiple entries on Lennon published in 1979 - distinct should deduplicate Entry.objects.create(blog=b1, headline='Something about Lennon', pub_date=datetime.date(1979, 1, 1)) Entry.objects.create(blog=b1, headline='Another thing about Lennon', pub_date=datetime.date(1979, 6, 1)) + # Entry on Lennon *and* a separate entry in 1979 - should not match Entry.objects.create(blog=b2, headline='Something unrelated', pub_date=datetime.date(1979, 1, 1)) Entry.objects.create(blog=b2, headline='Retrospective on Lennon', pub_date=datetime.date(1990, 6, 1)) + @unittest.skipIf(django.VERSION < (1, 9), "Django 1.8 does not support transforms") def test_multiple_filter_conditions(self): class SearchListView(generics.ListAPIView): queryset = Blog.objects.all()