[RFC] REST: Use cursor-based pagination for '/events'

Stephen Finucane stephen at that.guru
Thu Jun 22 06:44:10 AEST 2017


Given the high-volume, time-based nature of this API, cursor-based
pagination provides some important usability improvements over page
number-based pagination, most notably in going back through historical
pages.

Signed-off-by: Stephen Finucane <stephen at that.guru>
Cc: Aaron Conole <aconole at bytheb.org>
---
 patchwork/api/base.py  | 30 +++++++++++++++++++++++-------
 patchwork/api/event.py |  4 +++-
 2 files changed, 26 insertions(+), 8 deletions(-)

diff --git a/patchwork/api/base.py b/patchwork/api/base.py
index 09b3bef..958dfc3 100644
--- a/patchwork/api/base.py
+++ b/patchwork/api/base.py
@@ -19,22 +19,20 @@
 
 from django.conf import settings
 from django.shortcuts import get_object_or_404
+from rest_framework import pagination
 from rest_framework import permissions
-from rest_framework.pagination import PageNumberPagination
 from rest_framework.response import Response
 from rest_framework.serializers import HyperlinkedIdentityField
 
 
-class LinkHeaderPagination(PageNumberPagination):
-    """Provide pagination based on rfc5988.
+class LinkHeaderMixin(object):
+    """Provide pagination based on RFC5988.
 
     This is the Link header, similar to how GitHub does it. See:
 
-       https://tools.ietf.org/html/rfc5988#section-5
-       https://developer.github.com/guides/traversing-with-pagination
+    - https://tools.ietf.org/html/rfc5988#section-5
+    - https://developer.github.com/guides/traversing-with-pagination
     """
-    page_size = max_page_size = settings.REST_RESULTS_PER_PAGE
-    page_size_query_param = 'per_page'
 
     def get_paginated_response(self, data):
         next_url = self.get_next_link()
@@ -47,11 +45,29 @@ class LinkHeaderPagination(PageNumberPagination):
             link = '<{next_url}>; rel="next"'
         elif previous_url is not None:
             link = '<{previous_url}>; rel="prev"'
+
         link = link.format(next_url=next_url, previous_url=previous_url)
         headers = {'Link': link} if link else {}
+
         return Response(data, headers=headers)
 
 
+
+class PageNumberPagination(LinkHeaderMixin, pagination.PageNumberPagination):
+    """RFC5988-based page-number pagination."""
+    page_size = max_page_size = settings.REST_RESULTS_PER_PAGE
+    page_size_query_param = 'per_page'
+
+
+class CursorPagination(LinkHeaderMixin, pagination.CursorPagination):
+    """RFC5988-based cursor pagination."""
+    page_size = 30
+    cursor_query_param = 'cursor'
+
+
+LinkHeaderPagination = PageNumberPagination
+
+
 class PatchworkPermission(permissions.BasePermission):
     """This permission works for Project and Patch model objects"""
     def has_object_permission(self, request, view, obj):
diff --git a/patchwork/api/event.py b/patchwork/api/event.py
index cc9270a..d333501 100644
--- a/patchwork/api/event.py
+++ b/patchwork/api/event.py
@@ -23,6 +23,7 @@ from rest_framework.generics import ListAPIView
 from rest_framework.serializers import ModelSerializer
 from rest_framework.serializers import SerializerMethodField
 
+from patchwork.api.base import CursorPagination
 from patchwork.api.embedded import CheckSerializer
 from patchwork.api.embedded import CoverLetterSerializer
 from patchwork.api.embedded import PatchSerializer
@@ -34,6 +35,7 @@ from patchwork.api.patch import StateField
 from patchwork.models import Event
 
 
+
 class EventSerializer(ModelSerializer):
 
     project = ProjectSerializer(read_only=True)
@@ -89,8 +91,8 @@ class EventList(ListAPIView):
     """List events."""
 
     serializer_class = EventSerializer
+    pagination_class = CursorPagination
     filter_class = EventFilter
-    page_size_query_param = None  # fixed page size
     ordering = '-date'
     ordering_fields = ()
 
-- 
2.9.4



More information about the Patchwork mailing list