Python Flask pagination

Flask pagination

The [crossword hints]() Flask application I have been writing has been expanding and I have taken some time to add more clues and solutions such that there is now some need to break up the index list into more manageable chunks using pagination.

I’ve been considering a couple of options:

  • the flask-paginate pip which is modelled on Rails’ will_paginate (which I have used in the past.
  • a Flask snippet from Armin Ronacher, which on the face of it looks like it will be harder to implement;
  • a hand-rolled solution, but why re-invent the wheel?

Flask-paginate

This should be the preferred solution because it reduces the amount of coding required in the application, but I had a cserious problems when it came to rendering the pagination: it’s missing much of the necessary CSS to display the page listing.

At first, the page listing just appeared as a bulleted list. It turns out that Flask-paginate uses a CSS framework called bootstrap but the documentation
doesn’t include any real mention of it, let alone describing it as essential.

StackOverflow responses to Flask=paginate styling problems just said to install
the bootstrap CSS files, but when I tried this it just completely mangled much of
the other navigation styling I already had. I hate messing around with CSS at the best of times so there’s no way I’m wasting time fiddling around with minimised 3rd-party dependent CSS. Time to ‘git checkout — ‘ the files I’d been working on.

Snippet 44

I was wary of attempting to implementing the suggestion in the snippet, not
because I thought it wouldn’t be any good (total regard for Armin’s code), but because of my lack opf confidence working with what looked like a bit more advanced Python and Flask and whether my ropey application could support it being added without a complete rewrite.
I need not have worried.

Anyway, the task breaks down into the following stages:

  • a pagination class
  • a view helper,
  • routing and rendering: URLs and views,
  • CSS to support a reasonable page layout

Pagination class

First off we need a class to contain properties and methods needed to describe the pagination:

  • total number of records,
  • items per page
  • number of pages
  • to determine whether the current page needs next and/or previous links
  • an iterator over the pages; I don’t think I would have been able to write this
    so concisely, so definitely good value here,
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 range(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

I had to make a tiny change to what appeared in the snippet, replacing xrange with just range.

View helper

There needs to be a view helper that can be used in a template macro to generate the link to the other pages.

def url_for_other_page(page):
    args = request.view_args.copy()
    args['page'] = page
    return url_for(request.endpoint, **args)
application.jinja_env.globals['url_for_other_page'] = url_for_other_page

Although this has been added to my main application file it is something that can be relocated somewhere more sensible when refactoring work starts; registering the helper with Jinja this way is certainly something I can try with future projects so this snippet is teaching more than just pagination. Win-win.

Routing and rendering

The trickiest part of the process is probably changing the index route to support displaying a particular page of content.

Default and paged indexes

The easiest way to drive the pagination routing is to associate a page number with the route

@application.route("/crossword-solutions/", defaults={'page': 1})
@application.route('/crossword-solutions/page/<int:page>')
def crossword_solution_index(page):

The index routing needs to include:

  • a count of the number of items
  • a query that only selects the necessary records for display,
  • a check to capture out of range page requests,
  • create a pagination instance and pass it to the template to render

This gives an index router like:

    count = crossword_solutions.select(fn.COUNT(crossword_solutions.rowid)).scalar()
    PER_PAGE=25
    offset = ((int(page)-1) * PER_PAGE)
    solutions = crossword_solutions.raw("""
        SELECT cs1.name as setter, cs2.solution AS solution, cs2.rowid AS csid, st1.name AS soltype
        FROM crossword_setters cs1
        INNER JOIN crossword_solutions cs2
        ON cs1.rowid = cs2.crossword_setter_id
        INNER JOIN solution_types st1
        ON cs2.solution_type_id = st1.rowid
        ORDER BY cs2.solution
        LIMIT %s, %s""" % (offset, PER_PAGE))
    # Display a 409 not found page for an out of bounds request
    if not solutions and page != 1:
        return(render_template('errors/409.html', errmsg="Requested page out of bounds"), 409 )
    pagination = Pagination(page, PER_PAGE, count)
    return(render_template('views/crossword-solutions/index.html',
                            r=request,
                            solns=solutions,
                            pagination=pagination))

Although I’m using PeeWee as the ORM, I’ve found that queries like this one work better as plain SQL particularly with the offset and limit. What we notice here is that the pagination has no connection at all with the database result set like, say, will_paginate, but still doesn’t lose any functionality.

Pagination macro

I’m still not very experienced with Jinja2 macros so really appreciated the sample provided by the snippet. I modified it slightly to include a previous page link and also display next and previous as grayed-out boxes and the last and first pages respectively.

{% macro render_pagination(pagination) %}

<br />
{% endmacro %}

This is added to an existing macros,html template file.

Template rendering

The macros file is imported into a high-level layout template as

{%- import "macros.html" as f -%}

and child templates apply the pagination with,

{{ f.render_pagination(pagination) }}

Pagination stylesheet

I dislike HTML layout in general and loathe CSS in particular so I will settle for whatever gives a reasonable look despite any ‘obvious’ inefficiencies and any prolonged CSS development will inevitably provoke much cussing and cursing. I have based the styling for my current project around ‘digg_pagination’ styles I came across for old Rails projects with as few tweaks as possible; I really don’t why some settings get included from some sections and not other.

The styling boils down to the following sections (I include it only as an example
and not a recommendation; no-one in their right mind would trust me to design CSS)

.pagination {
margin: 10px 0;
margin-top: 0;
margin-bottom: 0;
font-size: 0.7em;
}
.pagination ul li {
border-color: #105cb6;
border-width: 0 0 1px;
border-radius: 0;
float: left;
list-style-type: none;
}
.pagination ul li span.nolink {
padding: 2px 10px 2px 10px;
display: block;
/* float: left; */
margin-right: 1px;
font-size: 8pt;
font-weight: bold;
border: 1px solid #9aafe5;
color: #D5D5D5;
}
.pagination a, .pagination ul li span {
padding: 2px 10px 2px 10px;
display: block;
/* float: left; */
margin-right: 1px;
font-weight: bold;
color: #105cb6;
border: 1px solid #9aafe5;
}
.pagination ul li span.ellipsis {
/* font-size: 10pt; */
font-weight: normal;
padding: 2px;
margin: 1px;
border-color: #fff;
}

Which I think gives a reasonable look to the pagination with greyed out boxes for the prev and next links on the first and last pages and clearly indicates the active page.

References

I highly recommend http://flask.pocoo.org/snippets/44/ as a great place to start if looking at pagination for a Flask application; in general, Armin’s posts here are top-notch and very informative.

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.