Using Python kwargs (keyword arguments) in Flask url_for() for pagination
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.
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
Read more
Flask
Recent
- Hiding database UUID primary keys of your web application
- Don't Repeat Yourself (DRY) with Jinja2
- SQLAlchemy, PostgreSQL, maximum number of rows per user
- Show the values in SQLAlchemy dynamic filters
- Secure data transfer with Public Key encryption and pyNaCl
- rqlite: a high-availability and distributed SQLite alternative
Most viewed
- Using Python's pyOpenSSL to verify SSL certificates downloaded from a host
- Using UUIDs instead of Integer Autoincrement Primary Keys with SQLAlchemy and MariaDb
- Connect to a service on a Docker host from a Docker container
- Using PyInstaller and Cython to create a Python executable
- SQLAlchemy: Using Cascade Deletes to delete related objects
- Flask RESTful API request parameter validation with Marshmallow schemas