paginators.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. from django.core.paginator import EmptyPage, Page, PageNotAnInteger, Paginator
  2. from django.utils.translation import gettext as _
  3. class LazyPaginator(Paginator):
  4. """
  5. Implement lazy pagination, preventing any count() queries.
  6. By default, for any valid page, the total number of pages for the paginator will be
  7. - `current + 1` if the number of records fetched for the current page offset is
  8. bigger than the number of records per page.
  9. - `current` if the number of records fetched is less than the number of records per page.
  10. The number of additional records fetched can be adjusted using `look_ahead`, which
  11. defaults to 1 page. If you like to provide a little more extra information on how much
  12. pages follow the current page, you can use a higher value.
  13. .. note::
  14. The number of records fetched for each page is `per_page * look_ahead + 1`, so increasing
  15. the value for `look_ahead` makes the view a bit more expensive.
  16. So::
  17. paginator = LazyPaginator(range(10000), 10)
  18. >>> paginator.page(1).object_list
  19. [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  20. >>> paginator.num_pages
  21. 2
  22. >>> paginator.page(10).object_list
  23. [91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
  24. >>> paginator.num_pages
  25. 11
  26. >>> paginator.page(1000).object_list
  27. [9991, 9992, 9993, 9994, 9995, 9996, 9997, 9998, 9999]
  28. >>> paginator.num_pages
  29. 1000
  30. Usage with `~.SingleTableView`::
  31. class UserListView(SingleTableView):
  32. table_class = UserTable
  33. table_data = User.objects.all()
  34. pagination_class = LazyPaginator
  35. Or with `~.RequestConfig`::
  36. RequestConfig(paginate={"paginator_class": LazyPaginator}).configure(table)
  37. .. versionadded :: 2.0.0
  38. """
  39. look_ahead = 1
  40. def __init__(self, object_list, per_page, look_ahead=None, **kwargs):
  41. self._num_pages = None
  42. self._final_num_pages = None
  43. if look_ahead is not None:
  44. self.look_ahead = look_ahead
  45. super().__init__(object_list, per_page, **kwargs)
  46. def validate_number(self, number):
  47. """Validate the given 1-based page number."""
  48. try:
  49. if isinstance(number, float) and not number.is_integer():
  50. raise ValueError
  51. number = int(number)
  52. except (TypeError, ValueError):
  53. raise PageNotAnInteger(_("That page number is not an integer"))
  54. if number < 1:
  55. raise EmptyPage(_("That page number is less than 1"))
  56. return number
  57. def page(self, number):
  58. # Number might be None, because the total number of pages is not known in this paginator.
  59. # If an unknown page is requested, serve the first page.
  60. number = self.validate_number(number or 1)
  61. bottom = (number - 1) * self.per_page
  62. top = bottom + self.per_page
  63. # Retrieve more objects to check if there is a next page.
  64. look_ahead_items = (self.look_ahead - 1) * self.per_page + 1
  65. objects = list(self.object_list[bottom : top + self.orphans + look_ahead_items])
  66. objects_count = len(objects)
  67. if objects_count > (self.per_page + self.orphans):
  68. # If another page is found, increase the total number of pages.
  69. self._num_pages = number + (objects_count // self.per_page)
  70. # In any case, return only objects for this page.
  71. objects = objects[: self.per_page]
  72. elif (number != 1) and (objects_count <= self.orphans):
  73. raise EmptyPage(_("That page contains no results"))
  74. else:
  75. # This is the last page.
  76. self._num_pages = number
  77. # For rendering purposes in `table_page_range`, we have to remember the final count
  78. self._final_num_pages = number
  79. return Page(objects, number, self)
  80. def is_last_page(self, number):
  81. return number == self._final_num_pages
  82. def _get_count(self):
  83. raise NotImplementedError
  84. count = property(_get_count)
  85. def _get_num_pages(self):
  86. return self._num_pages
  87. num_pages = property(_get_num_pages)
  88. def _get_page_range(self):
  89. raise NotImplementedError
  90. page_range = property(_get_page_range)