| 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 | |