paginator.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import collections
  2. from math import ceil
  3. from django.utils import six
  4. class InvalidPage(Exception):
  5. pass
  6. class PageNotAnInteger(InvalidPage):
  7. pass
  8. class EmptyPage(InvalidPage):
  9. pass
  10. class Paginator(object):
  11. def __init__(self, object_list, per_page, orphans=0,
  12. allow_empty_first_page=True):
  13. self.object_list = object_list
  14. self.per_page = int(per_page)
  15. self.orphans = int(orphans)
  16. self.allow_empty_first_page = allow_empty_first_page
  17. self._num_pages = self._count = None
  18. def validate_number(self, number):
  19. """
  20. Validates the given 1-based page number.
  21. """
  22. try:
  23. number = int(number)
  24. except (TypeError, ValueError):
  25. raise PageNotAnInteger('That page number is not an integer')
  26. if number < 1:
  27. raise EmptyPage('That page number is less than 1')
  28. if number > self.num_pages:
  29. if number == 1 and self.allow_empty_first_page:
  30. pass
  31. else:
  32. raise EmptyPage('That page contains no results')
  33. return number
  34. def page(self, number):
  35. """
  36. Returns a Page object for the given 1-based page number.
  37. """
  38. number = self.validate_number(number)
  39. bottom = (number - 1) * self.per_page
  40. top = bottom + self.per_page
  41. if top + self.orphans >= self.count:
  42. top = self.count
  43. return self._get_page(self.object_list[bottom:top], number, self)
  44. def _get_page(self, *args, **kwargs):
  45. """
  46. Returns an instance of a single page.
  47. This hook can be used by subclasses to use an alternative to the
  48. standard :cls:`Page` object.
  49. """
  50. return Page(*args, **kwargs)
  51. def _get_count(self):
  52. """
  53. Returns the total number of objects, across all pages.
  54. """
  55. if self._count is None:
  56. try:
  57. self._count = self.object_list.count()
  58. except (AttributeError, TypeError):
  59. # AttributeError if object_list has no count() method.
  60. # TypeError if object_list.count() requires arguments
  61. # (i.e. is of type list).
  62. self._count = len(self.object_list)
  63. return self._count
  64. count = property(_get_count)
  65. def _get_num_pages(self):
  66. """
  67. Returns the total number of pages.
  68. """
  69. if self._num_pages is None:
  70. if self.count == 0 and not self.allow_empty_first_page:
  71. self._num_pages = 0
  72. else:
  73. hits = max(1, self.count - self.orphans)
  74. self._num_pages = int(ceil(hits / float(self.per_page)))
  75. return self._num_pages
  76. num_pages = property(_get_num_pages)
  77. def _get_page_range(self):
  78. """
  79. Returns a 1-based range of pages for iterating through within
  80. a template for loop.
  81. """
  82. return list(six.moves.range(1, self.num_pages + 1))
  83. page_range = property(_get_page_range)
  84. QuerySetPaginator = Paginator # For backwards-compatibility.
  85. class Page(collections.Sequence):
  86. def __init__(self, object_list, number, paginator):
  87. self.object_list = object_list
  88. self.number = number
  89. self.paginator = paginator
  90. def __repr__(self):
  91. return '<Page %s of %s>' % (self.number, self.paginator.num_pages)
  92. def __len__(self):
  93. return len(self.object_list)
  94. def __getitem__(self, index):
  95. if not isinstance(index, (slice,) + six.integer_types):
  96. raise TypeError
  97. # The object_list is converted to a list so that if it was a QuerySet
  98. # it won't be a database hit per __getitem__.
  99. if not isinstance(self.object_list, list):
  100. self.object_list = list(self.object_list)
  101. return self.object_list[index]
  102. def has_next(self):
  103. return self.number < self.paginator.num_pages
  104. def has_previous(self):
  105. return self.number > 1
  106. def has_other_pages(self):
  107. return self.has_previous() or self.has_next()
  108. def next_page_number(self):
  109. return self.paginator.validate_number(self.number + 1)
  110. def previous_page_number(self):
  111. return self.paginator.validate_number(self.number - 1)
  112. def start_index(self):
  113. """
  114. Returns the 1-based index of the first object on this page,
  115. relative to total objects in the paginator.
  116. """
  117. # Special case, return zero if no items.
  118. if self.paginator.count == 0:
  119. return 0
  120. return (self.paginator.per_page * (self.number - 1)) + 1
  121. def end_index(self):
  122. """
  123. Returns the 1-based index of the last object on this page,
  124. relative to total objects found (hits).
  125. """
  126. # Special case for the last page because there can be orphans.
  127. if self.number == self.paginator.num_pages:
  128. return self.paginator.count
  129. return self.number * self.paginator.per_page