Remove DjangoFilterBackend and associated tests

This commit is contained in:
Ryan P Kilby 2017-07-14 12:05:42 -04:00 committed by Carlton Gibson
parent cb6e7e0fdd
commit f6c19e5eac
4 changed files with 2 additions and 479 deletions

View File

@ -182,13 +182,6 @@ except ImportError:
coreschema = None coreschema = None
# django-filter is optional
try:
import django_filters
except ImportError:
django_filters = None
# django-crispy-forms is optional # django-crispy-forms is optional
try: try:
import crispy_forms import crispy_forms

View File

@ -5,7 +5,6 @@ returned by list views.
from __future__ import unicode_literals from __future__ import unicode_literals
import operator import operator
import warnings
from functools import reduce from functools import reduce
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
@ -18,7 +17,7 @@ from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework.compat import ( from rest_framework.compat import (
coreapi, coreschema, distinct, django_filters, guardian, template_render coreapi, coreschema, distinct, guardian, template_render
) )
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
@ -40,44 +39,6 @@ class BaseFilterBackend(object):
return [] return []
if django_filters:
from django_filters.rest_framework.filterset import FilterSet as DFFilterSet
class FilterSet(DFFilterSet):
def __init__(self, *args, **kwargs):
warnings.warn(
"The built in 'rest_framework.filters.FilterSet' is deprecated. "
"You should use 'django_filters.rest_framework.FilterSet' instead.",
DeprecationWarning, stacklevel=2
)
return super(FilterSet, self).__init__(*args, **kwargs)
DFBase = django_filters.rest_framework.DjangoFilterBackend
else:
def FilterSet():
assert False, 'django-filter must be installed to use the `FilterSet` class'
DFBase = BaseFilterBackend
class DjangoFilterBackend(DFBase):
"""
A filter backend that uses django-filter.
"""
def __new__(cls, *args, **kwargs):
assert django_filters, 'Using DjangoFilterBackend, but django-filter is not installed'
assert django_filters.VERSION >= (0, 15, 3), 'django-filter 0.15.3 and above is required'
warnings.warn(
"The built in 'rest_framework.filters.DjangoFilterBackend' is deprecated. "
"You should use 'django_filters.rest_framework.DjangoFilterBackend' instead.",
DeprecationWarning, stacklevel=2
)
return super(DjangoFilterBackend, cls).__new__(cls, *args, **kwargs)
class SearchFilter(BaseFilterBackend): class SearchFilter(BaseFilterBackend):
# The URL query parameter used for the search. # The URL query parameter used for the search.
search_param = api_settings.SEARCH_PARAM search_param = api_settings.SEARCH_PARAM

View File

@ -24,15 +24,6 @@ class BasicModel(RESTFrameworkModel):
) )
class BaseFilterableItem(RESTFrameworkModel):
text = models.CharField(max_length=100)
class FilterableItem(BaseFilterableItem):
decimal = models.DecimalField(max_digits=4, decimal_places=2)
date = models.DateField()
# Models for relations tests # Models for relations tests
# ManyToMany # ManyToMany
class ManyToManyTarget(RESTFrameworkModel): class ManyToManyTarget(RESTFrameworkModel):

View File

@ -2,125 +2,21 @@ from __future__ import unicode_literals
import datetime import datetime
import unittest import unittest
import warnings
from decimal import Decimal
import django import django
import pytest import pytest
from django.conf.urls import url
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.db import models from django.db import models
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils.dateparse import parse_date
from django.utils.six.moves import reload_module from django.utils.six.moves import reload_module
from rest_framework import filters, generics, serializers, status from rest_framework import filters, generics, serializers
from rest_framework.compat import django_filters, reverse
from rest_framework.test import APIRequestFactory from rest_framework.test import APIRequestFactory
from .models import BaseFilterableItem, BasicModel, FilterableItem
factory = APIRequestFactory() factory = APIRequestFactory()
if django_filters:
class FilterableItemSerializer(serializers.ModelSerializer):
class Meta:
model = FilterableItem
fields = '__all__'
# Basic filter on a list view.
class FilterFieldsRootView(generics.ListCreateAPIView):
queryset = FilterableItem.objects.all()
serializer_class = FilterableItemSerializer
filter_fields = ['decimal', 'date']
filter_backends = (filters.DjangoFilterBackend,)
# These class are used to test a filter class.
class SeveralFieldsFilter(django_filters.FilterSet):
text = django_filters.CharFilter(lookup_expr='icontains')
decimal = django_filters.NumberFilter(lookup_expr='lt')
date = django_filters.DateFilter(lookup_expr='gt')
class Meta:
model = FilterableItem
fields = ['text', 'decimal', 'date']
class FilterClassRootView(generics.ListCreateAPIView):
queryset = FilterableItem.objects.all()
serializer_class = FilterableItemSerializer
filter_class = SeveralFieldsFilter
filter_backends = (filters.DjangoFilterBackend,)
# These classes are used to test a misconfigured filter class.
class MisconfiguredFilter(django_filters.FilterSet):
text = django_filters.CharFilter(lookup_expr='icontains')
class Meta:
model = BasicModel
fields = ['text']
class IncorrectlyConfiguredRootView(generics.ListCreateAPIView):
queryset = FilterableItem.objects.all()
serializer_class = FilterableItemSerializer
filter_class = MisconfiguredFilter
filter_backends = (filters.DjangoFilterBackend,)
class FilterClassDetailView(generics.RetrieveAPIView):
queryset = FilterableItem.objects.all()
serializer_class = FilterableItemSerializer
filter_class = SeveralFieldsFilter
filter_backends = (filters.DjangoFilterBackend,)
# These classes are used to test base model filter support
class BaseFilterableItemFilter(django_filters.FilterSet):
text = django_filters.CharFilter()
class Meta:
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()
serializer_class = FilterableItemSerializer
filter_fields = ['decimal', 'date']
filter_backends = (filters.DjangoFilterBackend,)
class GetQuerysetView(generics.ListCreateAPIView):
serializer_class = FilterableItemSerializer
filter_class = SeveralFieldsFilter
filter_backends = (filters.DjangoFilterBackend,)
def get_queryset(self):
return FilterableItem.objects.all()
urlpatterns = [
url(r'^(?P<pk>\d+)/$', FilterClassDetailView.as_view(), name='detail-view'),
url(r'^$', FilterClassRootView.as_view(), name='root-view'),
url(r'^get-queryset/$', GetQuerysetView.as_view(),
name='get-queryset-view'),
]
class BaseFilterTests(TestCase): class BaseFilterTests(TestCase):
def setUp(self): def setUp(self):
self.original_coreapi = filters.coreapi self.original_coreapi = filters.coreapi
@ -142,288 +38,6 @@ class BaseFilterTests(TestCase):
assert self.filter_backend.get_schema_fields({}) == [] assert self.filter_backend.get_schema_fields({}) == []
class CommonFilteringTestCase(TestCase):
def _serialize_object(self, obj):
return {'id': obj.id, 'text': obj.text, 'decimal': str(obj.decimal), 'date': obj.date.isoformat()}
def setUp(self):
"""
Create 10 FilterableItem instances.
"""
base_data = ('a', Decimal('0.25'), datetime.date(2012, 10, 8))
for i in range(10):
text = chr(i + ord(base_data[0])) * 3 # Produces string 'aaa', 'bbb', etc.
decimal = base_data[1] + i
date = base_data[2] - datetime.timedelta(days=i * 2)
FilterableItem(text=text, decimal=decimal, date=date).save()
self.objects = FilterableItem.objects
self.data = [
self._serialize_object(obj)
for obj in self.objects.all()
]
class IntegrationTestFiltering(CommonFilteringTestCase):
"""
Integration tests for filtered list views.
"""
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_backend_deprecation(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
view = FilterFieldsRootView.as_view()
request = factory.get('/')
response = view(request).render()
assert response.status_code == status.HTTP_200_OK
assert response.data == self.data
self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
self.assertIn("'rest_framework.filters.DjangoFilterBackend' is deprecated.", str(w[-1].message))
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_no_df_deprecation(self):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
import django_filters.rest_framework
class DFFilterFieldsRootView(FilterFieldsRootView):
filter_backends = (django_filters.rest_framework.DjangoFilterBackend,)
view = DFFilterFieldsRootView.as_view()
request = factory.get('/')
response = view(request).render()
assert response.status_code == status.HTTP_200_OK
assert response.data == self.data
assert len(w) == 0
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_backend_mro(self):
class CustomBackend(filters.DjangoFilterBackend):
def filter_queryset(self, request, queryset, view):
assert False, "custom filter_queryset should run"
class DFFilterFieldsRootView(FilterFieldsRootView):
filter_backends = (CustomBackend,)
view = DFFilterFieldsRootView.as_view()
request = factory.get('/')
with pytest.raises(AssertionError, message="custom filter_queryset should run"):
view(request).render()
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_get_filtered_fields_root_view(self):
"""
GET requests to paginated ListCreateAPIView should return paginated results.
"""
view = FilterFieldsRootView.as_view()
# Basic test with no filter.
request = factory.get('/')
response = view(request).render()
assert response.status_code == status.HTTP_200_OK
assert response.data == self.data
# Tests that the decimal filter works.
search_decimal = Decimal('2.25')
request = factory.get('/', {'decimal': '%s' % search_decimal})
response = view(request).render()
assert response.status_code == status.HTTP_200_OK
expected_data = [f for f in self.data if Decimal(f['decimal']) == search_decimal]
assert response.data == expected_data
# Tests that the date filter works.
search_date = datetime.date(2012, 9, 22)
request = factory.get('/', {'date': '%s' % search_date}) # search_date str: '2012-09-22'
response = view(request).render()
assert response.status_code == status.HTTP_200_OK
expected_data = [f for f in self.data if parse_date(f['date']) == search_date]
assert response.data == expected_data
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_filter_with_queryset(self):
"""
Regression test for #814.
"""
view = FilterFieldsQuerysetView.as_view()
# Tests that the decimal filter works.
search_decimal = Decimal('2.25')
request = factory.get('/', {'decimal': '%s' % search_decimal})
response = view(request).render()
assert response.status_code == status.HTTP_200_OK
expected_data = [f for f in self.data if Decimal(f['decimal']) == search_decimal]
assert response.data == expected_data
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_filter_with_get_queryset_only(self):
"""
Regression test for #834.
"""
view = GetQuerysetView.as_view()
request = factory.get('/get-queryset/')
view(request).render()
# Used to raise "issubclass() arg 2 must be a class or tuple of classes"
# here when neither `model' nor `queryset' was specified.
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_get_filtered_class_root_view(self):
"""
GET requests to filtered ListCreateAPIView that have a filter_class set
should return filtered results.
"""
view = FilterClassRootView.as_view()
# Basic test with no filter.
request = factory.get('/')
response = view(request).render()
assert response.status_code == status.HTTP_200_OK
assert response.data == self.data
# Tests that the decimal filter set with 'lt' in the filter class works.
search_decimal = Decimal('4.25')
request = factory.get('/', {'decimal': '%s' % search_decimal})
response = view(request).render()
assert response.status_code == status.HTTP_200_OK
expected_data = [f for f in self.data if Decimal(f['decimal']) < search_decimal]
assert response.data == expected_data
# Tests that the date filter set with 'gt' in the filter class works.
search_date = datetime.date(2012, 10, 2)
request = factory.get('/', {'date': '%s' % search_date}) # search_date str: '2012-10-02'
response = view(request).render()
assert response.status_code == status.HTTP_200_OK
expected_data = [f for f in self.data if parse_date(f['date']) > search_date]
assert response.data == expected_data
# Tests that the text filter set with 'icontains' in the filter class works.
search_text = 'ff'
request = factory.get('/', {'text': '%s' % search_text})
response = view(request).render()
assert response.status_code == status.HTTP_200_OK
expected_data = [f for f in self.data if search_text in f['text'].lower()]
assert response.data == expected_data
# Tests that multiple filters works.
search_decimal = Decimal('5.25')
search_date = datetime.date(2012, 10, 2)
request = factory.get('/', {
'decimal': '%s' % (search_decimal,),
'date': '%s' % (search_date,)
})
response = view(request).render()
assert response.status_code == status.HTTP_200_OK
expected_data = [f for f in self.data if parse_date(f['date']) > search_date and
Decimal(f['decimal']) < search_decimal]
assert response.data == expected_data
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_incorrectly_configured_filter(self):
"""
An error should be displayed when the filter class is misconfigured.
"""
view = IncorrectlyConfiguredRootView.as_view()
request = factory.get('/')
self.assertRaises(AssertionError, view, request)
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_base_model_filter(self):
"""
The `get_filter_class` model checks should allow base model filters.
"""
view = BaseFilterableItemFilterRootView.as_view()
request = factory.get('/?text=aaa')
response = view(request).render()
assert response.status_code == status.HTTP_200_OK
assert 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()
assert response.status_code == status.HTTP_200_OK
assert len(response.data) == 1
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_unknown_filter(self):
"""
GET requests with filters that aren't configured should return 200.
"""
view = FilterFieldsRootView.as_view()
search_integer = 10
request = factory.get('/', {'integer': '%s' % search_integer})
response = view(request).render()
assert response.status_code == status.HTTP_200_OK
@override_settings(ROOT_URLCONF='tests.test_filters')
class IntegrationTestDetailFiltering(CommonFilteringTestCase):
"""
Integration tests for filtered detail views.
"""
def _get_url(self, item):
return reverse('detail-view', kwargs=dict(pk=item.pk))
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_get_filtered_detail_view(self):
"""
GET requests to filtered RetrieveAPIView that have a filter_class set
should return filtered results.
"""
item = self.objects.all()[0]
data = self._serialize_object(item)
# Basic test with no filter.
response = self.client.get(self._get_url(item))
assert response.status_code == status.HTTP_200_OK
assert response.data == data
# Tests that the decimal filter set that should fail.
search_decimal = Decimal('4.25')
high_item = self.objects.filter(decimal__gt=search_decimal)[0]
response = self.client.get(
'{url}'.format(url=self._get_url(high_item)),
{'decimal': '{param}'.format(param=search_decimal)})
assert response.status_code == status.HTTP_404_NOT_FOUND
# Tests that the decimal filter set that should succeed.
search_decimal = Decimal('4.25')
low_item = self.objects.filter(decimal__lt=search_decimal)[0]
low_item_data = self._serialize_object(low_item)
response = self.client.get(
'{url}'.format(url=self._get_url(low_item)),
{'decimal': '{param}'.format(param=search_decimal)})
assert response.status_code == status.HTTP_200_OK
assert response.data == low_item_data
# Tests that multiple filters works.
search_decimal = Decimal('5.25')
search_date = datetime.date(2012, 10, 2)
valid_item = self.objects.filter(decimal__lt=search_decimal, date__gt=search_date)[0]
valid_item_data = self._serialize_object(valid_item)
response = self.client.get(
'{url}'.format(url=self._get_url(valid_item)), {
'decimal': '{decimal}'.format(decimal=search_decimal),
'date': '{date}'.format(date=search_date)
})
assert response.status_code == status.HTTP_200_OK
assert response.data == valid_item_data
class SearchFilterModel(models.Model): class SearchFilterModel(models.Model):
title = models.CharField(max_length=20) title = models.CharField(max_length=20)
text = models.CharField(max_length=100) text = models.CharField(max_length=100)
@ -720,42 +334,6 @@ class DjangoFilterOrderingSerializer(serializers.ModelSerializer):
fields = '__all__' fields = '__all__'
class DjangoFilterOrderingTests(TestCase):
def setUp(self):
data = [{
'date': datetime.date(2012, 10, 8),
'text': 'abc'
}, {
'date': datetime.date(2013, 10, 8),
'text': 'bcd'
}, {
'date': datetime.date(2014, 10, 8),
'text': 'cde'
}]
for d in data:
DjangoFilterOrderingModel.objects.create(**d)
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_default_ordering(self):
class DjangoFilterOrderingView(generics.ListAPIView):
serializer_class = DjangoFilterOrderingSerializer
queryset = DjangoFilterOrderingModel.objects.all()
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ['text']
ordering = ('-date',)
view = DjangoFilterOrderingView.as_view()
request = factory.get('/')
response = view(request)
assert response.data == [
{'id': 3, 'date': '2014-10-08', 'text': 'cde'},
{'id': 2, 'date': '2013-10-08', 'text': 'bcd'},
{'id': 1, 'date': '2012-10-08', 'text': 'abc'}
]
class OrderingFilterTests(TestCase): class OrderingFilterTests(TestCase):
def setUp(self): def setUp(self):
# Sequence of title/text is: # Sequence of title/text is: