Ticket #2093: better_paginator_with_paginator_page_addition.2.patch
File better_paginator_with_paginator_page_addition.2.patch, 7.3 KB (added by , 18 years ago) |
---|
-
django/core/paginator.py
1 from math import ceil2 3 1 class InvalidPage(Exception): 4 2 pass 5 3 6 4 class ObjectPaginator(object): 7 5 """ 8 This class makes pagination easy. Feed it a QuerySet , plus the number of9 o bjects you want on each page. Then read the hits and pages properties to6 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 10 8 see how many pages it involves. Call get_page with a page number (starting 11 9 at 0) to get back a list of objects for that page. 12 10 13 11 Finally, check if a page number has a next/prev page using 14 12 has_next_page(page_number) and has_previous_page(page_number). 13 14 Use orphan to avoid small final pages. For example: 15 13 records, num_per_page=10, orphan=2 --> pages==2, len(self.get_page(0))==10 16 12 records, num_per_page=10, orphan=2 --> pages==1, len(self.get_page(0))==12 15 17 """ 16 def __init__(self, query_set, num_per_page ):18 def __init__(self, query_set, num_per_page, orphan=0): 17 19 self.query_set = query_set 18 20 self.num_per_page = num_per_page 19 self. _hits, self._pages = None, None20 self._h as_next = {} # Caches page_number -> has_next_boolean21 self.orphan = orphan 22 self._hits = self._pages = None 21 23 22 def get_page(self, page_number):24 def validate_page_number(self, page_number): 23 25 try: 24 26 page_number = int(page_number) 25 27 except ValueError: 26 28 raise InvalidPage 27 if page_number < 0 :29 if page_number < 0 or page_number > self.pages - 1: 28 30 raise InvalidPage 31 return page_number 29 32 30 # Retrieve one extra record, and check for the existence of that extra 31 # record to determine whether there's a next page. 32 limit = self.num_per_page + 1 33 offset = page_number * self.num_per_page 33 def get_page(self, page_number): 34 page_number = self.validate_page_number(page_number) 35 bottom = page_number * self.num_per_page 36 top = bottom + self.num_per_page 37 if top + self.orphan >= self.hits: 38 top = self.hits 39 return self.query_set[bottom:top] 34 40 35 object_list = list(self.query_set[offset:offset+limit])36 37 if not object_list:38 raise InvalidPage39 40 self._has_next[page_number] = (len(object_list) > self.num_per_page)41 return object_list[:self.num_per_page]42 43 41 def has_next_page(self, page_number): 44 42 "Does page $page_number have a 'next' page?" 45 if not self._has_next.has_key(page_number): 46 if self._pages is None: 47 offset = (page_number + 1) * self.num_per_page 48 self._has_next[page_number] = len(self.query_set[offset:offset+1]) > 0 49 else: 50 self._has_next[page_number] = page_number < (self.pages - 1) 51 return self._has_next[page_number] 43 return page_number < self.pages - 1 52 44 53 45 def has_previous_page(self, page_number): 54 46 return page_number > 0 … … 58 50 Returns the 1-based index of the first object on the given page, 59 51 relative to total objects found (hits). 60 52 """ 61 if page_number == 0: 62 return 1 53 page_number = self.validate_page_number(page_number) 63 54 return (self.num_per_page * page_number) + 1 64 55 65 56 def last_on_page(self, page_number): … … 67 58 Returns the 1-based index of the last object on the given page, 68 59 relative to total objects found (hits). 69 60 """ 70 if page_number == 0 and self.num_per_page >= self._hits:71 return self._hits72 elif page_number == (self._pages - 1) and (page_number + 1) * self.num_per_page > self._hits:73 return self. _hits74 return (page_number + 1)* self.num_per_page61 page_number = self.validate_page_number(page_number) 62 page_number += 1 # 1-base 63 if page_number == self.pages: 64 return self.hits 65 return page_number * self.num_per_page 75 66 76 67 def _get_hits(self): 77 68 if self._hits is None: 78 self._hits = self.query_set.count() 69 # If query_set is a list or tuple, use len(self.query_set) otherwise assume 70 # that it is a QuerySet and use .count(). 71 if isinstance(self.query_set, list) or isinstance(self.query_set, tuple): 72 self._hits = len(self.query_set) 73 else: 74 self._hits = self.query_set.count() 79 75 return self._hits 80 76 81 77 def _get_pages(self): 82 78 if self._pages is None: 83 self._pages = int(ceil(self.hits / float(self.num_per_page))) 79 hits = (self.hits - 1 - self.orphan) 80 if hits < 1: 81 hits = 0 82 self._pages = hits // self.num_per_page + 1 84 83 return self._pages 85 84 86 85 hits = property(_get_hits) 87 86 pages = property(_get_pages) 87 88 89 class PaginatorPage(object): 90 """ 91 This class is used to work with a page of a paginator object. 92 Pass a paginator object, and a valid page number. 93 It uses the same methods as a paginator, except you do not need 94 to use a page number, and also provides some other useful 95 properties. 96 97 `page_number` is always 0-based to match the Paginator object. 98 `base` allows next_page_number, previous_page_number and 99 page_number return their numbers using a different base. 100 101 For example: 102 >>> page = PaginatorPage(paginator, 0, base=1) 103 >>> page.next_page_number() 104 2 105 >>> page.page_number 106 1 107 """ 108 def __init__(self, paginator, page_number, base=0): 109 self.paginator = paginator 110 # _page_number remains the base-0 page number 111 self._page_number = paginator.validate_page_number(page_number) 112 self.base = int(base) 113 114 def get_page(self): 115 if not hasattr(self, '_page'): 116 self._page = self.paginator.get_page(self._page_number) 117 return self._page 118 119 def has_next_page(self): 120 return self.paginator.has_next_page(self._page_number) 121 122 def has_previous_page(self): 123 return self.paginator.has_previous_page(self._page_number) 124 125 def first_on_page(self): 126 return self.paginator.first_on_page(self._page_number) 127 128 def last_on_page(self): 129 return self.paginator.last_on_page(self._page_number) 130 131 def _get_hits(self): 132 return self.paginator.hits 133 hits = property(_get_hits) 134 135 def _get_pages(self): 136 return self.paginator.pages 137 pages = property(_get_pages) 138 139 def _get_page_number(self): 140 self._page_number + base 141 page_number = property(_get_page_number) 142 143 def _get_next_page_number(self): 144 if self.has_next_page(): 145 # Uses page_number so that we return the correct base 146 return self.page_number + 1 147 next_page_number = property(_get_next_page_number) 148 149 def _get_previous_page_number(self): 150 if self.has_previous_page(): 151 # Uses page_number so that we return the correct base 152 return self.page_number - 1 153 previous_page_number = property(_get_previous_page_number)