diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index 426865ff9..97f44e673 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -10,6 +10,7 @@ from django.http import Http404 from rest_framework import status from rest_framework.response import Response from rest_framework.request import clone_request +from rest_framework import pagination import warnings @@ -187,3 +188,15 @@ class DestroyModelMixin(object): obj = self.get_object() obj.delete() return Response(status=status.HTTP_204_NO_CONTENT) + + +class LinkPaginationMixin(object): + pagination_serializer_class = pagination.LinkPaginationSerializer + + def paginate_queryset(self, queryset, page_size=None): + page = super(LinkPaginationMixin, self).paginate_queryset( + queryset, page_size) + if page: + page_ser = self.get_pagination_serializer(page) + self.headers.update(page_ser.get_link_header()) + return None # Don't use pagination serializer on response diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 0e8ac9db6..2e03e5961 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -49,6 +49,35 @@ class PreviousPageField(serializers.Field): return replace_query_param(url, self.page_field, page) +class FirstPageField(serializers.Field): + """ + Field that returns a link to the first page in paginated results. + """ + page_field = 'page' + + def to_native(self, value): + if not value.has_previous(): + return None + request = self.context.get('request') + url = request and request.build_absolute_uri() or '' + return replace_query_param(url, self.page_field, 1) + + +class LastPageField(serializers.Field): + """ + Field that returns a link to the previous page in paginated results. + """ + page_field = 'page' + + def to_native(self, value): + if not value.has_next(): + return None + page = value.paginator.num_pages + request = self.context.get('request') + url = request and request.build_absolute_uri() or '' + return replace_query_param(url, self.page_field, page) + + class DefaultObjectSerializer(serializers.Field): """ If no object serializer is specified, then this serializer will be applied @@ -94,7 +123,8 @@ class BasePaginationSerializer(serializers.Serializer): else: context_kwarg = {} - self.fields[results_field] = object_serializer(source='object_list', **context_kwarg) + self.fields[results_field] = object_serializer(source='object_list', + **context_kwarg) class PaginationSerializer(BasePaginationSerializer): @@ -104,3 +134,19 @@ class PaginationSerializer(BasePaginationSerializer): count = serializers.Field(source='paginator.count') next = NextPageField(source='*') previous = PreviousPageField(source='*') + + +class LinkPaginationSerializer(PaginationSerializer): + """ Pagination serializer in order to build Link header """ + first = FirstPageField(source='*') + last = LastPageField(source='*') + + relations = ('next', 'previous', 'first', 'last') + + def get_link_header(self): + link_keader_items = [ + '<%s>; rel="%s"' % (link, rel) + for rel, link in self.data.items() + if (rel in self.relations and link is not None) + ] + return {'Link': ', '.join(link_keader_items)}