Django

Code

Ticket #1374: paginatorNEW.py

File paginatorNEW.py, 4.1 kB (added by adrian, 2 years ago)

Unfinished implementation of MultiObjectPaginator?

Line 
1 from copy import copy
2 from math import ceil
3
4 class InvalidPage(Exception):
5     pass
6
7 class 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
82 class 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