| | 107 | == paginate() == |
| | 108 | |
| | 109 | A basic pagination routine written by [http://www.cmlenz.net/ Christopher Lenz]. |
| | 110 | |
| | 111 | {{{ |
| | 112 | #!python |
| | 113 | from math import ceil |
| | 114 | |
| | 115 | def paginate(items, page=0, max_per_page=10): |
| | 116 | """Simple generic pagination. |
| | 117 | |
| | 118 | Given an iterable, this function returns: |
| | 119 | * the slice of objects on the requested page, |
| | 120 | * the total number of items, and |
| | 121 | * the total number of pages. |
| | 122 | |
| | 123 | The `items` parameter can be a list, tuple, or iterator: |
| | 124 | |
| | 125 | >>> items = range(12) |
| | 126 | >>> items |
| | 127 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] |
| | 128 | >>> paginate(items) |
| | 129 | ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 12, 2) |
| | 130 | >>> paginate(items, page=1) |
| | 131 | ([10, 11], 12, 2) |
| | 132 | >>> paginate(iter(items)) |
| | 133 | ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 12, 2) |
| | 134 | >>> paginate(iter(items), page=1) |
| | 135 | ([10, 11], 12, 2) |
| | 136 | |
| | 137 | This function also works with generators: |
| | 138 | |
| | 139 | >>> def generate(): |
| | 140 | ... for idx in range(12): |
| | 141 | ... yield idx |
| | 142 | >>> paginate(generate()) |
| | 143 | ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 12, 2) |
| | 144 | >>> paginate(generate(), page=1) |
| | 145 | ([10, 11], 12, 2) |
| | 146 | |
| | 147 | The `max_per_page` parameter can be used to set the number of items that |
| | 148 | should be displayed per page: |
| | 149 | |
| | 150 | >>> items = range(12) |
| | 151 | >>> paginate(items, page=0, max_per_page=6) |
| | 152 | ([0, 1, 2, 3, 4, 5], 12, 2) |
| | 153 | >>> paginate(items, page=1, max_per_page=6) |
| | 154 | ([6, 7, 8, 9, 10, 11], 12, 2) |
| | 155 | """ |
| | 156 | if not page: |
| | 157 | page = 0 |
| | 158 | start = page * max_per_page |
| | 159 | stop = start + max_per_page |
| | 160 | |
| | 161 | count = None |
| | 162 | if hasattr(items, '__len__'): |
| | 163 | count = len(items) |
| | 164 | |
| | 165 | try: # Try slicing first for better performance |
| | 166 | retval = items[start:stop] |
| | 167 | except TypeError: # Slicing not supported, so iterate through the whole list |
| | 168 | retval = [] |
| | 169 | for idx, item in enumerate(items): |
| | 170 | if start <= idx < stop: |
| | 171 | retval.append(item) |
| | 172 | # If we already obtained the total number of items via `len()`, |
| | 173 | # we can break out of the loop as soon as we've got the last item |
| | 174 | # for the requested page |
| | 175 | if count is not None and idx >= stop: |
| | 176 | break |
| | 177 | if count is None: |
| | 178 | count = idx + 1 |
| | 179 | |
| | 180 | return retval, count, int(ceil(float(count) / max_per_page)) |
| | 181 | }}} |
| | 182 | |
| | 183 | === Example usage === |
| | 184 | |
| | 185 | {{{ |
| | 186 | #!xml |
| | 187 | <div py:with="items, num_items, num_pages = paginate(range(1, 24), cur_page - 1)"> |
| | 188 | <h1>Page ${cur_page} of ${num_pages}</h1> |
| | 189 | <ul><li py:for="item in items">${item}</li></ul> |
| | 190 | <hr /> |
| | 191 | <py:for="num in range(cur_page, num_pages + 1)"> |
| | 192 | <a href="?page=$num">$num</a> |
| | 193 | </py:for> |
| | 194 | </div> |
| | 195 | }}} |
| | 196 | |
| | 197 | That should result in the following output: |
| | 198 | |
| | 199 | {{{ |
| | 200 | #!xml |
| | 201 | <div> |
| | 202 | <h1>Page 1 of 3</h1> |
| | 203 | <ul><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li></ul> |
| | 204 | <hr /> |
| | 205 | <a href="?page=1">1</a> |
| | 206 | <a href="?page=2">2</a> |
| | 207 | <a href="?page=3">3</a> |
| | 208 | </div> |
| | 209 | }}} |
| | 210 | |