Changeset 7306
- Timestamp:
- 03/18/08 16:13:48 (1 year ago)
- Files:
-
- django/trunk/django/core/paginator.py (modified) (3 diffs)
- django/trunk/tests/modeltests/pagination/models.py (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
django/trunk/django/core/paginator.py
r6702 r7306 2 2 pass 3 3 4 class ObjectPaginator(object): 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): 5 65 """ 6 This class makes pagination easy. Feed it a QuerySet or list, plus the number 7 of objects you want on each page. Then read the hits and pages properties to 8 see how many pages it involves. Call get_page with a page number (starting 9 at 0) to get back a list of objects for that page. 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) 10 73 11 Finally, check if a page number has a next/prev page using 12 has_next_page(page_number) and has_previous_page(page_number). 13 14 Use orphans to avoid small final pages. For example: 15 13 records, num_per_page=10, orphans=2 --> pages==2, len(self.get_page(0))==10 16 12 records, num_per_page=10, orphans=2 --> pages==1, len(self.get_page(0))==12 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. 17 121 """ 18 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. 19 128 self.query_set = query_set 20 129 self.num_per_page = num_per_page 21 self.orphans = orphans22 130 self._hits = self._pages = None 23 self._page_range = None24 131 25 132 def validate_page_number(self, page_number): 26 133 try: 27 page_number = int(page_number) 134 page_number = int(page_number) + 1 28 135 except ValueError: 29 136 raise InvalidPage 30 if page_number < 0 or page_number > self.pages - 1: 31 raise InvalidPage 32 return page_number 137 return self.validate_number(page_number) 33 138 34 139 def get_page(self, page_number): 35 page_number = self.validate_page_number(page_number) 36 bottom = page_number * self.num_per_page 37 top = bottom + self.num_per_page 38 if top + self.orphans >= self.hits: 39 top = self.hits 40 return self.query_set[bottom:top] 140 try: 141 page_number = int(page_number) + 1 142 except ValueError: 143 raise InvalidPage 144 return self.page(page_number).object_list 41 145 42 146 def has_next_page(self, page_number): 43 "Does page $page_number have a 'next' page?"44 147 return page_number < self.pages - 1 45 148 … … 53 156 """ 54 157 page_number = self.validate_page_number(page_number) 55 return (self.num_per_page * page_number) + 1158 return (self.num_per_page * (page_number - 1)) + 1 56 159 57 160 def last_on_page(self, page_number): … … 61 164 """ 62 165 page_number = self.validate_page_number(page_number) 63 page_number += 1 # 1-base 64 if page_number == self.pages: 65 return self.hits 166 if page_number == self.num_pages: 167 return self.count 66 168 return page_number * self.num_per_page 67 169 68 def _get_hits(self): 69 if self._hits is None: 70 # Try .count() or fall back to len(). 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: 71 174 try: 72 self._hits = int(self.query_set.count()) 73 except (AttributeError, TypeError, ValueError): 74 # AttributeError if query_set has no object count. 75 # TypeError if query_set.count() required arguments. 76 # ValueError if int() fails. 77 self._hits = len(self.query_set) 78 return self._hits 175 self._count = self.object_list.count() 176 except AttributeError: 177 self._count = len(self.object_list) 178 return self._count 179 count = property(_get_count) 79 180 80 def _get_pages(self): 81 if self._pages is None: 82 hits = (self.hits - 1 - self.orphans) 83 if hits < 1: 84 hits = 0 85 self._pages = hits // self.num_per_page + 1 86 return self._pages 87 88 def _get_page_range(self): 89 """ 90 Returns a 1-based range of pages for iterating through within 91 a template for loop. 92 """ 93 if self._page_range is None: 94 self._page_range = range(1, self.pages + 1) 95 return self._page_range 181 # The old API called it "hits" instead of "count". 182 hits = count 96 183 97 hits = property(_get_hits) 98 pages = property(_get_pages) 99 page_range = property(_get_page_range) 184 # The old API called it "pages" instead of "num_pages". 185 pages = Paginator.num_pages django/trunk/tests/modeltests/pagination/models.py
r6702 r7306 5 5 of code. This is often useful for dividing search results or long lists of 6 6 objects into easily readable pages. 7 8 In Django 0.96 and earlier, a single ObjectPaginator class implemented this 9 functionality. In the Django development version, the behavior is split across 10 two classes -- Paginator and Page -- that are more easier to use. The legacy 11 ObjectPaginator class is deprecated. 7 12 """ 8 13 … … 17 22 18 23 __test__ = {'API_TESTS':""" 19 # prepare a list of objects for pagination24 # Prepare a list of objects for pagination. 20 25 >>> from datetime import datetime 21 26 >>> for x in range(1, 10): … … 23 28 ... a.save() 24 29 25 # create a basic paginator, 5 articles per page 30 #################################### 31 # New/current API (Paginator/Page) # 32 #################################### 33 34 >>> from django.core.paginator import Paginator, InvalidPage 35 >>> paginator = Paginator(Article.objects.all(), 5) 36 >>> paginator.count 37 9 38 >>> paginator.num_pages 39 2 40 >>> paginator.page_range 41 [1, 2] 42 43 # Get the first page. 44 >>> p = paginator.page(1) 45 >>> p 46 <Page 1 of 2> 47 >>> p.object_list 48 [<Article: Article 1>, <Article: Article 2>, <Article: Article 3>, <Article: Article 4>, <Article: Article 5>] 49 >>> p.has_next() 50 True 51 >>> p.has_previous() 52 False 53 >>> p.has_other_pages() 54 True 55 >>> p.next_page_number() 56 2 57 >>> p.previous_page_number() 58 0 59 >>> p.start_index() 60 1 61 >>> p.end_index() 62 5 63 64 # Get the second page. 65 >>> p = paginator.page(2) 66 >>> p 67 <Page 2 of 2> 68 >>> p.object_list 69 [<Article: Article 6>, <Article: Article 7>, <Article: Article 8>, <Article: Article 9>] 70 >>> p.has_next() 71 False 72 >>> p.has_previous() 73 True 74 >>> p.has_other_pages() 75 True 76 >>> p.next_page_number() 77 3 78 >>> p.previous_page_number() 79 1 80 >>> p.start_index() 81 6 82 >>> p.end_index() 83 9 84 85 # Invalid pages raise InvalidPage. 86 >>> paginator.page(0) 87 Traceback (most recent call last): 88 ... 89 InvalidPage: ... 90 >>> paginator.page(3) 91 Traceback (most recent call last): 92 ... 93 InvalidPage: ... 94 95 # Empty paginators with allow_empty_first_page=True. 96 >>> paginator = Paginator(Article.objects.filter(id=0), 5, allow_empty_first_page=True) 97 >>> paginator.count 98 0 99 >>> paginator.num_pages 100 1 101 >>> paginator.page_range 102 [1] 103 104 # Empty paginators with allow_empty_first_page=False. 105 >>> paginator = Paginator(Article.objects.filter(id=0), 5, allow_empty_first_page=False) 106 >>> paginator.count 107 0 108 >>> paginator.num_pages 109 0 110 >>> paginator.page_range 111 [] 112 113 # Paginators work with regular lists/tuples, too -- not just with QuerySets. 114 >>> paginator = Paginator([1, 2, 3, 4, 5, 6, 7, 8, 9], 5) 115 >>> paginator.count 116 9 117 >>> paginator.num_pages 118 2 119 >>> paginator.page_range 120 [1, 2] 121 122 # Get the first page. 123 >>> p = paginator.page(1) 124 >>> p 125 <Page 1 of 2> 126 >>> p.object_list 127 [1, 2, 3, 4, 5] 128 >>> p.has_next() 129 True 130 >>> p.has_previous() 131 False 132 >>> p.has_other_pages() 133 True 134 >>> p.next_page_number() 135 2 136 >>> p.previous_page_number() 137 0 138 >>> p.start_index() 139 1 140 >>> p.end_index() 141 5 142 143 ################################ 144 # Legacy API (ObjectPaginator) # 145 ################################ 146 26 147 >>> from django.core.paginator import ObjectPaginator, InvalidPage 27 148 >>> paginator = ObjectPaginator(Article.objects.all(), 5) 28 29 # the paginator knows how many hits and pages it contains30 149 >>> paginator.hits 31 150 9 32 33 151 >>> paginator.pages 34 152 2 35 36 # get the first page (zero-based) 153 >>> paginator.page_range 154 [1, 2] 155 156 # Get the first page. 37 157 >>> paginator.get_page(0) 38 158 [<Article: Article 1>, <Article: Article 2>, <Article: Article 3>, <Article: Article 4>, <Article: Article 5>] 39 40 # get the second page 159 >>> paginator.has_next_page(0) 160 True 161 >>> paginator.has_previous_page(0) 162 False 163 >>> paginator.first_on_page(0) 164 1 165 >>> paginator.last_on_page(0) 166 5 167 168 # Get the second page. 41 169 >>> paginator.get_page(1) 42 170 [<Article: Article 6>, <Article: Article 7>, <Article: Article 8>, <Article: Article 9>] 43 44 # does the first page have a next or previous page?45 >>> paginator.has_next_page(0)46 True47 48 >>> paginator.has_previous_page(0)49 False50 51 # check the second page52 171 >>> paginator.has_next_page(1) 53 172 False 54 55 173 >>> paginator.has_previous_page(1) 56 174 True 57 58 >>> paginator.first_on_page(0)59 160 175 >>> paginator.first_on_page(1) 61 176 6 62 >>> paginator.last_on_page(0)63 564 177 >>> paginator.last_on_page(1) 65 178 9 179 180 # Invalid pages raise InvalidPage. 181 >>> paginator.get_page(-1) 182 Traceback (most recent call last): 183 ... 184 InvalidPage: ... 185 >>> paginator.get_page(2) 186 Traceback (most recent call last): 187 ... 188 InvalidPage: ... 189 190 # Empty paginators with allow_empty_first_page=True. 191 >>> paginator = ObjectPaginator(Article.objects.filter(id=0), 5) 192 >>> paginator.count 193 0 194 >>> paginator.num_pages 195 1 196 >>> paginator.page_range 197 [1] 198 199 ################## 200 # Orphan support # 201 ################## 66 202 67 203 # Add a few more records to test out the orphans feature. … … 69 205 ... Article(headline="Article %s" % x, pub_date=datetime(2006, 10, 6)).save() 70 206 71 # With orphans set to 3 and 10 items per page, we should get all 12 items on a single page: 207 # With orphans set to 3 and 10 items per page, we should get all 12 items on a single page. 208 >>> paginator = Paginator(Article.objects.all(), 10, orphans=3) 209 >>> paginator.num_pages 210 1 211 212 # With orphans only set to 1, we should get two pages. 213 >>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=1) 214 >>> paginator.num_pages 215 2 216 217 # LEGACY: With orphans set to 3 and 10 items per page, we should get all 12 items on a single page. 72 218 >>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=3) 73 219 >>> paginator.pages 74 220 1 75 221 76 # With orphans only set to 1, we should get two pages:222 # LEGACY: With orphans only set to 1, we should get two pages. 77 223 >>> paginator = ObjectPaginator(Article.objects.all(), 10, orphans=1) 78 224 >>> paginator.pages 79 225 2 80 81 # The paginator can provide a list of all available pages.82 >>> paginator = ObjectPaginator(Article.objects.all(), 10)83 >>> paginator.page_range84 [1, 2]85 226 """}
