1 # Copyright (c) 2008, Eric Florenzano
2 # Copyright (c) 2010, 2011 Linaro Limited
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are
9 # * Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 # * Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following
13 # disclaimer in the documentation and/or other materials provided
14 # with the distribution.
15 # * Neither the name of the author nor the names of other
16 # contributors may be used to endorse or promote products derived
17 # from this software without specific prior written permission.
19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 from django.core.paginator import Paginator, Page, PageNotAnInteger, EmptyPage
35 class InfinitePaginator(Paginator):
37 Paginator designed for cases when it's not important to know how many total
38 pages. This is useful for any object_list that has no count() method or can
39 be used to improve performance for MySQL by removing counts.
41 The orphans parameter has been removed for simplicity and there's a link
42 template string for creating the links to the next and previous pages.
45 def __init__(self, object_list, per_page, allow_empty_first_page=True,
46 link_template='/page/%d/'):
47 orphans = 0 # no orphans
48 super(InfinitePaginator, self).__init__(object_list, per_page, orphans,
49 allow_empty_first_page)
50 # no count or num pages
51 del self._num_pages, self._count
53 self.link_template = link_template
55 def validate_number(self, number):
57 Validates the given 1-based page number.
62 raise PageNotAnInteger('That page number is not an integer')
64 raise EmptyPage('That page number is less than 1')
67 def page(self, number):
69 Returns a Page object for the given 1-based page number.
71 number = self.validate_number(number)
72 bottom = (number - 1) * self.per_page
73 top = bottom + self.per_page
74 page_items = self.object_list[bottom:top]
75 # check moved from validate_number
77 if number == 1 and self.allow_empty_first_page:
80 raise EmptyPage('That page contains no results')
81 return InfinitePage(page_items, number, self)
85 Returns the total number of objects, across all pages.
87 raise NotImplementedError
88 count = property(_get_count)
90 def _get_num_pages(self):
92 Returns the total number of pages.
94 raise NotImplementedError
95 num_pages = property(_get_num_pages)
97 def _get_page_range(self):
99 Returns a 1-based range of pages for iterating through within
102 raise NotImplementedError
103 page_range = property(_get_page_range)
106 class InfinitePage(Page):
109 return '<Page %s>' % self.number
113 Checks for one more item than last on this page.
116 self.paginator.object_list[self.number * self.paginator.per_page]
123 Returns the 1-based index of the last object on this page,
124 relative to total objects found (hits).
126 return ((self.number - 1) * self.paginator.per_page +
127 len(self.object_list))
129 #Bonus methods for creating links
133 return self.paginator.link_template % (self.number + 1)
136 def previous_link(self):
137 if self.has_previous():
138 return self.paginator.link_template % (self.number - 1)
142 class FinitePaginator(InfinitePaginator):
144 Paginator for cases when the list of items is already finite.
146 A good example is a list generated from an API call. This is a subclass
147 of InfinitePaginator because we have no idea how many items exist in the
150 To accurately determine if the next page exists, a FinitePaginator MUST be
151 created with an object_list_plus that may contain more items than the
152 per_page count. Typically, you'll have an object_list_plus with one extra
153 item (if there's a next page). You'll also need to supply the offset from
154 the full collection in order to get the page start_index.
156 This is a very silly class but useful if you love the Django pagination
160 def __init__(self, object_list_plus, per_page, offset=None,
161 allow_empty_first_page=True, link_template='/page/%d/'):
162 super(FinitePaginator, self).__init__(object_list_plus, per_page,
163 allow_empty_first_page, link_template)
166 def validate_number(self, number):
167 super(FinitePaginator, self).validate_number(number)
168 # check for an empty list to see if the page exists
169 if not self.object_list:
170 if number == 1 and self.allow_empty_first_page:
173 raise EmptyPage('That page contains no results')
176 def page(self, number):
178 Returns a Page object for the given 1-based page number.
180 number = self.validate_number(number)
181 # remove the extra item(s) when creating the page
182 page_items = self.object_list[:self.per_page]
183 return FinitePage(page_items, number, self)
186 class FinitePage(InfinitePage):
190 Checks for one more item than last on this page.
193 self.paginator.object_list[self.paginator.per_page]
198 def start_index(self):
200 Returns the 1-based index of the first object on this page,
201 relative to total objects in the paginator.
203 ## TODO should this holler if you haven't defined the offset?
204 return self.paginator.offset