From d87bb67d11918683425af1c1d56c0c57f50e81b3 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 10 Feb 2015 10:50:35 +0100 Subject: [PATCH 1/2] Failing test case for #1488 --- tests/test_filters.py | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/test_filters.py b/tests/test_filters.py index 355f02cef..e7cb0c795 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -429,6 +429,56 @@ class SearchFilterTests(TestCase): reload_module(filters) +class AttributeModel(models.Model): + label = models.CharField(max_length=32) + + +class SearchFilterModelM2M(models.Model): + title = models.CharField(max_length=20) + text = models.CharField(max_length=100) + attributes = models.ManyToManyField(AttributeModel) + + +class SearchFilterM2MSerializer(serializers.ModelSerializer): + class Meta: + model = SearchFilterModelM2M + + +class SearchFilterM2MTests(TestCase): + def setUp(self): + # Sequence of title/text/attributes is: + # + # z abc [1, 2, 3] + # zz bcd [1, 2, 3] + # zzz cde [1, 2, 3] + # ... + for idx in range(3): + label = 'w' * (idx + 1) + AttributeModel(label=label) + + for idx in range(10): + title = 'z' * (idx + 1) + text = ( + chr(idx + ord('a')) + + chr(idx + ord('b')) + + chr(idx + ord('c')) + ) + SearchFilterModelM2M(title=title, text=text).save() + SearchFilterModelM2M.objects.get(title='zz').attributes.add(1, 2, 3) + + def test_m2m_search(self): + class SearchListView(generics.ListAPIView): + queryset = SearchFilterModelM2M.objects.all() + serializer_class = SearchFilterM2MSerializer + filter_backends = (filters.SearchFilter,) + search_fields = ('=title', 'text', 'attributes__label') + + view = SearchListView.as_view() + request = factory.get('/', {'search': 'zz'}) + response = view(request) + self.assertEqual(len(response.data), 1) + + class OrderingFilterModel(models.Model): title = models.CharField(max_length=20) text = models.CharField(max_length=100) From 3522b69394d932c8bf8028a456b6d9b64c38b54e Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 10 Feb 2015 10:51:38 +0100 Subject: [PATCH 2/2] Add `distinct` call in `filter_queryset` --- rest_framework/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index d188a2d1e..d3f55a447 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -104,7 +104,7 @@ class SearchFilter(BaseFilterBackend): for search_term in self.get_search_terms(request): or_queries = [models.Q(**{orm_lookup: search_term}) for orm_lookup in orm_lookups] - queryset = queryset.filter(reduce(operator.or_, or_queries)) + queryset = queryset.filter(reduce(operator.or_, or_queries)).distinct() return queryset