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

1import collections.abc 

2import inspect 

3import warnings 

4from math import ceil 

5 

6from django.utils.functional import cached_property 

7from django.utils.inspect import method_has_no_args 

8from django.utils.translation import gettext_lazy as _ 

9 

10 

11class UnorderedObjectListWarning(RuntimeWarning): 

12 pass 

13 

14 

15class InvalidPage(Exception): 

16 pass 

17 

18 

19class PageNotAnInteger(InvalidPage): 

20 pass 

21 

22 

23class EmptyPage(InvalidPage): 

24 pass 

25 

26 

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 = _("…") 

31 

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 

38 

39 def __iter__(self): 

40 for page_number in self.page_range: 

41 yield self.page(page_number) 

42 

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 

59 

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) 

72 

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) 

81 

82 def _get_page(self, *args, **kwargs): 

83 """ 

84 Return an instance of a single page. 

85 

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) 

90 

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) 

98 

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) 

106 

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) 

114 

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 ) 

134 

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. 

138 

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: 

143 

144 1, 2, …, 40, 41, 42, 43, 44, 45, 46, …, 49, 50. 

145 """ 

146 number = self.validate_number(number) 

147 

148 if self.num_pages <= (on_each_side + on_ends) * 2: 

149 yield from self.page_range 

150 return 

151 

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) 

158 

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) 

165 

166 

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 

172 

173 def __repr__(self): 

174 return "<Page %s of %s>" % (self.number, self.paginator.num_pages) 

175 

176 def __len__(self): 

177 return len(self.object_list) 

178 

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] 

190 

191 def has_next(self): 

192 return self.number < self.paginator.num_pages 

193 

194 def has_previous(self): 

195 return self.number > 1 

196 

197 def has_other_pages(self): 

198 return self.has_previous() or self.has_next() 

199 

200 def next_page_number(self): 

201 return self.paginator.validate_number(self.number + 1) 

202 

203 def previous_page_number(self): 

204 return self.paginator.validate_number(self.number - 1) 

205 

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 

215 

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