Ticket #2093: better_paginator_with_paginator_page_addition.patch
File better_paginator_with_paginator_page_addition.patch, 6.2 KB (added by , 18 years ago) |
---|
-
paginator.py
1 from copy import copy2 from math import ceil3 4 1 class InvalidPage(Exception): 5 2 pass 6 3 7 4 class ObjectPaginator(object): 8 5 """ 9 This class makes pagination easy. Feed it a QuerySet , plus the number of10 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 11 8 see how many pages it involves. Call get_page with a page number (starting 12 9 at 0) to get back a list of objects for that page. 13 10 14 11 Finally, check if a page number has a next/prev page using 15 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 16 17 """ 17 def __init__(self, query_set, num_per_page ):18 def __init__(self, query_set, num_per_page, orphan=0): 18 19 self.query_set = query_set 19 20 self.num_per_page = num_per_page 20 self. _hits, self._pages = None, None21 self._h as_next = {} # Caches page_number -> has_next_boolean21 self.orphan = orphan 22 self._hits = self._pages = None 22 23 23 def get_page(self, page_number):24 def validate_page_number(self, page_number): 24 25 try: 25 26 page_number = int(page_number) 26 27 except ValueError: 27 28 raise InvalidPage 28 if page_number < 0 :29 if page_number < 0 or page_number > self.pages - 1: 29 30 raise InvalidPage 31 return page_number 30 32 31 # Retrieve one extra record, and check for the existence of that extra 32 # record to determine whether there's a next page. 33 limit = self.num_per_page + 1 34 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] 35 40 36 object_list = list(self.query_set[offset:offset+limit])37 38 if not object_list:39 raise InvalidPage40 41 self._has_next[page_number] = (len(object_list) > self.num_per_page)42 return object_list[:self.num_per_page]43 44 41 def has_next_page(self, page_number): 45 42 "Does page $page_number have a 'next' page?" 46 if not self._has_next.has_key(page_number): 47 if self._pages is None: 48 offset = (page_number + 1) * self.num_per_page 49 self._has_next[page_number] = len(self.query_set[offset:offset+1]) > 0 50 else: 51 self._has_next[page_number] = page_number < (self.pages - 1) 52 return self._has_next[page_number] 43 return page_number < self.pages - 1 53 44 54 45 def has_previous_page(self, page_number): 55 46 return page_number > 0 … … 59 50 Returns the 1-based index of the first object on the given page, 60 51 relative to total objects found (hits). 61 52 """ 62 if page_number == 0: 63 return 1 53 page_number = self.validate_page_number(page_number) 64 54 return (self.num_per_page * page_number) + 1 65 55 66 56 def last_on_page(self, page_number): … … 68 58 Returns the 1-based index of the last object on the given page, 69 59 relative to total objects found (hits). 70 60 """ 71 if page_number == 0 and self.num_per_page >= self._hits:72 return self._hits73 elif page_number == (self._pages - 1) and (page_number + 1) * self.num_per_page > self._hits:74 return self. _hits75 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 76 66 77 67 def _get_hits(self): 78 68 if self._hits is None: 79 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() 80 75 return self._hits 81 76 82 77 def _get_pages(self): 83 78 if self._pages is None: 84 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 85 83 return self._pages 86 84 87 85 hits = property(_get_hits) 88 86 pages = property(_get_pages) 87 88 89 class PaginatorPage: 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. 95 """ 96 def __init__(self, paginator, page_number): 97 self.paginator = paginator 98 self.page_number = paginator.validate_page_number(page_number) 99 def get_page(self): 100 if not hasattr(self, '_page'): 101 self._page = self.paginator.get_page(self.page_number) 102 return self._page 103 def has_next_page(self): 104 return self.paginator.has_next_page(self.page_number) 105 def has_previous_page(self): 106 return self.paginator.has_previous_page(self.page_number) 107 def first_on_page(self): 108 return self.paginator.first_on_page(self.page_number) 109 def last_on_page(self): 110 return self.paginator.last_on_page(self.page_number) 111 def _get_hits(self): 112 return self.paginator.hits 113 hits = property(_get_hits) 114 def _get_pages(self): 115 return self.paginator.pages 116 pages = property(_get_pages)