Code

Ticket #1374: paginatorNEW.py

File paginatorNEW.py, 4.1 KB (added by adrian, 8 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