Middleware flash messages in view methods

Flash messages are typically used when users perform an action (e.g. submit a form) and it's necessary to tell them if the action was successful or if there was some kind of error. Other times flash messages are used as one time notifications on web pages to tell users about certain events (e.g. site maintenance or special discounts). Figure 2-4 shows a set of sample flash messages.

Web page flash messages
Figure 2-4. Web page flash messages
Django flash messages require a Django app, middleware and a template context processor

By default, all Django projects are enabled to support flash messages. However, if you tweaked your project's settings.py file you may have inadvertently disabled flash messages.

In order for Django flash messages to work you must ensure the following values are set in settings.py: The variable INSTALLED_APPS has the django.contrib.messages value, the variable MIDDLEWARE has the django.contrib.messages.middleware.MessageMiddleware value and the context_processors list in OPTIONS of the TEMPLATES variable has the django.contrib.messages.context_processors.messages value.

As you can see in figure 2-4 there can be different types of flash messages, which are technically known as levels. Django follows the standard Syslog standard severity levels and supports five built-in message levels described in table 2-8.

Table 2-8 Django built-in flash messages

Level ConstantTagValuePurpose
DEBUGdebug10Development-related messages that will be ignored (or removed) in a production deployment
INFOinfo20Informational messages for the user
SUCCESSsuccess25An action was successful, e.g. "Contact info was sent successfully"
WARNINGwarning30A failure did not occur but may be imminent
ERRORerror40An action was not successful or some other failure occurred

Add flash messages

Django flash messages are managed on a per request basis and are added in view methods, as this is the best place to determine whether flash messages are warranted. To add messages you use the django.contrib.messages package.

There are two techniques to add flash messages with the django.contrib.messages package, one is the generic add_message() method and the other are shortcuts methods for the different levels described in table 2-8. Listing 2-27 illustrates the different techniques.

Listing 2-27. Techniques to add Django flash messages

from django.contrib import messages

# Generic add_message method
messages.add_message(request, messages.DEBUG, 'The following SQL statements were executed: %s' % sqlqueries) # Debug messages ignored by default
messages.add_message(request, messages.INFO, 'All items on this page have free shipping.')
messages.add_message(request, messages.SUCCESS, 'Email sent successfully.')
messages.add_message(request, messages.WARNING, 'You will need to change your password in one week.')
messages.add_message(request, messages.ERROR, 'We could not process your request at this time.')

# Shortcut level methods
messages.debug(request, 'The following SQL statements were executed: %s' % sqlqueries) # Debug messages ignored by default 
messages.info(request, 'All items on this page have free shipping.')
messages.success(request, 'Email sent successfully.')
messages.warning(request, 'You will need to change your password in one week.')
messages.error(request, 'We could not process your request at this time.')

The first set of samples in listing 2-27 use the add_message() method, where as the second set uses shortcut level methods. Both sets of samples in listing 2-27 produce the same results.

If you look closely at listing 2-27 you'll notice both DEBUG level messages have the end of line comment # Ignored by default. The Django messages framework by default processes all messages above the INFO level (inclusive), which means DEBUG messages -- being a lower level message threshold, as described in table 2-8 -- are ignored even though they might be defined.

You can change the default Django message level threshold to include all message levels or inclusively reduce the default INFO threshold. The default message level threshold can be changed in one of two ways: globally (i.e. for the entire project) in settings.py with the MESSAGE_LEVEL variable as illustrated in listing 2-28 or on a per request basis with the set_level method of the django.contrib.messages package as illustrated in listing 2-29.

Listing 2-28. Set default Django message level globally in settings.py

# Reduce threshold to DEBUG level in settings.py 
from django.contrib.messages import constants as message_constants
MESSAGE_LEVEL = message_constants.DEBUG

# Increase threshold to WARNING level in setting.py 
from django.contrib.messages import constants as message_constants
MESSAGE_LEVEL = message_constants.WARNING

Listing 2-29. Set default Django message level on a per request basis

# Reduce threshold to DEBUG level per request
from django.contrib import messages
messages.set_level(request, messages.DEBUG)

# Increase threshold to WARNING level per request
from django.contrib import messages
messages.set_level(request, messages.WARNING)

The first MESSAGE_LEVEL definition in listing 2-28 changes the default message level to DEBUG, which means all message level definitions get processed, since DEBUG is the lowest threshold. The second MESSAGE_LEVEL definition in listing 2-28 changes the default message level to WARNING, which means message levels higher than WARNING (inclusive) are processed (i.e. WARNING and ERROR).

The first set_level definition in listing 2-29 changes the default request message level to DEBUG, which means all message level definitions get processed, since DEBUG is the lowest threshold. The second set_level definition in listing 2-29 changes the default message level to WARNING, which means message levels higher than WARNING (inclusive) are processed (i.e. WARNING and ERROR).

If you define both default message level mechanisms at once, the default request message level takes precedence over the default global message level definition (e.g. if you define messages.set_level(request, messages.WARNING), message levels above WARNING (inclusive) are processed, even if the global MESSAGE_LEVEL variable is set to MESSAGE_LEVEL = message_constants.DEBUG to include all messages.

In addition to setting up flash messages and knowing about the built-in threshold mechanism that ignores messages from a certain level, it's also important you realize the message definitions in listing 2-27 assume the Django messages framework pre-requisites are declared in settings.py -- as described in the sidebar at the beginning of this section.

Because you can end up distributing a Django project to a third party and have no control over the final deployment settings.py file, the Django messages framework offers the ability to silently ignore message definitions in case the necessary pre-requisites aren't declared in settings.py. To silently ignore message definitions if pre-requisites aren't declared you can add the fail_silently=True attribute to either technique that adds messages, as illustrated in listing 2-30.

Listing 2-30 Use of the fail_silently=True attribute to ignore errors in case Django messages framework not installed

from django.contrib import messages

# Generic add_message method, with fail_silently=True
messages.add_message(request, messages.INFO, 'All items on this page have free shipping.',fail_silently=True)

# Shortcut level method, with fail_silently=True
messages.info(request, 'All items on this page have free shipping.',fail_silently=True)

Now that you know how to add messages and the important aspects to keep in mind when adding messages, lets take a look at how to access messages.

Access flash messages

The most common place you'll access Django flash messages is in Django templates to display to end users. As a short cut and thanks to the context processor django.contrib.messages.context_processors.messages Django flash messages are available on all templates through the messages variable. But before we get to an actual template sample, lets take a quick look at the structure of Django flash messages.

When you add a Django flash message with one of the techniques described in the previous section, Django creates an instance of the storage.base.Message class. Table 2-9 describes the structure of the storage.base.Message class.

Table 2-9 Django storage.base.Message structure

AttributeDescriptionExample
messageThe actual text of the message.All items on this page have free shipping.
levelAn integer describing the type of the message (see Value column in table 2-8).20
tagsA string combining all the message tags (extra_tags and level_tag) separated by spaces.info
extra_tagsA string containing custom tags for this message, separated by spaces.Empty, by default.
level_tagThe string representation of the level.info

As you can see in table 2-9, there are several attributes which you can leverage to display in Django templates. Listing 2-31 shows the boilerplate template code you can use to display all flash messages set in a request.

Listing 2-31 Boilerplate code to use in Django template to display Django flash messages

{% if messages %}
<ul class="messages">
    {% for msg in messages %}       
    <li>
        <div class="alert alert-{{msg.level_tag}}" role="alert">
 	{{msg.message}}
	</div>
    </li>
    {% endfor %}
</ul>
{% endif %}

Listing 2-31 starts by checking if the messages variable exists -- which contains all flash messages -- if it does, then an HTML list is started with <ul>. Next, a loop is made over all the elements in messages, where each of these elements corresponds to a storage.base.Message instance. For each of these elements, a list and section tag -- <li> and <div> -- are created to output the level_tag attribute as a CSS class and the message attribute as the <div> content.

You can modify the boilerplate code in listing 2-31 as you see necessary, for example to include conditionals and output certain message levels or leverage some of the other storage.base.Message attributes, among other things.

Note The HTML code in listing 2-31 uses the CSS class class="alert alert-{{msg.level_tag}}" which gets rendered into class="alert alert-info" or class="alert alert-success", depending on the level_tag attribute.These CSS classes are part of the CSS bootstrap framework. In this manner, you can quickly format flash messages to look like those presented in figure 2-4.

Although you'll commonly access Django flash messages in Django templates, this doesn't mean you can't access them elsewhere, such as view methods. You can also gain access to Django flash messages in a request through the get_messages() method of the django.contrib.messages package. Listing 2-32 illustrates a code snippet with the use of the get_messages() method.

Listing 2-32 Use of get_messages() method to access Django flash messages

from django.contrib import messages

the_req_messages = messages.get_messages(request)
for msg in the_req_messages:    
    do_something_with_the_flash_message(msg)

In listing 2-32 the get_messages() method receives the request as input and assigns the result to the_req_messages variable. Next, a loop is made over all the elements in the_req_messages, where each of these elements corresponds to a storage.base.Message instance. For each of these elements a call is made to the method do_something_with_the_flash_message to do something with each flash message.

An important aspect to understand when accessing Django flash messages is the duration of the messages themselves. Django flash messages are marked to be cleared when an iteration occurs on the main messages instance and cleared when the response is processed.

For access in Django templates, this means that if you fail to make an iteration in a Django template like the one in listing 2-31 and flash messages are in the request, it can lead to stale or phantom messages appearing elsewhere until an iteration is made and a response is processed. For access in Django view methods (i.e. using get_messages()), this has no impact because even though you may make an iteration over the main messages instance -- therefore, marking messages to be cleared -- a response is not processed in a Django view method, so messages are never cleared, just marked to be cleared.