1 from django.core.paginator import Paginator, Page, PageNotAnInteger, EmptyPage
 
   4 class InfinitePaginator(Paginator):
 
   6     Paginator designed for cases when it's not important to know how many total
 
   7     pages.  This is useful for any object_list that has no count() method or can
 
   8     be used to improve performance for MySQL by removing counts.
 
  10     The orphans parameter has been removed for simplicity and there's a link
 
  11     template string for creating the links to the next and previous pages.
 
  14     def __init__(self, object_list, per_page, allow_empty_first_page=True,
 
  15         link_template='/page/%d/'):
 
  16         orphans = 0 # no orphans
 
  17         super(InfinitePaginator, self).__init__(object_list, per_page, orphans,
 
  18             allow_empty_first_page)
 
  19         # no count or num pages
 
  20         del self._num_pages, self._count
 
  22         self.link_template = link_template
 
  24     def validate_number(self, number):
 
  26         Validates the given 1-based page number.
 
  31             raise PageNotAnInteger('That page number is not an integer')
 
  33             raise EmptyPage('That page number is less than 1')
 
  36     def page(self, number):
 
  38         Returns a Page object for the given 1-based page number.
 
  40         number = self.validate_number(number)
 
  41         bottom = (number - 1) * self.per_page
 
  42         top = bottom + self.per_page
 
  43         page_items = self.object_list[bottom:top]
 
  44         # check moved from validate_number
 
  46             if number == 1 and self.allow_empty_first_page:
 
  49                 raise EmptyPage('That page contains no results')
 
  50         return InfinitePage(page_items, number, self)
 
  54         Returns the total number of objects, across all pages.
 
  56         raise NotImplementedError
 
  57     count = property(_get_count)
 
  59     def _get_num_pages(self):
 
  61         Returns the total number of pages.
 
  63         raise NotImplementedError
 
  64     num_pages = property(_get_num_pages)
 
  66     def _get_page_range(self):
 
  68         Returns a 1-based range of pages for iterating through within
 
  71         raise NotImplementedError
 
  72     page_range = property(_get_page_range)
 
  75 class InfinitePage(Page):
 
  78         return '<Page %s>' % self.number
 
  82         Checks for one more item than last on this page.
 
  85             self.paginator.object_list[self.number * self.paginator.per_page]
 
  92         Returns the 1-based index of the last object on this page,
 
  93         relative to total objects found (hits).
 
  95         return ((self.number - 1) * self.paginator.per_page +
 
  96             len(self.object_list))
 
  98     #Bonus methods for creating links
 
 102             return self.paginator.link_template % (self.number + 1)
 
 105     def previous_link(self):
 
 106         if self.has_previous():
 
 107             return self.paginator.link_template % (self.number - 1)
 
 111 class FinitePaginator(InfinitePaginator):
 
 113     Paginator for cases when the list of items is already finite.
 
 115     A good example is a list generated from an API call. This is a subclass
 
 116     of InfinitePaginator because we have no idea how many items exist in the
 
 119     To accurately determine if the next page exists, a FinitePaginator MUST be
 
 120     created with an object_list_plus that may contain more items than the
 
 121     per_page count.  Typically, you'll have an object_list_plus with one extra
 
 122     item (if there's a next page).  You'll also need to supply the offset from
 
 123     the full collection in order to get the page start_index.
 
 125     This is a very silly class but useful if you love the Django pagination
 
 129     def __init__(self, object_list_plus, per_page, offset=None,
 
 130         allow_empty_first_page=True, link_template='/page/%d/'):
 
 131         super(FinitePaginator, self).__init__(object_list_plus, per_page,
 
 132             allow_empty_first_page, link_template)
 
 135     def validate_number(self, number):
 
 136         super(FinitePaginator, self).validate_number(number)
 
 137         # check for an empty list to see if the page exists
 
 138         if not self.object_list:
 
 139             if number == 1 and self.allow_empty_first_page:
 
 142                 raise EmptyPage('That page contains no results')
 
 145     def page(self, number):
 
 147         Returns a Page object for the given 1-based page number.
 
 149         number = self.validate_number(number)
 
 150         # remove the extra item(s) when creating the page
 
 151         page_items = self.object_list[:self.per_page]
 
 152         return FinitePage(page_items, number, self)
 
 155 class FinitePage(InfinitePage):
 
 159         Checks for one more item than last on this page.
 
 162             self.paginator.object_list[self.paginator.per_page]
 
 167     def start_index(self):
 
 169         Returns the 1-based index of the first object on this page,
 
 170         relative to total objects in the paginator.
 
 172         ## TODO should this holler if you haven't defined the offset?
 
 173         return self.paginator.offset