Skip to main content

The Messages Framework

The messages framework allows you to store temporary notifications for users and display them on subsequent requests. This is commonly used for "flash messages" like "Profile updated successfully" or "Invalid password."

The framework is managed by the MessagesConfig class in django/contrib/messages/apps.py, which ensures that message level tags are updated whenever the MESSAGE_TAGS setting is changed.

The Message Lifecycle

The framework operates through a coordinated lifecycle involving middleware, storage backends, and the template engine.

1. Initialization

When a request enters the application, the MessageMiddleware (in django/contrib/messages/middleware.py) initializes the storage backend:

def process_request(self, request):
request._messages = default_storage(request)

The default_storage is typically FallbackStorage, which orchestrates multiple backends like CookieStorage and SessionStorage.

2. Adding Messages

Developers add messages using the API functions in django/contrib/messages/api.py. These functions (like success, error, or info) call add_message, which delegates to the storage object attached to the request:

# django/contrib/messages/api.py
def success(request, message, extra_tags="", fail_silently=False):
add_message(request, constants.SUCCESS, message, extra_tags=extra_tags, fail_silently=fail_silently)

Inside BaseStorage.add (in django/contrib/messages/storage/base.py), the message is wrapped in a Message object and appended to an internal queue:

def add(self, level, message, extra_tags=""):
# ... validation ...
self.added_new = True
message = Message(level, message, extra_tags=extra_tags)
self._queued_messages.append(message)

3. Consumption and the "Used" Flag

Messages are designed to be "one-time." This is enforced by the used flag in BaseStorage. When you iterate over messages in a template, the storage marks itself as used:

# django/contrib/messages/storage/base.py
def __iter__(self):
self.used = True
if self._queued_messages:
self._loaded_messages.extend(self._queued_messages)
self._queued_messages = []
return iter(self._loaded_messages)

Once self.used is True, the framework knows these messages have been seen and should be cleared during the response phase.

4. Persistence and Cleanup

At the end of the request, MessageMiddleware.process_response calls storage.update(response). This method determines which messages need to be saved for the next request and which should be deleted:

# django/contrib/messages/storage/base.py
def update(self, response):
self._prepare_messages(self._queued_messages)
if self.used:
# If messages were iterated, only store the NEWLY added ones (if any)
return self._store(self._queued_messages, response)
elif self.added_new:
# If not iterated, store both old and new messages
messages = self._loaded_messages + self._queued_messages
return self._store(messages, response)

Storage Backends

The framework provides different strategies for where messages live between requests.

CookieStorage

CookieStorage (in django/contrib/messages/storage/cookie.py) stores messages on the client side in a signed cookie.

  • Security: It uses django.core.signing to ensure the client cannot tamper with the messages.
  • Constraints: It has a max_cookie_size of 2048 bytes. If messages exceed this size, CookieStorage will drop older messages to fit the limit, adding a sentinel value (__messagesnotfinished__) to indicate data was lost.

SessionStorage

SessionStorage (in django/contrib/messages/storage/session.py) stores messages in the server-side session.

  • Requirement: It requires django.contrib.sessions to be installed and SessionMiddleware to appear before MessageMiddleware in the settings.
  • Reliability: Unlike cookies, it is not limited by browser header size constraints.

Message Levels and Tags

Every Message object has a level (integer) and a tags property. The tags property is a string containing both the level-specific tag and any extra_tags provided when the message was created.

# django/contrib/messages/storage/base.py
@property
def tags(self):
return " ".join(tag for tag in [self.extra_tags, self.level_tag] if tag)

The level_tag is derived from the MESSAGE_TAGS setting. This allows for easy CSS integration in templates:

{% for message in messages %}
<li class="{{ message.tags }}">{{ message }}</li>
{% endfor %}

Integration in Class-Based Views

The framework provides the SuccessMessageMixin in django/contrib/messages/views.py to automate success notifications for form-based views:

class SuccessMessageMixin:
success_message = ""

def form_valid(self, form):
response = super().form_valid(form)
success_message = self.get_success_message(form.cleaned_data)
if success_message:
messages.success(self.request, success_message)
return response

This mixin hooks into the form_valid workflow, ensuring that a message is queued only when the form submission is successful.