Search code examples
pythondjangopaginationdjango-rest-frameworkdjango-pagination

Django Rest Framework 3.1 breaks pagination.PaginationSerializer


I just updated to Django Rest Framework 3.1 and it seems that all hell broke loose.

in my serializers.py I was having the following code:

class TaskSerializer(serializers.ModelSerializer):
    class Meta:
    model = task
    exclude = ('key', ...)

class PaginatedTaskSerializer(pagination.PaginationSerializer):
    class Meta:
        object_serializer_class = TaskSerializer

which was working just fine. Now with the release of 3.1 I can't find examples on how to do the same thing since PaginationSerializer is no longer there. I have tried to subclass PageNumberPagination and use its default paginate_queryset and get_paginated_response methods but I can no longer get their results serialized.

In other words my problem is that I can no longer do this:

class Meta:
    object_serializer_class = TaskSerializer

Any ideas?

Thanks in advance


Solution

  • I am not sure if this is the completely correct way to do it, but it works for my needs. It uses the Django Paginator and a custom serializer.

    Here is my View Class that retrieves the objects for serialization

    class CourseListView(AuthView):
        def get(self, request, format=None):
            """
            Returns a JSON response with a listing of course objects
            """
            courses = Course.objects.order_by('name').all()
            serializer = PaginatedCourseSerializer(courses, request, 25)
            return Response(serializer.data)
    

    Here is the hacked together Serializer that uses my Course serializer.

    from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
    
    class PaginatedCourseSerializer():
        def __init__(self, courses, request, num):
            paginator = Paginator(courses, num)
            page = request.QUERY_PARAMS.get('page')
            try:
                courses = paginator.page(page)
            except PageNotAnInteger:
                courses = paginator.page(1)
            except EmptyPage:
                courses = paginator.page(paginator.num_pages)
            count = paginator.count
        
            previous = None if not courses.has_previous() else courses.previous_page_number()
            next = None if not courses.has_next() else courses.next_page_number()
            serializer = CourseSerializer(courses, many=True)
            self.data = {'count':count,'previous':previous,
                     'next':next,'courses':serializer.data}
    

    This gives me a result that is similar to the behavior that the old paginator gave.

    {
        "previous": 1,
        "next": 3,
        "courses": [...],
        "count": 384
    }
    

    I hope this helps. I still think there has got to be a beter way to do this wiht the new API, but it's just not documented well. If I figure anything more out, I'll edit my post.

    EDIT

    I think I have found a better, more elegant way to do it bey creating my own custom paginator to get behavior like I used to get with the old Paginated Serializer class.

    This is a custom paginator class. I overloaded the response and next page methods to get the result I want (i.e. ?page=2 instead of the full url).

    from rest_framework.response import Response
    from rest_framework.utils.urls import replace_query_param
    
    class CustomCoursePaginator(pagination.PageNumberPagination):
        def get_paginated_response(self, data):
            return Response({'count': self.page.paginator.count,
                             'next': self.get_next_link(),
                             'previous': self.get_previous_link(),
                             'courses': data})
    
        def get_next_link(self):
            if not self.page.has_next():
                return None
            page_number = self.page.next_page_number()
            return replace_query_param('', self.page_query_param, page_number)
    
        def get_previous_link(self):
            if not self.page.has_previous():
                return None
            page_number = self.page.previous_page_number()
            return replace_query_param('', self.page_query_param, page_number)
    

    Then my course view is very similar to how you implemented it, only this time using the Custom paginator.

    class CourseListView(AuthView):
        def get(self, request, format=None):
            """
            Returns a JSON response with a listing of course objects
            """
            courses = Course.objects.order_by('name').all()
            paginator = CustomCoursePaginator()
            result_page = paginator.paginate_queryset(courses, request)
            serializer = CourseSerializer(result_page, many=True)
            return paginator.get_paginated_response(serializer.data)
    

    Now I get the result that I'm looking for.

    {
        "count": 384,
        "next": "?page=3",
        "previous": "?page=1",
        "courses": []
    }
    

    I am still not certain about how this works for the Browsable API (I don't user this feature of drf). I think you can also create your own custom class for this. I hope this helps!