152 lines
4.6 KiB
Python
Executable file
152 lines
4.6 KiB
Python
Executable file
import collections
|
|
import inspect
|
|
from math import ceil
|
|
|
|
from django.core.paginator import EmptyPage, InvalidPage
|
|
from django.http import Http404
|
|
from django.utils.functional import cached_property
|
|
from django.utils.inspect import method_has_no_args
|
|
|
|
|
|
class InfinitePage(collections.abc.Sequence):
|
|
def __init__(
|
|
self, object_list, number, unfiltered_queryset, page_size, pad_pages, paginator
|
|
):
|
|
self.object_list = list(object_list)
|
|
self.number = number
|
|
self.unfiltered_queryset = unfiltered_queryset
|
|
self.page_size = page_size
|
|
self.pad_pages = pad_pages
|
|
self.num_pages = 1e3000
|
|
self.paginator = paginator
|
|
|
|
def __repr__(self):
|
|
return "<Page %s of many>" % self.number
|
|
|
|
def __len__(self):
|
|
return len(self.object_list)
|
|
|
|
def __getitem__(self, index):
|
|
return self.object_list[index]
|
|
|
|
@cached_property
|
|
def _after_up_to_pad(self):
|
|
first_after = self.number * self.page_size
|
|
padding_length = self.pad_pages * self.page_size
|
|
queryset = self.unfiltered_queryset[
|
|
first_after : first_after + padding_length + 1
|
|
]
|
|
c = getattr(queryset, "count", None)
|
|
if callable(c) and not inspect.isbuiltin(c) and method_has_no_args(c):
|
|
return c()
|
|
return len(queryset)
|
|
|
|
def has_next(self):
|
|
return self._after_up_to_pad > 0
|
|
|
|
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():
|
|
raise EmptyPage()
|
|
return self.number + 1
|
|
|
|
def previous_page_number(self):
|
|
if self.number <= 1:
|
|
raise EmptyPage()
|
|
return self.number - 1
|
|
|
|
def start_index(self):
|
|
return (self.page_size * (self.number - 1)) + 1
|
|
|
|
def end_index(self):
|
|
return self.start_index() + len(self.object_list)
|
|
|
|
@cached_property
|
|
def main_range(self):
|
|
start = max(1, self.number - self.pad_pages)
|
|
end = self.number + min(
|
|
int(ceil(self._after_up_to_pad / self.page_size)), self.pad_pages
|
|
)
|
|
return range(start, end + 1)
|
|
|
|
@cached_property
|
|
def leading_range(self):
|
|
return range(1, min(3, self.main_range[0]))
|
|
|
|
@cached_property
|
|
def has_trailing(self):
|
|
return self._after_up_to_pad > self.pad_pages * self.page_size
|
|
|
|
@cached_property
|
|
def page_range(self):
|
|
result = list(self.leading_range)
|
|
main_range = self.main_range
|
|
|
|
# Add ... element if there is space in between.
|
|
if result and result[-1] + 1 < self.main_range[0]:
|
|
result.append(False)
|
|
|
|
result += list(main_range)
|
|
|
|
# Add ... element if there are elements after main_range.
|
|
if self.has_trailing:
|
|
result.append(False)
|
|
return result
|
|
|
|
|
|
class DummyPaginator:
|
|
is_infinite = True
|
|
|
|
def __init__(self, per_page):
|
|
self.per_page = per_page
|
|
|
|
|
|
def infinite_paginate(queryset, page, page_size, pad_pages, paginator=None):
|
|
if page < 1:
|
|
raise EmptyPage()
|
|
sliced = queryset[(page - 1) * page_size : page * page_size]
|
|
if page > 1 and not sliced:
|
|
raise EmptyPage()
|
|
return InfinitePage(sliced, page, queryset, page_size, pad_pages, paginator)
|
|
|
|
|
|
class InfinitePaginationMixin:
|
|
pad_pages = 4
|
|
|
|
@property
|
|
def use_infinite_pagination(self):
|
|
return True
|
|
|
|
def paginate_queryset(self, queryset, page_size):
|
|
if not self.use_infinite_pagination:
|
|
paginator, page, object_list, has_other = super().paginate_queryset(
|
|
queryset, page_size
|
|
)
|
|
paginator.is_infinite = False
|
|
return paginator, page, object_list, has_other
|
|
|
|
page_kwarg = self.page_kwarg
|
|
page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1
|
|
try:
|
|
page_number = int(page)
|
|
except ValueError:
|
|
raise Http404("Page cannot be converted to an int.")
|
|
try:
|
|
paginator = DummyPaginator(page_size)
|
|
page = infinite_paginate(
|
|
queryset, page_number, page_size, self.pad_pages, paginator
|
|
)
|
|
return paginator, page, page.object_list, page.has_other_pages()
|
|
except InvalidPage as e:
|
|
raise Http404(
|
|
"Invalid page (%(page_number)s): %(message)s"
|
|
% {
|
|
"page_number": page_number,
|
|
"message": str(e),
|
|
}
|
|
)
|