From 98df932194722d6fc81becedc55eb695a32f925f Mon Sep 17 00:00:00 2001 From: Kieran Spear Date: Tue, 1 Nov 2016 18:30:17 +0800 Subject: [PATCH] Fix FilterSet proxy (#4620) --- rest_framework/filters.py | 35 ++++++++++++++++++++++++++--------- tests/test_filters.py | 23 +++++++++++++++++++++++ 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index 47d9a0342..531531efc 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -37,15 +37,32 @@ class BaseFilterBackend(object): return [] -class FilterSet(object): - def __new__(cls, *args, **kwargs): - warnings.warn( - "The built in 'rest_framework.filters.FilterSet' is pending deprecation. " - "You should use 'django_filters.rest_framework.FilterSet' instead.", - PendingDeprecationWarning - ) - from django_filters.rest_framework import FilterSet - return FilterSet(*args, **kwargs) +if django_filters: + from django_filters.filterset import FilterSetMetaclass as DFFilterSetMetaclass + from django_filters.rest_framework.filterset import FilterSet as DFFilterSet + + class FilterSetMetaclass(DFFilterSetMetaclass): + def __new__(cls, name, bases, attrs): + warnings.warn( + "The built in 'rest_framework.filters.FilterSet' is pending deprecation. " + "You should use 'django_filters.rest_framework.FilterSet' instead.", + PendingDeprecationWarning + ) + return super(FilterSetMetaclass, cls).__new__(cls, name, bases, attrs) + _BaseFilterSet = DFFilterSet +else: + # Dummy metaclass just so we can give a user-friendly error message. + class FilterSetMetaclass(type): + def __init__(self, name, bases, attrs): + # Assert only on subclasses, so we can define FilterSet below. + if bases != (object,): + assert False, 'django-filter must be installed to use the `FilterSet` class' + super(FilterSetMetaclass, self).__init__(name, bases, attrs) + _BaseFilterSet = object + + +class FilterSet(six.with_metaclass(FilterSetMetaclass, _BaseFilterSet)): + pass class DjangoFilterBackend(BaseFilterBackend): diff --git a/tests/test_filters.py b/tests/test_filters.py index 9795230d6..12fb85895 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -79,12 +79,23 @@ if django_filters: model = BaseFilterableItem fields = '__all__' + # Test the same filter using the deprecated internal FilterSet class. + class BaseFilterableItemFilterWithProxy(filters.FilterSet): + text = django_filters.CharFilter() + + class Meta: + model = BaseFilterableItem + fields = '__all__' + class BaseFilterableItemFilterRootView(generics.ListCreateAPIView): queryset = FilterableItem.objects.all() serializer_class = FilterableItemSerializer filter_class = BaseFilterableItemFilter filter_backends = (filters.DjangoFilterBackend,) + class BaseFilterableItemFilterWithProxyRootView(BaseFilterableItemFilterRootView): + filter_class = BaseFilterableItemFilterWithProxy + # Regression test for #814 class FilterFieldsQuerysetView(generics.ListCreateAPIView): queryset = FilterableItem.objects.all() @@ -296,6 +307,18 @@ class IntegrationTestFiltering(CommonFilteringTestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) + @unittest.skipUnless(django_filters, 'django-filter not installed') + def test_base_model_filter_with_proxy(self): + """ + The `get_filter_class` model checks should allow base model filters. + """ + view = BaseFilterableItemFilterWithProxyRootView.as_view() + + request = factory.get('/?text=aaa') + response = view(request).render() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data), 1) + @unittest.skipUnless(django_filters, 'django-filter not installed') def test_unknown_filter(self): """