From c5a2a13040ed06c2e5b8171a946851cf29adb65d Mon Sep 17 00:00:00 2001 From: Andi Albrecht Date: Fri, 22 Jul 2016 11:55:17 +0200 Subject: [PATCH] Restore meta information for each search field. The meta information stored in opts needs to be restored for each search field. Otherwise it references the wrong model when an attribute of a related model comes before an attribute of the original model in search fields. This doesn't apply to m2m relations since must_call_distinct returns True in that case. --- rest_framework/filters.py | 2 +- tests/test_filters.py | 41 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index caff1c17f..aaf313491 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -174,8 +174,8 @@ class SearchFilter(BaseFilterBackend): """ Return True if 'distinct()' should be used to query the given lookups. """ - opts = queryset.model._meta for search_field in search_fields: + opts = queryset.model._meta if search_field[0] in self.lookup_prefixes: search_field = search_field[1:] parts = search_field.split(LOOKUP_SEP) diff --git a/tests/test_filters.py b/tests/test_filters.py index 8bffd215c..175ae5b12 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -454,6 +454,47 @@ class AttributeModel(models.Model): label = models.CharField(max_length=32) +class SearchFilterModelFk(models.Model): + title = models.CharField(max_length=20) + attribute = models.ForeignKey(AttributeModel) + + +class SearchFilterFkSerializer(serializers.ModelSerializer): + class Meta: + model = SearchFilterModelFk + fields = '__all__' + + +class SearchFilterFkTests(TestCase): + + def test_must_call_distinct(self): + filter_ = filters.SearchFilter() + prefixes = [''] + list(filter_.lookup_prefixes) + for prefix in prefixes: + self.assertFalse( + filter_.must_call_distinct( + SearchFilterModelFk._meta, ["%stitle" % prefix] + ) + ) + self.assertFalse( + filter_.must_call_distinct( + SearchFilterModelFk._meta, ["%stitle" % prefix, "%sattribute__label" % prefix] + ) + ) + + def test_must_call_distinct_restores_meta_for_each_field(self): + # In this test case the attribute of the fk model comes first in the + # list of search fields. + filter_ = filters.SearchFilter() + prefixes = [''] + list(filter_.lookup_prefixes) + for prefix in prefixes: + self.assertFalse( + filter_.must_call_distinct( + SearchFilterModelFk._meta, ["%sattribute__label" % prefix, "%stitle" % prefix] + ) + ) + + class SearchFilterModelM2M(models.Model): title = models.CharField(max_length=20) text = models.CharField(max_length=100)