Using Python kwargs (keyword arguments) in Flask url_for() for pagination

24 November 2019 Updated 24 November 2019 by Peter

Python kwargs is an easy way to pass data to a function. Using the double asterisk for unpacking we can pass this data to another function.

post main image
https://unsplash.com/@josephtpearson

For this website I am using Flask and SQLAlchemy without the Flask-SQLAlchemy extension. I need pagination for several pages. For example the home page holds the list of blogs and it should show a maximum of 12 items per page. It is not that difficult to implement. The home page view function requires a page_number that defaults to 1 if it not specified:

@pages_blueprint.route('/', defaults={'page_number': 1})
@pages_blueprint.route('/<int:page_number>')
def homepage(page_number):

    # get total items for the selected language
    total_items = content_item_query.total_items_count()

    pagination = Pagination(items_per_page=12)
    pagination.set_params(page_number, total_items, 'pages.homepage')
    ...
    return = render_template(
        'pages/index.html', 
        page_title=page_title,
        ...
        pagination=pagination)

and part of the Pagination class:

class Pagination:
    ...
    def set_params(self, page_number, total_items, endpoint):
        ...
        # previous page
        if page_number > 1:
            previous_page_number = page_number - 1
            self.previous_page = {
                'page_number': previous_page_number,
                'url': url_for(endpoint, page_number=str(previous_page_number)),
            }

Nothing special, works fine, some urls generated by url_for(), when the language is 'en':

http:127.0.0.1:8000/en/

http:127.0.0.1:8000/en/2

Note that Flask looks up the endpoint and uses the value of the named argument.

Multilevel url parameters

In the admin part of the application there is an entry where I can edit the languages. Language records are grouped into a language version. Language versions can be listed, a new language version can be created from an existing language version, edited and activated. This scheme also allows for changing languages on-the-fly, i.e. without restarting the application.

The first step is to select language version. This will present a list of languages that can be editted. Like all lists, the list of languages is paginated.
Like the home page view function above, the languages list view function is called with a page number. To indicate the language version, the languages list function is also called with the language_version_id:

@admin_language_blueprint.route('/languages/list/<int:language_version_id>/<int:page_number>')
@admin_language_blueprint.route('/languages/list/<int:language_version_id>', defaults={'page_number': 1})
@login_required
@requires_user_roles('language_admin')
def languages_list(language_version_id, page_number):

    # get total items
    total_items = len(all_languages)

    pagination = Pagination(items_per_page=6)
    pagination.set_params(page_number, total_languages, 'admin_language.languages_list', language_version_id=language_version_id)
    ...

Note that the pagination.set_params() function now has an additional (name) parameter: language_version_id. This must added to the pagination url, so they will look like:

http://127.0.0.1:8000/admin/en/language/languages/list/5

http://127.0.0.1:8000/admin/en/language/languages/list/5/1

http://127.0.0.1:8000/admin/en/language/languages/list/5/2

Here 5 is the language_version_id and the next number is the page number.

Kwargs (keyword arguments) to the rescue

I did not do much special with kwargs until now, but in this case it proves extremely useful. Using kwargs we can have an unlimited number of keyword arguments. Here I only need two but with kwargs everything is inplace to have three or more. Below is again part of the Pagination class but now with kwargs added:

class Pagination:
    ...
    def set_params(self, page_number, total_items, endpoint, **endpoint_kwargs):
        ...
        # previous page
        if page_number > 1:
            previous_page_number = page_number - 1
            self.previous_page = {
                'page_number': previous_page_number,
                'url': url_for(endpoint, **endpoint_kwargs, page_number=str(previous_page_number)),
            }

In the def set_params(...) line I added **endpoint_kwargs. We do not have to use it but it is there when we need it. Then in the url_for() function I also added **endpoint_kwargs. The '**' before endpoint_kwargs notation means that the keyword arguments must be unpacked. If there is nothing to unpack then no problem. The order of keyword arguments is not important here because they are named.

Summary

What seemed a lot of work in the beginning appeared to be very easy using Python keyword arguments. For the first time I used kwargs with the double asterisk for unpacking. Very nice.

Links / credits

What is the purpose and use of **kwargs?
https://stackoverflow.com/questions/1769403/what-is-the-purpose-and-use-of-kwargs