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.signingto ensure the client cannot tamper with the messages. - Constraints: It has a
max_cookie_sizeof 2048 bytes. If messages exceed this size,CookieStoragewill 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.sessionsto be installed andSessionMiddlewareto appear beforeMessageMiddlewarein 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.