Ticket #1374: paginatorNEW.py

File paginatorNEW.py, 4.1 KB (added by Adrian Holovaty, 19 years ago)

Unfinished implementation of MultiObjectPaginator

Line 
1from copy import copy
2from math import ceil
3
4class InvalidPage(Exception):
5 pass
6
7class ObjectPaginator(object):
8 """
9 This class makes pagination easy. Feed it a module (an object with
10 get_count() and get_list() methods) and a dictionary of arguments
11 to be passed to those methods, plus the number of objects you want
12 on each page. Then read the hits and pages properties to see how
13 many pages it involves. Call get_page with a page number (starting
14 at 0) to get back a list of objects for that page.
15
16 Finally, check if a page number has a next/prev page using
17 has_next_page(page_number) and has_previous_page(page_number).
18 """
19 def __init__(self, module, args, num_per_page, count_method='get_count', list_method='get_list'):
20 self.module, self.args = module, args
21 self.num_per_page = num_per_page
22 self.count_method, self.list_method = count_method, list_method
23 self._hits, self._pages = None, None
24 self._has_next = {} # Caches page_number -> has_next_boolean
25
26 def _normalize_page_number(self, page_number):
27 try:
28 page_number = int(page_number)
29 except ValueError:
30 raise InvalidPage
31 if page_number < 0:
32 raise InvalidPage
33 return page_number
34
35 def get_page(self, page_number):
36 page_number = self._normalize_page_number(page_number)
37 args = copy(self.args)
38 args['offset'] = page_number * self.num_per_page
39 # Retrieve one extra record, and check for the existence of that extra
40 # record to determine whether there's a next page.
41 args['limit'] = self.num_per_page + 1
42 object_list = getattr(self.module, self.list_method)(**args)
43 if not object_list:
44 raise InvalidPage
45 self._has_next[page_number] = (len(object_list) > self.num_per_page)
46 return object_list[:self.num_per_page]
47
48 def has_next_page(self, page_number):
49 "Does page $page_number have a 'next' page?"
50 if not self._has_next.has_key(page_number):
51 if self._pages is None:
52 args = copy(self.args)
53 args['offset'] = (page_number + 1) * self.num_per_page
54 args['limit'] = 1
55 object_list = getattr(self.module, self.list_method)(**args)
56 self._has_next[page_number] = (object_list != [])
57 else:
58 self._has_next[page_number] = page_number < (self.pages - 1)
59 return self._has_next[page_number]
60
61 def has_previous_page(self, page_number):
62 return page_number > 0
63
64 def _get_hits(self):
65 if self._hits is None:
66 order_args = copy(self.args)
67 if order_args.has_key('order_by'):
68 del order_args['order_by']
69 if order_args.has_key('select_related'):
70 del order_args['select_related']
71 self._hits = getattr(self.module, self.count_method)(**order_args)
72 return self._hits
73
74 def _get_pages(self):
75 if self._pages is None:
76 self._pages = int(ceil(self.hits / float(self.num_per_page)))
77 return self._pages
78
79 hits = property(_get_hits)
80 pages = property(_get_pages)
81
82class MultiObjectPaginator(ObjectPaginator):
83 """
84 A paginator for multiple object types.
85
86 Has the same interface as ObjectPaginator except for __init__().
87 """
88 def __init__(self, mods_and_args, num_per_page, count_method='get_count', list_method='get_list'):
89 """
90 mods_and_args = ((some_mod, 'foo__exact="bar"'), (other_mod, 'bar__startswith="hi"'))
91 """
92 self.mods_and_args = mods_and_args
93 self.num_per_page = num_per_page
94 self.count_method, self.list_method = count_method, list_method
95 self._hits, self._pages = None, None
96 self._has_next = {} # Caches page_number -> has_next_boolean
97
98 def get_page(self, page_number):
99 page_number = self._normalize_page_number(page_number)
100 raise NotImplementedError
101
102 def has_next_page(self, page_number):
103 "Does page $page_number have a 'next' page?"
104 raise NotImplementedError
Back to Top