diff options
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:])  | 
