Unless you are using JavaScript to dynamically load more contents pagination is a neat concept to structure many items of information into multiple pages. The idea is that if you have 100 items you show 20 per page and have 5 pages in total then.
If you are using Flask-SQLAlchemy you can use the integrated pagination class it provides. Here is a simple pagination class that does roughly the same without the support for slicing SQLAlchemy query objects:
from math import ceil
class Pagination(object):
def __init__(self, page, per_page, total_count):
self.page = page
self.per_page = per_page
self.total_count = total_count
@property
def pages(self):
return int(ceil(self.total_count / float(self.per_page)))
@property
def has_prev(self):
return self.page > 1
@property
def has_next(self):
return self.page < self.pages
def iter_pages(self, left_edge=2, left_current=2,
right_current=5, right_edge=2):
last = 0
for num in xrange(1, self.pages + 1):
if num <= left_edge or \
(num > self.page - left_current - 1 and \
num < self.page + right_current) or \
num > self.pages - right_edge:
if last + 1 != num:
yield None
yield num
last = num
So how do you declare URLs and views when using Pagination? The Werkzeug routing system which Flask use supports this nicely with route level defaults. You specify a “default” for page 1 for the bare URL and provide an integer wildcard for other pages:
from flask import redirect
PER_PAGE = 20
@app.route('/users/', defaults={'page': 1})
@app.route('/users/page/<int:page>')
def show_users(page):
count = count_all_users()
users = get_users_for_page(page, PER_PAGE, count)
if not users and page != 1:
abort(404)
pagination = Pagination(page, PER_PAGE, count)
return render_template('users.html',
pagination=pagination,
users=users
)
Note how this code is returning an 404 error for all pages besides the first page if no items were there to display. This is generally a good idea.
When a user heads to /users/page/1 Flask will redirect him automatically to /users/ to keep the URL unique.
Now how can a template generate a URL to a different page without much hassle? Because the only difference from one URL to the other is the page part in it we can provide a little helper function that wraps url_for to generate a new URL to the same endpoint with a different page:
def url_for_other_page(page):
args = request.view_args.copy()
args['page'] = page
return url_for(request.endpoint, **args)
app.jinja_env.globals['url_for_other_page'] = url_for_other_page
So how do you render such a pagination? Here is a simple macro that uses the iter_pages method of the pagination class to show a simple pagination:
{% macro render_pagination(pagination) %}
<div class=pagination>
{%- for page in pagination.iter_pages() %}
{% if page %}
{% if page != pagination.page %}
<a href="{{ url_for_other_page(page) }}">{{ page }}</a>
{% else %}
<strong>{{ page }}</strong>
{% endif %}
{% else %}
<span class=ellipsis>…</span>
{% endif %}
{%- endfor %}
{% if pagination.has_next %}
<a href="{{ url_for_other_page(pagination.page + 1)
}}">Next »</a>
{% endif %}
</div>
{% endmacro %}
This snippet by Armin Ronacher can be used freely for anything you like. Consider it public domain.
Comments
Thanks for the code by Sigma0 on 2011-03-15 @ 13:28
It works well. There is a small typo in the following line however:
should be:
Comment by Armin Ronacher on 2011-06-08 @ 10:25
Fixed. Thanks :)
small typo by Christian Ştefănescu on 2011-06-08 @ 10:35
In the 'URLs and Views' section, there's a small typo: Pagiation(... instead of Pagination(...
Comment by Wieland Hoffmann on 2011-10-18 @ 20:20
The macro is missing::
{% if pagination.has_prev %}< <a href="{{ url_for_other_page(pagination.page - 1) }}">« Previous</a>< {% endif %}<