|
1 class InvalidPage(Exception): |
|
2 pass |
|
3 |
|
4 class Paginator(object): |
|
5 def __init__(self, object_list, per_page, orphans=0, allow_empty_first_page=True): |
|
6 self.object_list = object_list |
|
7 self.per_page = per_page |
|
8 self.orphans = orphans |
|
9 self.allow_empty_first_page = allow_empty_first_page |
|
10 self._num_pages = self._count = None |
|
11 |
|
12 def validate_number(self, number): |
|
13 "Validates the given 1-based page number." |
|
14 try: |
|
15 number = int(number) |
|
16 except ValueError: |
|
17 raise InvalidPage('That page number is not an integer') |
|
18 if number < 1: |
|
19 raise InvalidPage('That page number is less than 1') |
|
20 if number > self.num_pages: |
|
21 if number == 1 and self.allow_empty_first_page: |
|
22 pass |
|
23 else: |
|
24 raise InvalidPage('That page contains no results') |
|
25 return number |
|
26 |
|
27 def page(self, number): |
|
28 "Returns a Page object for the given 1-based page number." |
|
29 number = self.validate_number(number) |
|
30 bottom = (number - 1) * self.per_page |
|
31 top = bottom + self.per_page |
|
32 if top + self.orphans >= self.count: |
|
33 top = self.count |
|
34 return Page(self.object_list[bottom:top], number, self) |
|
35 |
|
36 def _get_count(self): |
|
37 "Returns the total number of objects, across all pages." |
|
38 if self._count is None: |
|
39 self._count = len(self.object_list) |
|
40 return self._count |
|
41 count = property(_get_count) |
|
42 |
|
43 def _get_num_pages(self): |
|
44 "Returns the total number of pages." |
|
45 if self._num_pages is None: |
|
46 hits = self.count - 1 - self.orphans |
|
47 if hits < 1: |
|
48 hits = 0 |
|
49 if hits == 0 and not self.allow_empty_first_page: |
|
50 self._num_pages = 0 |
|
51 else: |
|
52 self._num_pages = hits // self.per_page + 1 |
|
53 return self._num_pages |
|
54 num_pages = property(_get_num_pages) |
|
55 |
|
56 def _get_page_range(self): |
|
57 """ |
|
58 Returns a 1-based range of pages for iterating through within |
|
59 a template for loop. |
|
60 """ |
|
61 return range(1, self.num_pages + 1) |
|
62 page_range = property(_get_page_range) |
|
63 |
|
64 class QuerySetPaginator(Paginator): |
|
65 """ |
|
66 Like Paginator, but works on QuerySets. |
|
67 """ |
|
68 def _get_count(self): |
|
69 if self._count is None: |
|
70 self._count = self.object_list.count() |
|
71 return self._count |
|
72 count = property(_get_count) |
|
73 |
|
74 class Page(object): |
|
75 def __init__(self, object_list, number, paginator): |
|
76 self.object_list = object_list |
|
77 self.number = number |
|
78 self.paginator = paginator |
|
79 |
|
80 def __repr__(self): |
|
81 return '<Page %s of %s>' % (self.number, self.paginator.num_pages) |
|
82 |
|
83 def has_next(self): |
|
84 return self.number < self.paginator.num_pages |
|
85 |
|
86 def has_previous(self): |
|
87 return self.number > 1 |
|
88 |
|
89 def has_other_pages(self): |
|
90 return self.has_previous() or self.has_next() |
|
91 |
|
92 def next_page_number(self): |
|
93 return self.number + 1 |
|
94 |
|
95 def previous_page_number(self): |
|
96 return self.number - 1 |
|
97 |
|
98 def start_index(self): |
|
99 """ |
|
100 Returns the 1-based index of the first object on this page, |
|
101 relative to total objects in the paginator. |
|
102 """ |
|
103 return (self.paginator.per_page * (self.number - 1)) + 1 |
|
104 |
|
105 def end_index(self): |
|
106 """ |
|
107 Returns the 1-based index of the last object on this page, |
|
108 relative to total objects found (hits). |
|
109 """ |
|
110 if self.number == self.paginator.num_pages: |
|
111 return self.paginator.count |
|
112 return self.number * self.paginator.per_page |
|
113 |
|
114 class ObjectPaginator(Paginator): |
|
115 """ |
|
116 Legacy ObjectPaginator class, for backwards compatibility. |
|
117 |
|
118 Note that each method on this class that takes page_number expects a |
|
119 zero-based page number, whereas the new API (Paginator/Page) uses one-based |
|
120 page numbers. |
|
121 """ |
|
122 def __init__(self, query_set, num_per_page, orphans=0): |
|
123 Paginator.__init__(self, query_set, num_per_page, orphans) |
|
124 import warnings |
|
125 warnings.warn("The ObjectPaginator is deprecated. Use django.core.paginator.Paginator instead.", DeprecationWarning) |
|
126 |
|
127 # Keep these attributes around for backwards compatibility. |
|
128 self.query_set = query_set |
|
129 self.num_per_page = num_per_page |
|
130 self._hits = self._pages = None |
|
131 |
|
132 def validate_page_number(self, page_number): |
|
133 try: |
|
134 page_number = int(page_number) + 1 |
|
135 except ValueError: |
|
136 raise InvalidPage |
|
137 return self.validate_number(page_number) |
|
138 |
|
139 def get_page(self, page_number): |
|
140 try: |
|
141 page_number = int(page_number) + 1 |
|
142 except ValueError: |
|
143 raise InvalidPage |
|
144 return self.page(page_number).object_list |
|
145 |
|
146 def has_next_page(self, page_number): |
|
147 return page_number < self.pages - 1 |
|
148 |
|
149 def has_previous_page(self, page_number): |
|
150 return page_number > 0 |
|
151 |
|
152 def first_on_page(self, page_number): |
|
153 """ |
|
154 Returns the 1-based index of the first object on the given page, |
|
155 relative to total objects found (hits). |
|
156 """ |
|
157 page_number = self.validate_page_number(page_number) |
|
158 return (self.num_per_page * (page_number - 1)) + 1 |
|
159 |
|
160 def last_on_page(self, page_number): |
|
161 """ |
|
162 Returns the 1-based index of the last object on the given page, |
|
163 relative to total objects found (hits). |
|
164 """ |
|
165 page_number = self.validate_page_number(page_number) |
|
166 if page_number == self.num_pages: |
|
167 return self.count |
|
168 return page_number * self.num_per_page |
|
169 |
|
170 def _get_count(self): |
|
171 # The old API allowed for self.object_list to be either a QuerySet or a |
|
172 # list. Here, we handle both. |
|
173 if self._count is None: |
|
174 try: |
|
175 self._count = self.object_list.count() |
|
176 except TypeError: |
|
177 self._count = len(self.object_list) |
|
178 return self._count |
|
179 count = property(_get_count) |
|
180 |
|
181 # The old API called it "hits" instead of "count". |
|
182 hits = count |
|
183 |
|
184 # The old API called it "pages" instead of "num_pages". |
|
185 pages = Paginator.num_pages |