Skip to content

Instantly share code, notes, and snippets.

@flisky
Last active August 29, 2015 14:01
Show Gist options
  • Save flisky/4986e51b403ddb2e22c5 to your computer and use it in GitHub Desktop.
Save flisky/4986e51b403ddb2e22c5 to your computer and use it in GitHub Desktop.
countless django paginator for django-rest-framework
import collections
from django.utils import six
from django.core.paginator import PageNotAnInteger, EmptyPage
class Paginator(object):
def __init__(self, object_list, per_page, orphans=0,
allow_empty_first_page=True):
self.object_list = object_list
self.per_page = int(per_page)
self.orphans = int(orphans)
self.allow_empty_first_page = allow_empty_first_page
self._num_pages = self._count = None
def validate_number(self, number):
"""
Validates the given 1-based page number.
"""
try:
number = int(number)
except (TypeError, ValueError):
raise PageNotAnInteger('That page number is not an integer')
if number < 1:
raise EmptyPage('That page number is less than 1')
return number
def page(self, number):
"""
Returns a Page object for the given 1-based page number.
"""
number = self.validate_number(number)
bottom = (number - 1) * self.per_page
top = bottom + self.per_page
return Page(self.object_list[bottom:top + 1], number, self)
class Page(collections.Sequence):
def __init__(self, object_list, number, paginator):
object_length = len(object_list)
if object_length > paginator.per_page:
self.object_list = object_list[:object_length - 1]
self._has_next = True
else:
self.object_list = object_list
self._has_next = False
# force evaluate queryset to get rid of all function
# since DRF checks it and then call with queryset.all
# which will hit db due to different queryset
self.object_list = list(object_list)
self.number = number
self.paginator = paginator
def __repr__(self):
return '<Page %s of N/A>' % (self.number,)
def __len__(self):
return len(self.object_list)
def __getitem__(self, index):
if not isinstance(index, (slice,) + six.integer_types):
raise TypeError
# The object_list is converted to a list so that if it was a QuerySet
# it won't be a database hit per __getitem__.
if not isinstance(self.object_list, list):
self.object_list = list(self.object_list)
return self.object_list[index]
def has_next(self):
return self._has_next
def has_previous(self):
return self.number > 1
def has_other_pages(self):
return self.has_previous() or self.has_next()
def next_page_number(self):
if not self._has_next:
if self.number == 1 and self.allow_empty_first_page:
pass
else:
raise EmptyPage('That page contains no results')
return self.number + 1
def previous_page_number(self):
if self.number < 1:
raise EmptyPage('That page number is less than 1')
return self.number - 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment