"""Customized Django paginators.""" from __future__ import unicode_literals from math import ceil from django.core.paginator import ( EmptyPage, Page, PageNotAnInteger, Paginator, ) class CustomPage(Page): """Handle different number of items on the first page.""" def start_index(self): """Return the 1-based index of the first item on this page.""" paginator = self.paginator # Special case, return zero if no items. if paginator.count == 0: return 0 elif self.number == 1: return 1 return ( (self.number - 2) * paginator.per_page + paginator.first_page + 1) def end_index(self): """Return the 1-based index of the last item on this page.""" paginator = self.paginator # Special case for the last page because there can be orphans. if self.number == paginator.num_pages: return paginator.count return (self.number - 1) * paginator.per_page + paginator.first_page class BasePaginator(Paginator): """A base paginator class subclassed by the other real paginators. Handle different number of items on the first page. """ def __init__(self, object_list, per_page, **kwargs): self._num_pages = None if 'first_page' in kwargs: self.first_page = kwargs.pop('first_page') else: self.first_page = per_page super(BasePaginator, self).__init__(object_list, per_page, **kwargs) def get_current_per_page(self, number): return self.first_page if number == 1 else self.per_page class DefaultPaginator(BasePaginator): """The default paginator used by this application.""" def page(self, number): number = self.validate_number(number) if number == 1: bottom = 0 else: bottom = ((number - 2) * self.per_page + self.first_page) top = bottom + self.get_current_per_page(number) if top + self.orphans >= self.count: top = self.count return CustomPage(self.object_list[bottom:top], number, self) def _get_num_pages(self): if self._num_pages is None: if self.count == 0 and not self.allow_empty_first_page: self._num_pages = 0 else: hits = max(0, self.count - self.orphans - self.first_page) try: self._num_pages = int(ceil(hits / float(self.per_page))) + 1 except ZeroDivisionError: self._num_pages = 0 # fallback to a safe value return self._num_pages num_pages = property(_get_num_pages) class LazyPaginator(BasePaginator): """Implement lazy pagination.""" def validate_number(self, number): try: number = int(number) except 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): number = self.validate_number(number) current_per_page = self.get_current_per_page(number) if number == 1: bottom = 0 else: bottom = ((number - 2) * self.per_page + self.first_page) top = bottom + current_per_page # Retrieve more objects to check if there is a next page. objects = list(self.object_list[bottom:top + self.orphans + 1]) objects_count = len(objects) if objects_count > (current_per_page + self.orphans): # If another page is found, increase the total number of pages. self._num_pages = number + 1 # In any case, return only objects for this page. objects = objects[:current_per_page] elif (number != 1) and (objects_count <= self.orphans): raise EmptyPage('That page contains no results') else: # This is the last page. self._num_pages = number return CustomPage(objects, number, self) def _get_count(self): raise NotImplementedError count = property(_get_count) def _get_num_pages(self): return self._num_pages num_pages = property(_get_num_pages) def _get_page_range(self): raise NotImplementedError page_range = property(_get_page_range)