18

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

stratis
  • 7,750
  • 13
  • 53
  • 94
  • 1
    I have just run into this exact same problem. I decided to upgrade all of my packages and this was the only thing that broke. I hope this gets answered because I haven't found anything either. – Brobin Mar 18 '15 at 17:05
  • "I just updated to Django Rest Framework 3.1 and it seems that all hell broke loose." Lol. – jmoz Aug 04 '15 at 15:45

3 Answers3

23

I think I figured it out (for the most part at least):

What we should have used from the very beginning is this:

Just use the built-in paginator and change your views.py to this:

from rest_framework.pagination import PageNumberPagination

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 = PageNumberPagination()
        # From the docs:
        # The paginate_queryset method is passed the initial queryset 
        # and should return an iterable object that contains only the 
        # data in the requested page.
        result_page = paginator.paginate_queryset(courses, request)
        # Now we just have to serialize the data just like you suggested.
        serializer = CourseSerializer(result_page, many=True)
        # From the docs:
        # The get_paginated_response method is passed the serialized page 
        # data and should return a Response instance.
        return paginator.get_paginated_response(serializer.data)

For the desired page size just set the PAGE_SIZE in settings.py:

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 15
}

You should be all set now with all the options present in the body of the response (count, next and back links) ordered just like before the update.

However there is one more thing that still troubles me: We should also be able to get the new html pagination controls which for some reason are missing for now...

I could definitely use a couple more suggestions on this...

stratis
  • 7,750
  • 13
  • 53
  • 94
  • 2
    Wow. Is it really that simple? They should really update the documentation with an example. As far as the HTML links, I don't know, I've never really used the browsable API. – Brobin Mar 19 '15 at 13:59
  • 2
    cool! this solution save me so much time on checking their source code. I hope they make the doc asap. – haudoing Mar 31 '15 at 06:37
  • @haudoing: Make the docs you wish to see in the world! It's open source, and docs are a great way to give back. – mlissner Oct 29 '15 at 23:11
  • Will the same work in case of cursor pagination? No tutorial or API guide on how to use the pagination classes in DRF docs. – Babu Feb 01 '16 at 10:46
  • 1
    I checked and it works the same for cursor pagination. Infact DRF v3.x pagination is a huge improvement and works right out of the box. It's just the usage is not documented. – Babu Feb 02 '16 at 05:37
  • 1
    Found this answer because HTML pagination controls were missing for me, turns out you need to set `self.display_page_controls=True` in `paginate_queryset` if you override it. http://www.django-rest-framework.org/api-guide/pagination/#low-level-api – maryokhin Jul 27 '16 at 09:28
12

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!

Community
  • 1
  • 1
Brobin
  • 3,241
  • 2
  • 19
  • 35
  • Yes. Your solution works. It's not optimal yet as we can't get pagination links on the browsable API (and the pagination hyperlinks are gone) but currently it's the only one working. We should be very close now. Let me know if you come up with anything better otherwise I'll accept your answer in a couple of days. Thanks again. – stratis Mar 19 '15 at 10:07
  • That's also correct. We can do pretty much anything now..! I believe the key to the solution was how you replaced `class Meta: object_serializer_class = CourseSerializer` with a simple constructor `serializer = CourseSerializer(courses, many=True)`. Past that point we can let our imagination take over. Anyway, I prefer to stick with the defaults, but since you gave me the "key", I am accepting your answer. Thank you. – stratis Mar 20 '15 at 11:07
0

I realize over a year has passed since this was posted but hoping this helps others. The response to my similar question was the solution for me. I am using DRF 3.2.3.

Django Rest Framework 3.2.3 pagination not working for generics.ListCreateAPIView

Seeing how it was implemented gave me the solution needed to get pagination + the controls in the visible API.

https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py#L39

Community
  • 1
  • 1
cjukjones
  • 136
  • 1
  • 6