Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/core/paginator.py: 58%
125 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1import collections.abc
2import inspect
3import warnings
4from math import ceil
6from django.utils.functional import cached_property
7from django.utils.inspect import method_has_no_args
8from django.utils.translation import gettext_lazy as _
11class UnorderedObjectListWarning(RuntimeWarning):
12 pass
15class InvalidPage(Exception):
16 pass
19class PageNotAnInteger(InvalidPage):
20 pass
23class EmptyPage(InvalidPage):
24 pass
27class Paginator:
28 # Translators: String used to replace omitted page numbers in elided page
29 # range generated by paginators, e.g. [1, 2, '…', 5, 6, 7, '…', 9, 10].
30 ELLIPSIS = _("…")
32 def __init__(self, object_list, per_page, orphans=0, allow_empty_first_page=True):
33 self.object_list = object_list
34 self._check_object_list_is_ordered()
35 self.per_page = int(per_page)
36 self.orphans = int(orphans)
37 self.allow_empty_first_page = allow_empty_first_page
39 def __iter__(self):
40 for page_number in self.page_range:
41 yield self.page(page_number)
43 def validate_number(self, number):
44 """Validate the given 1-based page number."""
45 try:
46 if isinstance(number, float) and not number.is_integer(): 46 ↛ 47line 46 didn't jump to line 47, because the condition on line 46 was never true
47 raise ValueError
48 number = int(number)
49 except (TypeError, ValueError):
50 raise PageNotAnInteger(_("That page number is not an integer"))
51 if number < 1: 51 ↛ 52line 51 didn't jump to line 52, because the condition on line 51 was never true
52 raise EmptyPage(_("That page number is less than 1"))
53 if number > self.num_pages: 53 ↛ 54line 53 didn't jump to line 54, because the condition on line 53 was never true
54 if number == 1 and self.allow_empty_first_page:
55 pass
56 else:
57 raise EmptyPage(_("That page contains no results"))
58 return number
60 def get_page(self, number):
61 """
62 Return a valid page, even if the page argument isn't a number or isn't
63 in range.
64 """
65 try:
66 number = self.validate_number(number)
67 except PageNotAnInteger:
68 number = 1
69 except EmptyPage:
70 number = self.num_pages
71 return self.page(number)
73 def page(self, number):
74 """Return a Page object for the given 1-based page number."""
75 number = self.validate_number(number)
76 bottom = (number - 1) * self.per_page
77 top = bottom + self.per_page
78 if top + self.orphans >= self.count: 78 ↛ 80line 78 didn't jump to line 80, because the condition on line 78 was never false
79 top = self.count
80 return self._get_page(self.object_list[bottom:top], number, self)
82 def _get_page(self, *args, **kwargs):
83 """
84 Return an instance of a single page.
86 This hook can be used by subclasses to use an alternative to the
87 standard :cls:`Page` object.
88 """
89 return Page(*args, **kwargs)
91 @cached_property
92 def count(self):
93 """Return the total number of objects, across all pages."""
94 c = getattr(self.object_list, "count", None)
95 if callable(c) and not inspect.isbuiltin(c) and method_has_no_args(c): 95 ↛ 97line 95 didn't jump to line 97, because the condition on line 95 was never false
96 return c()
97 return len(self.object_list)
99 @cached_property
100 def num_pages(self):
101 """Return the total number of pages."""
102 if self.count == 0 and not self.allow_empty_first_page: 102 ↛ 103line 102 didn't jump to line 103, because the condition on line 102 was never true
103 return 0
104 hits = max(1, self.count - self.orphans)
105 return ceil(hits / self.per_page)
107 @property
108 def page_range(self):
109 """
110 Return a 1-based range of pages for iterating through within
111 a template for loop.
112 """
113 return range(1, self.num_pages + 1)
115 def _check_object_list_is_ordered(self):
116 """
117 Warn if self.object_list is unordered (typically a QuerySet).
118 """
119 ordered = getattr(self.object_list, "ordered", None)
120 if ordered is not None and not ordered: 120 ↛ 121line 120 didn't jump to line 121
121 obj_list_repr = (
122 "{} {}".format(
123 self.object_list.model, self.object_list.__class__.__name__
124 )
125 if hasattr(self.object_list, "model")
126 else "{!r}".format(self.object_list)
127 )
128 warnings.warn(
129 "Pagination may yield inconsistent results with an unordered "
130 "object_list: {}.".format(obj_list_repr),
131 UnorderedObjectListWarning,
132 stacklevel=3,
133 )
135 def get_elided_page_range(self, number=1, *, on_each_side=3, on_ends=2):
136 """
137 Return a 1-based range of pages with some values elided.
139 If the page range is larger than a given size, the whole range is not
140 provided and a compact form is returned instead, e.g. for a paginator
141 with 50 pages, if page 43 were the current page, the output, with the
142 default arguments, would be:
144 1, 2, …, 40, 41, 42, 43, 44, 45, 46, …, 49, 50.
145 """
146 number = self.validate_number(number)
148 if self.num_pages <= (on_each_side + on_ends) * 2:
149 yield from self.page_range
150 return
152 if number > (1 + on_each_side + on_ends) + 1:
153 yield from range(1, on_ends + 1)
154 yield self.ELLIPSIS
155 yield from range(number - on_each_side, number + 1)
156 else:
157 yield from range(1, number + 1)
159 if number < (self.num_pages - on_each_side - on_ends) - 1:
160 yield from range(number + 1, number + on_each_side + 1)
161 yield self.ELLIPSIS
162 yield from range(self.num_pages - on_ends + 1, self.num_pages + 1)
163 else:
164 yield from range(number + 1, self.num_pages + 1)
167class Page(collections.abc.Sequence):
168 def __init__(self, object_list, number, paginator):
169 self.object_list = object_list
170 self.number = number
171 self.paginator = paginator
173 def __repr__(self):
174 return "<Page %s of %s>" % (self.number, self.paginator.num_pages)
176 def __len__(self):
177 return len(self.object_list)
179 def __getitem__(self, index):
180 if not isinstance(index, (int, slice)): 180 ↛ 181line 180 didn't jump to line 181, because the condition on line 180 was never true
181 raise TypeError(
182 "Page indices must be integers or slices, not %s."
183 % type(index).__name__
184 )
185 # The object_list is converted to a list so that if it was a QuerySet
186 # it won't be a database hit per __getitem__.
187 if not isinstance(self.object_list, list):
188 self.object_list = list(self.object_list)
189 return self.object_list[index]
191 def has_next(self):
192 return self.number < self.paginator.num_pages
194 def has_previous(self):
195 return self.number > 1
197 def has_other_pages(self):
198 return self.has_previous() or self.has_next()
200 def next_page_number(self):
201 return self.paginator.validate_number(self.number + 1)
203 def previous_page_number(self):
204 return self.paginator.validate_number(self.number - 1)
206 def start_index(self):
207 """
208 Return the 1-based index of the first object on this page,
209 relative to total objects in the paginator.
210 """
211 # Special case, return zero if no items.
212 if self.paginator.count == 0:
213 return 0
214 return (self.paginator.per_page * (self.number - 1)) + 1
216 def end_index(self):
217 """
218 Return the 1-based index of the last object on this page,
219 relative to total objects found (hits).
220 """
221 # Special case for the last page because there can be orphans.
222 if self.number == self.paginator.num_pages:
223 return self.paginator.count
224 return self.number * self.paginator.per_page