From 1e9ece0f9353515265da9b6266dc4b39775a0257 Mon Sep 17 00:00:00 2001 From: Ben Konrath Date: Mon, 8 Oct 2012 22:00:55 +0200 Subject: [PATCH 01/14] First attempt at adding filter support. The filter support uses django-filter to work its magic. --- requirements.txt | 1 + rest_framework/generics.py | 35 ++++++- rest_framework/mixins.py | 2 +- rest_framework/tests/filterset.py | 160 +++++++++++++++++++++++++++++ rest_framework/tests/models.py | 7 ++ rest_framework/tests/pagination.py | 69 ++++++++++++- 6 files changed, 269 insertions(+), 5 deletions(-) create mode 100644 rest_framework/tests/filterset.py diff --git a/requirements.txt b/requirements.txt index 730c1d07a..b37b61851 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ Django>=1.3 +-e git+https://github.com/alex/django-filter.git#egg=django-filter diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 59739d010..3b2bea3bc 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -1,12 +1,12 @@ """ -Generic views that provide commmonly needed behaviour. +Generic views that provide commonly needed behaviour. """ from rest_framework import views, mixins from rest_framework.settings import api_settings from django.views.generic.detail import SingleObjectMixin from django.views.generic.list import MultipleObjectMixin - +import django_filters ### Base classes for the generic views ### @@ -58,6 +58,37 @@ class MultipleObjectBaseView(MultipleObjectMixin, BaseView): pagination_serializer_class = api_settings.PAGINATION_SERIALIZER paginate_by = api_settings.PAGINATE_BY + filter_class = None + filter_fields = None + + def get_filter_class(self): + """ + Return the django-filters `FilterSet` used to filter the queryset. + """ + if self.filter_class: + return self.filter_class + + if self.filter_fields: + class AutoFilterSet(django_filters.FilterSet): + class Meta: + model = self.model + fields = self.filter_fields + return AutoFilterSet + + return None + + def filter_queryset(self, queryset): + filter_class = self.get_filter_class() + + if filter_class: + assert issubclass(filter_class.Meta.model, self.model), \ + "%s is not a subclass of %s" % (filter_class.Meta.model, self.model) + return filter_class(self.request.GET, queryset=queryset) + + return queryset + + def get_filtered_queryset(self): + return self.filter_queryset(self.get_queryset()) def get_pagination_serializer_class(self): """ diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index 29153e182..04626fb0e 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -33,7 +33,7 @@ class ListModelMixin(object): empty_error = u"Empty list and '%(class_name)s.allow_empty' is False." def list(self, request, *args, **kwargs): - self.object_list = self.get_queryset() + self.object_list = self.get_filtered_queryset() # Default is to allow empty querysets. This can be altered by setting # `.allow_empty = False`, to raise 404 errors on empty querysets. diff --git a/rest_framework/tests/filterset.py b/rest_framework/tests/filterset.py new file mode 100644 index 000000000..8c857f3f9 --- /dev/null +++ b/rest_framework/tests/filterset.py @@ -0,0 +1,160 @@ +import datetime +from django.test import TestCase +from django.test.client import RequestFactory +from rest_framework import generics, status +from rest_framework.tests.models import FilterableItem, BasicModel +import django_filters + +factory = RequestFactory() + +# Basic filter on a list view. +class FilterFieldsRootView(generics.ListCreateAPIView): + model = FilterableItem + filter_fields = ['decimal', 'date'] + + +# These class are used to test a filter class. +class SeveralFieldsFilter(django_filters.FilterSet): + text = django_filters.CharFilter(lookup_type='icontains') + decimal = django_filters.NumberFilter(lookup_type='lt') + date = django_filters.DateFilter(lookup_type='gt') + class Meta: + model = FilterableItem + fields = ['text', 'decimal', 'date'] + + +class FilterClassRootView(generics.ListCreateAPIView): + model = FilterableItem + filter_class = SeveralFieldsFilter + + +# These classes are used to test a misconfigured filter class. +class MisconfiguredFilter(django_filters.FilterSet): + text = django_filters.CharFilter(lookup_type='icontains') + class Meta: + model = BasicModel + fields = ['text'] + + +class IncorrectlyConfiguredRootView(generics.ListCreateAPIView): + model = FilterableItem + filter_class = MisconfiguredFilter + + +class IntegrationTestFiltering(TestCase): + """ + Integration tests for filtered list views. + """ + + def setUp(self): + """ + Create 10 FilterableItem instances. + """ + base_data = ('a', 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 = [ + {'id': obj.id, 'text': obj.text, 'decimal': obj.decimal, 'date': obj.date} + for obj in self.objects.all() + ] + + 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() + self.assertEquals(response.status_code, status.HTTP_200_OK) + self.assertEquals(response.data, self.data) + + # Tests that the decimal filter works. + search_decimal = 2.25 + request = factory.get('/?decimal=%s' % search_decimal) + response = view(request).render() + self.assertEquals(response.status_code, status.HTTP_200_OK) + expected_data = [ f for f in self.data if f['decimal'] == search_decimal ] + self.assertEquals(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() + self.assertEquals(response.status_code, status.HTTP_200_OK) + expected_data = [ f for f in self.data if f['date'] == search_date ] + self.assertEquals(response.data, expected_data) + + 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() + self.assertEquals(response.status_code, status.HTTP_200_OK) + self.assertEquals(response.data, self.data) + + # Tests that the decimal filter set with 'lt' in the filter class works. + search_decimal = 4.25 + request = factory.get('/?decimal=%s' % search_decimal) + response = view(request).render() + self.assertEquals(response.status_code, status.HTTP_200_OK) + expected_data = [ f for f in self.data if f['decimal'] < search_decimal ] + self.assertEquals(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() + self.assertEquals(response.status_code, status.HTTP_200_OK) + expected_data = [ f for f in self.data if f['date'] > search_date ] + self.assertEquals(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() + self.assertEquals(response.status_code, status.HTTP_200_OK) + expected_data = [ f for f in self.data if search_text in f['text'].lower() ] + self.assertEquals(response.data, expected_data) + + # Tests that multiple filters works. + search_decimal = 5.25 + search_date = datetime.date(2012, 10, 2) + request = factory.get('/?decimal=%s&date=%s' % (search_decimal, search_date)) + response = view(request).render() + self.assertEquals(response.status_code, status.HTTP_200_OK) + expected_data = [ f for f in self.data if f['date'] > search_date and + f['decimal'] < search_decimal ] + self.assertEquals(response.data, expected_data) + + 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) + + # TODO Return 400 filter paramater requested that hasn't been configured. + def test_bad_request(self): + """ + GET requests with filters that aren't configured should return 400. + """ + view = FilterFieldsRootView.as_view() + + search_integer = 10 + request = factory.get('/?integer=%s' % search_integer) + response = view(request).render() + self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST) \ No newline at end of file diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py index 6a758f0ce..780c9dba7 100644 --- a/rest_framework/tests/models.py +++ b/rest_framework/tests/models.py @@ -85,6 +85,13 @@ class Bookmark(RESTFrameworkModel): tags = GenericRelation(TaggedItem) +# Model to test filtering. +class FilterableItem(RESTFrameworkModel): + text = models.CharField(max_length=100) + decimal = models.DecimalField(max_digits=4, decimal_places=2) + date = models.DateField() + + # Model for regression test for #285 class Comment(RESTFrameworkModel): diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py index a939c9ef5..729bbfc2f 100644 --- a/rest_framework/tests/pagination.py +++ b/rest_framework/tests/pagination.py @@ -1,8 +1,10 @@ +import datetime from django.core.paginator import Paginator from django.test import TestCase from django.test.client import RequestFactory from rest_framework import generics, status, pagination -from rest_framework.tests.models import BasicModel +from rest_framework.tests.models import BasicModel, FilterableItem +import django_filters factory = RequestFactory() @@ -15,6 +17,19 @@ class RootView(generics.ListCreateAPIView): paginate_by = 10 +class DecimalFilter(django_filters.FilterSet): + decimal = django_filters.NumberFilter(lookup_type='lt') + class Meta: + model = FilterableItem + fields = ['text', 'decimal', 'date'] + + +class FilterFieldsRootView(generics.ListCreateAPIView): + model = FilterableItem + paginate_by = 10 + filter_class = DecimalFilter + + class IntegrationTestPagination(TestCase): """ Integration tests for paginated list views. @@ -22,7 +37,7 @@ class IntegrationTestPagination(TestCase): def setUp(self): """ - Create 26 BasicModel intances. + Create 26 BasicModel instances. """ for char in 'abcdefghijklmnopqrstuvwxyz': BasicModel(text=char * 3).save() @@ -61,6 +76,56 @@ class IntegrationTestPagination(TestCase): self.assertEquals(response.data['next'], None) self.assertNotEquals(response.data['previous'], None) +class IntegrationTestPaginationAndFiltering(TestCase): + + def setUp(self): + """ + Create 50 FilterableItem instances. + """ + base_data = ('a', 0.25, datetime.date(2012, 10, 8)) + for i in range(26): + 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 = [ + {'id': obj.id, 'text': obj.text, 'decimal': obj.decimal, 'date': obj.date} + for obj in self.objects.all() + ] + self.view = FilterFieldsRootView.as_view() + + def test_get_paginated_filtered_root_view(self): + """ + GET requests to paginated filtered ListCreateAPIView should return + paginated results. The next and previous links should preserve the + filtered parameters. + """ + request = factory.get('/?decimal=15.20') + response = self.view(request).render() + self.assertEquals(response.status_code, status.HTTP_200_OK) + self.assertEquals(response.data['count'], 15) + self.assertEquals(response.data['results'], self.data[:10]) + self.assertNotEquals(response.data['next'], None) + self.assertEquals(response.data['previous'], None) + + request = factory.get(response.data['next']) + response = self.view(request).render() + self.assertEquals(response.status_code, status.HTTP_200_OK) + self.assertEquals(response.data['count'], 15) + self.assertEquals(response.data['results'], self.data[10:15]) + self.assertEquals(response.data['next'], None) + self.assertNotEquals(response.data['previous'], None) + + request = factory.get(response.data['previous']) + response = self.view(request).render() + self.assertEquals(response.status_code, status.HTTP_200_OK) + self.assertEquals(response.data['count'], 15) + self.assertEquals(response.data['results'], self.data[:10]) + self.assertNotEquals(response.data['next'], None) + self.assertEquals(response.data['previous'], None) + class UnitTestPagination(TestCase): """ From 692203f933b77cc3b18e15434002169b642fbd84 Mon Sep 17 00:00:00 2001 From: Ben Konrath Date: Tue, 9 Oct 2012 08:22:00 +0200 Subject: [PATCH 02/14] Check for 200 status when unknown filter requested. This changes the test from the failing checking for status 400. See discussion here: https://github.com/tomchristie/django-rest-framework/pull/169#issuecomment-9240480 --- rest_framework/tests/filterset.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rest_framework/tests/filterset.py b/rest_framework/tests/filterset.py index 8c857f3f9..b21abacb8 100644 --- a/rest_framework/tests/filterset.py +++ b/rest_framework/tests/filterset.py @@ -147,14 +147,13 @@ class IntegrationTestFiltering(TestCase): request = factory.get('/') self.assertRaises(AssertionError, view, request) - # TODO Return 400 filter paramater requested that hasn't been configured. - def test_bad_request(self): + def test_unknown_filter(self): """ - GET requests with filters that aren't configured should return 400. + 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() - self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST) \ No newline at end of file + self.assertEquals(response.status_code, status.HTTP_200_OK) \ No newline at end of file From e295f616ec2cfee9c24b22d4be1a605a93d9544d Mon Sep 17 00:00:00 2001 From: Ben Konrath Date: Thu, 11 Oct 2012 11:32:51 +0200 Subject: [PATCH 03/14] Fix small PEP8 problem. --- rest_framework/tests/pagination.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py index 729bbfc2f..054b7ee33 100644 --- a/rest_framework/tests/pagination.py +++ b/rest_framework/tests/pagination.py @@ -76,6 +76,7 @@ class IntegrationTestPagination(TestCase): self.assertEquals(response.data['next'], None) self.assertNotEquals(response.data['previous'], None) + class IntegrationTestPaginationAndFiltering(TestCase): def setUp(self): From 6fbd411254089c86baca65b08a89d239e5b804a9 Mon Sep 17 00:00:00 2001 From: Ben Konrath Date: Thu, 11 Oct 2012 11:35:00 +0200 Subject: [PATCH 04/14] Make query filters work with pagination. --- rest_framework/pagination.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 131718fd7..616c76749 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -14,6 +14,9 @@ class NextPageField(serializers.Field): request = self.context.get('request') relative_url = '?page=%d' % page if request: + for field, value in request.QUERY_PARAMS.iteritems(): + if field != 'page': + relative_url += '&%s=%s' % (field, value) return request.build_absolute_uri(relative_url) return relative_url @@ -29,7 +32,10 @@ class PreviousPageField(serializers.Field): request = self.context.get('request') relative_url = '?page=%d' % page if request: - return request.build_absolute_uri('?page=%d' % page) + for field, value in request.QUERY_PARAMS.iteritems(): + if field != 'page': + relative_url += '&%s=%s' % (field, value) + return request.build_absolute_uri(relative_url) return relative_url From 5454162b0419ab6564f37ea56b1852d686b0a11e Mon Sep 17 00:00:00 2001 From: Ben Konrath Date: Thu, 11 Oct 2012 11:39:23 +0200 Subject: [PATCH 05/14] Define 'page' query field name in one place. --- rest_framework/pagination.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 616c76749..c77a10051 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -3,7 +3,11 @@ from rest_framework import serializers # TODO: Support URLconf kwarg-style paging -class NextPageField(serializers.Field): +class PageField(serializers.Field): + page_field = 'page' + + +class NextPageField(PageField): """ Field that returns a link to the next page in paginated results. """ @@ -12,16 +16,16 @@ class NextPageField(serializers.Field): return None page = value.next_page_number() request = self.context.get('request') - relative_url = '?page=%d' % page + relative_url = '?%s=%d' % (self.page_field, page) if request: for field, value in request.QUERY_PARAMS.iteritems(): - if field != 'page': + if field != self.page_field: relative_url += '&%s=%s' % (field, value) return request.build_absolute_uri(relative_url) return relative_url -class PreviousPageField(serializers.Field): +class PreviousPageField(PageField): """ Field that returns a link to the previous page in paginated results. """ @@ -30,10 +34,10 @@ class PreviousPageField(serializers.Field): return None page = value.previous_page_number() request = self.context.get('request') - relative_url = '?page=%d' % page + relative_url = '?%s=%d' % (self.page_field, page) if request: for field, value in request.QUERY_PARAMS.iteritems(): - if field != 'page': + if field != self.page_field: relative_url += '&%s=%s' % (field, value) return request.build_absolute_uri(relative_url) return relative_url From 6300334acaef8fe66e03557f089fb335ac861a57 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 11 Oct 2012 11:21:50 +0100 Subject: [PATCH 06/14] Sanitise JSON error messages --- rest_framework/tests/views.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/rest_framework/tests/views.py b/rest_framework/tests/views.py index 3746d7c8c..43365e07a 100644 --- a/rest_framework/tests/views.py +++ b/rest_framework/tests/views.py @@ -1,3 +1,4 @@ +import copy from django.test import TestCase from django.test.client import RequestFactory from rest_framework import status @@ -27,6 +28,17 @@ def basic_view(request): return {'method': 'PUT', 'data': request.DATA} +def sanitise_json_error(error_dict): + """ + Exact contents of JSON error messages depend on the installed version + of json. + """ + ret = copy.copy(error_dict) + chop = len('JSON parse error - No JSON object could be decoded') + ret['detail'] = ret['detail'][:chop] + return ret + + class ClassBasedViewIntegrationTests(TestCase): def setUp(self): self.view = BasicView.as_view() @@ -38,7 +50,7 @@ class ClassBasedViewIntegrationTests(TestCase): 'detail': u'JSON parse error - No JSON object could be decoded' } self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEquals(response.data, expected) + self.assertEquals(sanitise_json_error(response.data), expected) def test_400_parse_error_tunneled_content(self): content = 'f00bar' @@ -53,7 +65,7 @@ class ClassBasedViewIntegrationTests(TestCase): 'detail': u'JSON parse error - No JSON object could be decoded' } self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEquals(response.data, expected) + self.assertEquals(sanitise_json_error(response.data), expected) class FunctionBasedViewIntegrationTests(TestCase): @@ -67,7 +79,7 @@ class FunctionBasedViewIntegrationTests(TestCase): 'detail': u'JSON parse error - No JSON object could be decoded' } self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEquals(response.data, expected) + self.assertEquals(sanitise_json_error(response.data), expected) def test_400_parse_error_tunneled_content(self): content = 'f00bar' @@ -82,4 +94,4 @@ class FunctionBasedViewIntegrationTests(TestCase): 'detail': u'JSON parse error - No JSON object could be decoded' } self.assertEquals(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEquals(response.data, expected) + self.assertEquals(sanitise_json_error(response.data), expected) From 6f736a682369e003e4ae4b8d587f9168d4196986 Mon Sep 17 00:00:00 2001 From: Ben Konrath Date: Thu, 11 Oct 2012 13:55:16 +0200 Subject: [PATCH 07/14] Explicitly use Decimal for creating filter test data. This fixes a Travis build failures on python 2.6: https://travis-ci.org/#!/tomchristie/django-rest-framework/builds/2746628 --- rest_framework/tests/filterset.py | 3 ++- rest_framework/tests/pagination.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rest_framework/tests/filterset.py b/rest_framework/tests/filterset.py index b21abacb8..5b2721ff5 100644 --- a/rest_framework/tests/filterset.py +++ b/rest_framework/tests/filterset.py @@ -1,4 +1,5 @@ import datetime +from decimal import Decimal from django.test import TestCase from django.test.client import RequestFactory from rest_framework import generics, status @@ -50,7 +51,7 @@ class IntegrationTestFiltering(TestCase): """ Create 10 FilterableItem instances. """ - base_data = ('a', 0.25, datetime.date(2012, 10, 8)) + 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 diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py index 054b7ee33..8c5e6ad72 100644 --- a/rest_framework/tests/pagination.py +++ b/rest_framework/tests/pagination.py @@ -1,4 +1,5 @@ import datetime +from decimal import Decimal from django.core.paginator import Paginator from django.test import TestCase from django.test.client import RequestFactory @@ -83,7 +84,7 @@ class IntegrationTestPaginationAndFiltering(TestCase): """ Create 50 FilterableItem instances. """ - base_data = ('a', 0.25, datetime.date(2012, 10, 8)) + base_data = ('a', Decimal(0.25), datetime.date(2012, 10, 8)) for i in range(26): text = chr(i + ord(base_data[0])) * 3 # Produces string 'aaa', 'bbb', etc. decimal = base_data[1] + i From 1d054f95725e5bec7d4ba9d23717897ef80b7388 Mon Sep 17 00:00:00 2001 From: Ben Konrath Date: Thu, 11 Oct 2012 14:19:29 +0200 Subject: [PATCH 08/14] Use Decimal (properly) everywhere. --- rest_framework/tests/filterset.py | 6 +++--- rest_framework/tests/pagination.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rest_framework/tests/filterset.py b/rest_framework/tests/filterset.py index 5b2721ff5..5374eefc8 100644 --- a/rest_framework/tests/filterset.py +++ b/rest_framework/tests/filterset.py @@ -77,7 +77,7 @@ class IntegrationTestFiltering(TestCase): self.assertEquals(response.data, self.data) # Tests that the decimal filter works. - search_decimal = 2.25 + search_decimal = Decimal('2.25') request = factory.get('/?decimal=%s' % search_decimal) response = view(request).render() self.assertEquals(response.status_code, status.HTTP_200_OK) @@ -106,7 +106,7 @@ class IntegrationTestFiltering(TestCase): self.assertEquals(response.data, self.data) # Tests that the decimal filter set with 'lt' in the filter class works. - search_decimal = 4.25 + search_decimal = Decimal('4.25') request = factory.get('/?decimal=%s' % search_decimal) response = view(request).render() self.assertEquals(response.status_code, status.HTTP_200_OK) @@ -130,7 +130,7 @@ class IntegrationTestFiltering(TestCase): self.assertEquals(response.data, expected_data) # Tests that multiple filters works. - search_decimal = 5.25 + search_decimal = Decimal('5.25') search_date = datetime.date(2012, 10, 2) request = factory.get('/?decimal=%s&date=%s' % (search_decimal, search_date)) response = view(request).render() diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py index 8c5e6ad72..170515a76 100644 --- a/rest_framework/tests/pagination.py +++ b/rest_framework/tests/pagination.py @@ -84,7 +84,7 @@ class IntegrationTestPaginationAndFiltering(TestCase): """ Create 50 FilterableItem instances. """ - base_data = ('a', Decimal(0.25), datetime.date(2012, 10, 8)) + base_data = ('a', Decimal('0.25'), datetime.date(2012, 10, 8)) for i in range(26): text = chr(i + ord(base_data[0])) * 3 # Produces string 'aaa', 'bbb', etc. decimal = base_data[1] + i From c24997df3b943e5d7a3b2e101508e4b79ee82dc4 Mon Sep 17 00:00:00 2001 From: Ben Konrath Date: Thu, 11 Oct 2012 16:39:25 +0200 Subject: [PATCH 09/14] Change django-filter to version that supports Django 1.3. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b37b61851..de7526394 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ Django>=1.3 --e git+https://github.com/alex/django-filter.git#egg=django-filter +-e git+https://github.com/onepercentclub/django-filter.git@django-1.3-compat#egg=django-filter From 9f1aca6a258ed231f2719df14aece541f00243ed Mon Sep 17 00:00:00 2001 From: Ben Konrath Date: Thu, 1 Nov 2012 16:41:31 +0100 Subject: [PATCH 10/14] Change django-filter requirement to upstream version. This git revision has the django 1.3 compatibility pull request. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index de7526394..48ff9d653 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ Django>=1.3 --e git+https://github.com/onepercentclub/django-filter.git@django-1.3-compat#egg=django-filter +-e git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter From 806bb728bf64f0b610418720cce4efe410c59135 Mon Sep 17 00:00:00 2001 From: Ben Konrath Date: Thu, 1 Nov 2012 20:11:15 +0100 Subject: [PATCH 11/14] Use requirements.txt in Travis 'install'. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 0e177a95a..fa8693a06 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ env: install: - pip install $DJANGO + - pip install -r requirements.txt --use-mirrors - export PYTHONPATH=. script: From 40c24a5ab0be49f3b47cbc864489532b48ac9bff Mon Sep 17 00:00:00 2001 From: Ben Konrath Date: Thu, 1 Nov 2012 20:11:50 +0100 Subject: [PATCH 12/14] Add django-filter to tox.ini. --- tox.ini | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tox.ini b/tox.ini index bcfff6729..3596bbdc3 100644 --- a/tox.ini +++ b/tox.ini @@ -8,23 +8,29 @@ commands = {envpython} rest_framework/runtests/runtests.py [testenv:py2.7-django1.5] basepython = python2.7 deps = https://github.com/django/django/zipball/master + git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter [testenv:py2.7-django1.4] basepython = python2.7 deps = django==1.4.1 + git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter [testenv:py2.7-django1.3] basepython = python2.7 deps = django==1.3.3 + git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter [testenv:py2.6-django1.5] basepython = python2.6 deps = https://github.com/django/django/zipball/master + git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter [testenv:py2.6-django1.4] basepython = python2.6 deps = django==1.4.1 + git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter [testenv:py2.6-django1.3] basepython = python2.6 deps = django==1.3.3 + git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter From 5b399a844bfd07e94084bff84bbd9f98dbfef139 Mon Sep 17 00:00:00 2001 From: Ben Konrath Date: Thu, 1 Nov 2012 20:28:15 +0100 Subject: [PATCH 13/14] Add django-filter (and django) requirement to setup.py. --- setup.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 26d072837..601bb65b8 100755 --- a/setup.py +++ b/setup.py @@ -63,7 +63,13 @@ setup( packages=get_packages('rest_framework'), package_data=get_package_data('rest_framework'), test_suite='rest_framework.runtests.runtests.main', - install_requires=[], + install_requires=[ + 'Django>=1.3.0', + 'django-filter', + ], + dependency_links = [ + 'git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter', + ], classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', From 01564fb1e5727134d2ceb4b3ab79e013af1b4807 Mon Sep 17 00:00:00 2001 From: Ben Konrath Date: Thu, 1 Nov 2012 21:57:14 +0100 Subject: [PATCH 14/14] Revert "Add django-filter (and django) requirement to setup.py." This reverts commit 5b399a844bfd07e94084bff84bbd9f98dbfef139. This has been reverted because it's not working properly. --- setup.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 601bb65b8..26d072837 100755 --- a/setup.py +++ b/setup.py @@ -63,13 +63,7 @@ setup( packages=get_packages('rest_framework'), package_data=get_package_data('rest_framework'), test_suite='rest_framework.runtests.runtests.main', - install_requires=[ - 'Django>=1.3.0', - 'django-filter', - ], - dependency_links = [ - 'git+https://github.com/alex/django-filter.git@0e4b3d703b31574922ab86fc78a86164aad0c1d0#egg=django-filter', - ], + install_requires=[], classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment',