diff options
| author | Tom Christie | 2012-10-01 15:49:19 +0100 |
|---|---|---|
| committer | Tom Christie | 2012-10-01 15:49:19 +0100 |
| commit | b16fb5777168246b1e217640b818a82eb6e2141b (patch) | |
| tree | d45a059c40ee2be8c77f6da0c7092a1a57a00ad1 /rest_framework | |
| parent | 6fa589fefd48d98e4f0a11548b6c3e5ced58e31e (diff) | |
| download | django-rest-framework-b16fb5777168246b1e217640b818a82eb6e2141b.tar.bz2 | |
Expand pagination support, add docs
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/fields.py | 2 | ||||
| -rw-r--r-- | rest_framework/generics.py | 50 | ||||
| -rw-r--r-- | rest_framework/pagination.py | 62 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 2 | ||||
| -rw-r--r-- | rest_framework/tests/generics.py | 1 | ||||
| -rw-r--r-- | rest_framework/tests/pagination.py | 34 |
6 files changed, 125 insertions, 26 deletions
diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 74675ee9..e1a551d3 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -158,6 +158,8 @@ class Field(object): return value elif hasattr(self, 'model_field'): return self.model_field.value_to_string(self.obj) + elif hasattr(value, '__iter__') and not isinstance(value, (dict, basestring)): + return [self.to_native(item) for item in value] return smart_unicode(value) def attributes(self): diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 1e547b32..8647ad42 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -16,20 +16,24 @@ class BaseView(views.APIView): """ serializer_class = None model_serializer_class = api_settings.MODEL_SERIALIZER - pagination_serializer_class = api_settings.PAGINATION_SERIALIZER - paginate_by = api_settings.PAGINATE_BY def get_serializer_context(self): + """ + Extra context provided to the serializer class. + """ return { 'request': self.request, - 'format': self.kwargs.get('format', None) + 'format': self.format, + 'view': self } - def get_serializer(self, data=None, files=None, instance=None, kwargs=None): - # TODO: add support for files - # TODO: add support for seperate serializer/deserializer + def get_serializer_class(self): + """ + Return the class to use for the serializer. + Use `self.serializer_class`, falling back to constructing a + model serializer class from `self.model_serializer_class` + """ serializer_class = self.serializer_class - kwargs = kwargs or {} if serializer_class is None: class DefaultSerializer(self.model_serializer_class): @@ -37,22 +41,38 @@ class BaseView(views.APIView): model = self.model serializer_class = DefaultSerializer - context = self.get_serializer_context() - return serializer_class(data, instance=instance, context=context, **kwargs) + return serializer_class - def get_pagination_serializer(self, page=None): - serializer_class = self.pagination_serializer_class + def get_serializer(self, data=None, files=None, instance=None): + # TODO: add support for files + # TODO: add support for seperate serializer/deserializer + serializer_class = self.get_serializer_class() context = self.get_serializer_context() - ret = serializer_class(instance=page, context=context) - ret.fields['results'] = self.get_serializer(kwargs={'source': 'object_list'}) - return ret + return serializer_class(data, instance=instance, context=context) class MultipleObjectBaseView(MultipleObjectMixin, BaseView): """ Base class for generic views onto a queryset. """ - pass + + pagination_serializer_class = api_settings.PAGINATION_SERIALIZER + paginate_by = api_settings.PAGINATE_BY + + def get_pagination_serializer_class(self): + """ + Return the class to use for the pagination serializer. + """ + class SerializerClass(self.pagination_serializer_class): + class Meta: + object_serializer_class = self.get_serializer_class() + + return SerializerClass + + def get_pagination_serializer(self, page=None): + pagination_serializer_class = self.get_pagination_serializer_class() + context = self.get_serializer_context() + return pagination_serializer_class(instance=page, context=context) class SingleObjectBaseView(SingleObjectMixin, BaseView): diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 398e6f3d..f8b1fd1a 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -4,27 +4,64 @@ from rest_framework import serializers class NextPageField(serializers.Field): + """ + Field that returns a link to the next page in paginated results. + """ def to_native(self, value): if not value.has_next(): return None page = value.next_page_number() - request = self.context['request'] - return request.build_absolute_uri('?page=%d' % page) + request = self.context.get('request') + relative_url = '?page=%d' % page + if request: + return request.build_absolute_uri(relative_url) + return relative_url class PreviousPageField(serializers.Field): + """ + Field that returns a link to the previous page in paginated results. + """ def to_native(self, value): if not value.has_previous(): return None page = value.previous_page_number() - request = self.context['request'] - return request.build_absolute_uri('?page=%d' % page) + request = self.context.get('request') + relative_url = '?page=%d' % page + if request: + return request.build_absolute_uri('?page=%d' % page) + return relative_url -class PaginationSerializer(serializers.Serializer): - count = serializers.Field(source='paginator.count') - next = NextPageField(source='*') - previous = PreviousPageField(source='*') +class PaginationSerializerOptions(serializers.SerializerOptions): + """ + An object that stores the options that may be provided to a + pagination serializer by using the inner `Meta` class. + + Accessible on the instance as `serializer.opts`. + """ + def __init__(self, meta): + super(PaginationSerializerOptions, self).__init__(meta) + self.object_serializer_class = getattr(meta, 'object_serializer_class', + serializers.Field) + + +class BasePaginationSerializer(serializers.Serializer): + """ + A base class for pagination serializers to inherit from, + to make implementing custom serializers more easy. + """ + _options_class = PaginationSerializerOptions + _results_field = 'results' + + def __init__(self, *args, **kwargs): + """ + Override init to add in the object serializer field on-the-fly. + """ + super(BasePaginationSerializer, self).__init__(*args, **kwargs) + results_field = self._results_field + object_serializer = self.opts.object_serializer_class + self.fields[results_field] = object_serializer(source='object_list') def to_native(self, obj): """ @@ -32,3 +69,12 @@ class PaginationSerializer(serializers.Serializer): each in turn. """ return self.convert_object(obj) + + +class PaginationSerializer(BasePaginationSerializer): + """ + A default implementation of a pagination serializer. + """ + count = serializers.Field(source='paginator.count') + next = NextPageField(source='*') + previous = PreviousPageField(source='*') diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 9cbdb9de..bb48e381 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -70,7 +70,7 @@ class SerializerMetaclass(type): class SerializerOptions(object): """ - Meta class options for ModelSerializer + Meta class options for Serializer """ def __init__(self, meta): self.nested = getattr(meta, 'nested', False) diff --git a/rest_framework/tests/generics.py b/rest_framework/tests/generics.py index fee6e3a6..ec46b427 100644 --- a/rest_framework/tests/generics.py +++ b/rest_framework/tests/generics.py @@ -13,6 +13,7 @@ class RootView(generics.RootAPIView): Example description for OPTIONS. """ model = BasicModel + paginate_by = None class InstanceView(generics.InstanceAPIView): diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py index 4ddfc915..9e424cc5 100644 --- a/rest_framework/tests/pagination.py +++ b/rest_framework/tests/pagination.py @@ -1,6 +1,7 @@ +from django.core.paginator import Paginator from django.test import TestCase from django.test.client import RequestFactory -from rest_framework import generics, status +from rest_framework import generics, status, pagination from rest_framework.tests.models import BasicModel factory = RequestFactory() @@ -14,7 +15,11 @@ class RootView(generics.RootAPIView): paginate_by = 10 -class TestPaginatedView(TestCase): +class IntegrationTestPagination(TestCase): + """ + Integration tests for paginated list views. + """ + def setUp(self): """ Create 26 BasicModel intances. @@ -55,3 +60,28 @@ class TestPaginatedView(TestCase): self.assertEquals(response.data['results'], self.data[20:]) self.assertEquals(response.data['next'], None) self.assertNotEquals(response.data['previous'], None) + + +class UnitTestPagination(TestCase): + """ + Unit tests for pagination of primative objects. + """ + + def setUp(self): + self.objects = [char * 3 for char in 'abcdefghijklmnopqrstuvwxyz'] + paginator = Paginator(self.objects, 10) + self.first_page = paginator.page(1) + self.last_page = paginator.page(3) + + def test_native_pagination(self): + serializer = pagination.PaginationSerializer(instance=self.first_page) + self.assertEquals(serializer.data['count'], 26) + self.assertEquals(serializer.data['next'], '?page=2') + self.assertEquals(serializer.data['previous'], None) + self.assertEquals(serializer.data['results'], self.objects[:10]) + + serializer = pagination.PaginationSerializer(instance=self.last_page) + self.assertEquals(serializer.data['count'], 26) + self.assertEquals(serializer.data['next'], None) + self.assertEquals(serializer.data['previous'], '?page=2') + self.assertEquals(serializer.data['results'], self.objects[20:]) |
