Home Articles Categories Series
Pythonise Just now

Flask message flashing | Learning Flask Ep. 17

Providing feedback and notifications to users using Flask's flash function


Article Posted on by in Flask
Julian Nash · 9 months ago in Flask

Providing feedback to users of a web application is critical, from notifications and error messages to warnings and progress alerts.

If the application doesn't the right level of feedback, chances are the user will become frustrated and end up having a bad experience.

Fortunately for us, Flask provides a simple way to send messages from the server to the client using flash.

High level overview

Flask's message flashing system allows us to record a message at any point withing a request, then display it at the start of the next request (and only the next request).

This is ideal for cases when we want to provide the user with some feedback based on the actions of their current request, take this scenario for example:

  • User attempts to create an account, submitting a form with a username and password
  • The password must be at least 10 characters long, but the one submitted is only 8
  • Our application rejects the short password and we call flash, passing it a message to let the user know the password was too short
  • We redirect the user back to the account creation page
  • The message is flashed to the user, then destroyed. Provising them with feedback to let them know

Working with flash requires 2 main components:

  • Calling the flash function and passing it a message from a route in our application
  • Checking for flashed messages in a template

Now you know a little more about what flash does, it's time for an example.

Using flash in a route

To work with flash, we need to import it from Flask

from flask import flash

We'll go ahead and create a route which we described in the scenario earlier. An account creation page:

@app.route("/sign-up", methods=["GET", "POST"])
def sign_up():

    if request.method == "POST":

        req = request.form

        username = req.get("username")
        email = req.get("email")
        password = req.get("password")

        if not len(password) >= 10:
            flash("Password length must be at least 10 characters")
            return redirect(request.url)

        flash("Account created!")

        return redirect(request.url)

    return render_template("public/sign_up.html")

This is obviously not a real account creation page! It's just to demonstrate message flashing (We're returning the user back to the sign up page, even on succcess)

The part to pay attention to here:

if not len(password) >= 10:
    flash("Password must be at least 10 characters")
    return redirect(request.url)

We're performing some basic validation on the length of the password that the user submitted. If it doesn't pass, we call the flash function and pass it a message:

flash("Password length must be at least 10 characters")

Now we need to add something to our template to check for flashed messages.

If you're using a base template, it's the ideal place to put it. We're going to put the following in our public_template.html base template. If not, place it somewhere your users will notice and doesn't interfere with the rest of your HTML.

Getting flashed messages

Here's the basic Jinja syntax for getting flashed messages:

{% with messages = get_flashed_messages() %}
    {% if messages %}
        {% for message in messages %}
            <!-- Do something with the message -->
            {{ message }}

        {% endfor %}
    {% endif %}
{% endwith %}
  • We call with messages = get_flashed_messages() to register any flashed messages
  • {% if messages %} then checkes to see if there are any messages in the queue
  • {% for message in messages %} iterates over the messages (you can call flash more then once to register several messages)
  • We can then do something with the contents of the message using {{ message }}

As it is, this code isn't going to produce anything pretty or useful, so let's go ahead and add some HTML to our flashed messages.

Styling flashed messages

We're using Bootstrap for styling but feel free to add your own CSS.

Any code inside the {% for message in messages %} block will be repeated for the amount of flashed messages in the queue and will persist on the page until the next request.

Bootstrap comes with the alert class, featuring a dismissable button to clear the alert, so we'll go ahead and use that. Place it above the main tag of your HTML or just above the form in the sign_up.html page:

{% with messages = get_flashed_messages() %}
    {% if messages %}
        {% for message in messages %}
            <div class="alert alert-primary alert-dismissible fade show" role="alert">
                <span>{{ message }}</span>
                <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                <span aria-hidden="true">&times;</span>
                </button>
            </div>
        {% endfor %}
    {% endif %}
{% endwith %}

If you now submit the form with less that 10 characters in the password field, you'll see a nice dismissable alert at the top of your page!

Flask message flashing

But what if we need even more control over the style of our flashed messages? The good news is there's a simple solution.

Flash categories

Flask's Flash function takes up to 2 arguments, a message and a category:

flash("message", "category")

Both values are not fixed and you've already seen us pass a message into flash, but what about category?

We can provide any value we like as the category argument and access it in our template, meaning we can style flashed messages using the category passed in and drop it into our HTML.

Consider the following:

You have 3 levels of alerts you'd like to show to your user.

  • Success (Green)
  • Warning (Yellow)
  • Danger (Red)

Using the category argument, we can design flashes depending on the control flow and what feedback we want the user to see.

To access the flash category, we need to refactor our previous flash:

{% with messages = get_flashed_messages(with_categories=true) %}
    {% if messages %}
        {% for category, message in messages %}
            <div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
                <span>{{ message }}</span>
                <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                <span aria-hidden="true">&times;</span>
                </button>
            </div>
        {% endfor %}
    {% endif %}
{% endwith %}
  • Passing with_categories=true to get_flashed_messages(), we're able to access the category of that flash
  • {% for category, message in messages %} unpacks the message and category, which we can then access within the for loop

Depending on your CSS, you should use {{ category }} as a class in your alert. As you can see from our Bootstrap example, we're using it to complete the alert-{{ category }}, which will trigger a different color of the alert.

  • alert-success will trigger a green alert
  • alert-warning will trigger an orange alert
  • alert-danger will trigger a red alert

Let's refactor our route to include a category in the flash:

@app.route("/sign-up", methods=["GET", "POST"])
def sign_up():

    if request.method == "POST":

        req = request.form

        username = req.get("username")
        email = req.get("email")
        password = req.get("password")

        if not len(password) >= 10:
            flash("Password must be at least 10 characters", "warning")
            return redirect(request.url)

        flash("Account created!", "success")

        return redirect(request.url)

    return render_template("public/sign_up.html")

We haven't changed much, just added the 2 classes to our flash functions:

flash("Password must be at least 10 characters", "warning")
flash("Account created!", "success")

If you now submit the sign up form with both less than and more than 10 characters, you'll see the 2 different styles of flash.

Warning:

Flask message flashing

Success:

Flask message flashing

Filtering flashes

In many cases, you'll want to have more than 1 style of alert. You may have a notification section, warning section and conformation alert in different parts of your templates and in varius styles.

Filtering flashes allows us to only trigger a flash based on the category it receives! meaning you're not fixed to one style of alert.

Say you'd like to have success notifications show up on the top right of your app and error messages pop up as a modal in the middle of your app, you can create 2 separate flash triggers in your templates to handle each instance.

The syntax for filtering flashes is very similar, with only some minor tweaks:

{% with success = get_flashed_messages(category_filter=["success"]) %}
    {% if success %}
        {%- for message in success %}
        <div class="alert alert-success alert-dismissible fade show" role="alert">
            <span>{{ message }}</span>
            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                <span aria-hidden="true">&times;</span>
            </button>
        </div>
        {% endfor -%}
    {% endif %}
{% endwith %}
  • By passing category_filter=["success"] to get_flashed_messages(), we only trigger the flash when a given category is passed into the flash function.

Using filtering, we can create several flashes in our templates and we only need to pass in the actual message of the flash.

In this simple example, we'll create 3 flash instances in our template, each with their own filter. One for success, one for warning and one for danger. We'll space these 3 flashes over 3 columns.

<div class="row">

    <div class="col-sm-4">
        {% with errors = get_flashed_messages(category_filter=["success"]) %}
            {% if errors %}
                {%- for message in errors %}
                    <div class="alert alert-success alert-dismissible fade show" role="alert">
                        <span>{{ message }}</span>
                        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                            <span aria-hidden="true">&times;</span>
                        </button>
                    </div>
                {% endfor -%}
            {% endif %}
        {% endwith %}
    </div>

    <div class="col-sm-4">
        {% with errors = get_flashed_messages(category_filter=["warning"]) %}
            {% if errors %}
                {%- for message in errors %}
                    <div class="alert alert-warning alert-dismissible fade show" role="alert">
                        <span>{{ message }}</span>
                        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                            <span aria-hidden="true">&times;</span>
                        </button>
                    </div>
                {% endfor -%}
            {% endif %}
        {% endwith %}
    </div>

    <div class="col-sm-4">
        {% with errors = get_flashed_messages(category_filter=["danger"]) %}
            {% if errors %}
                {%- for message in errors %}
                    <div class="alert alert-danger alert-dismissible fade show" role="alert">
                        <span>{{ message }}</span>
                        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                            <span aria-hidden="true">&times;</span>
                        </button>
                    </div>
                {% endfor -%}
            {% endif %}
        {% endwith %}
    </div>

</div>

You'll see, we're filtering the flashes using our 3 categories, with a specific flash for each category.

Calling all 3 categories of flash in our route:

flash("Success!", "success")
flash("This is a warning", "warning")
flash("DANGER DANGER", "danger")

Produces the following:

Flask message flashing

Wrapping up

Flashing provides us with a simple and flexible way to provide users with valuable feedback and can be styled freely.

Using filters allows us to customize flashes depending on the category passed to it, keeping users happy and notified!

Last modified · 28 Feb 2019
Did you find this article useful?
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
Contents
Loading...