El framework de mensajes

Los mensajes de notificación se utilizan comúnmente en aplicaciones web para mostrar un mensaje de notificación temporal (también conocido como «mensaje flash») al usuario después de procesar una forma o otros tipos de entrada del usuario.

Django proporciona plena compatibilidad con mensajería basada en cookies y sesiones, tanto para usuarios anónimos como autenticados. El marco de mensajes permite almacenar temporalmente mensajes en una solicitud y recuperarlos para su visualización en una solicitud posterior (generalmente la siguiente). Cada mensaje está etiquetado con un nivel específico que determina su prioridad (por ejemplo, info, warning o error).

Habilitación de mensajes

Los mensajes se implementan a través de una clase middleware y el procesador de contexto correspondiente context processor.

La configuración por defecto settings.py creada por django-admin startproject ya contiene todas las configuraciones necesarias para habilitar la funcionalidad de mensajes:

  • 'django.contrib.messages' está en INSTALLED_APPS.

  • MIDDLEWARE contiene 'django.contrib.sessions.middleware.SessionMiddleware' y 'django.contrib.messages.middleware.MessageMiddleware'.

    La configuración por defecto del almacenamiento de mensajes <message-storage-backends> se basa en sesiones. Por lo tanto, SessionMiddleware debe estar habilitado y aparecer antes que MessageMiddleware en MIDDLEWARE.

  • La opción 'context_processors' del backend de plantillas Django definido en tu configuración TEMPLATES contiene 'django.contrib.messages.context_processors.messages'.

Si no deseas utilizar mensajes, puedes eliminar 'django.contrib.messages' de tus INSTALLED_APPS, la línea MessageMiddleware de MIDDLEWARE y el procesador de contexto messages de TEMPLATES.

Configurando el motor de mensajes

Almacenamiento de backends

El marco de mensajes puede utilizar diferentes backends para almacenar mensajes temporales.

Django proporciona tres clases de almacenamiento integradas en django.contrib.messages:

class storage.session.SessionStorage

Esta clase almacena todos los mensajes dentro de la sesión del request. Por lo tanto, requiere la aplicación contrib.sessions de Django.

class storage.cookie.CookieStorage

Esta clase almacena los datos de mensaje en una cookie (firmada con un hash secreto para prevenir manipulación) para persistir las notificaciones a través de solicitudes. Los mensajes antiguos se eliminan si el tamaño de los datos de la cookie superaría 2048 bytes.

class storage.fallback.FallbackStorage

Esta clase utiliza primero CookieStorage, y cae en SessionStorage para los mensajes que no caben en una sola cookie. También requiere la aplicación contrib.sessions de Django.

Este comportamiento evita escribir en la sesión siempre que sea posible. Debería proporcionar el mejor rendimiento en el caso general.

FallbackStorage es la clase de almacenamiento por defecto. Si no le conviene a tus necesidades, puedes seleccionar otra clase de almacenamiento estableciendo MESSAGE_STORAGE a su importación completa, por ejemplo:

MESSAGE_STORAGE = "django.contrib.messages.storage.cookie.CookieStorage"
class storage.base.BaseStorage

Para crear tu propia clase de almacenamiento, hereda la clase BaseStorage en django.contrib.messages.storage.base y implementa los métodos _get y _store.

Niveles de mensajes

El marco de trabajo de los mensajes se basa en una arquitectura de nivel configurable similar a la del módulo de registro de Python. Los niveles de mensaje permiten agrupar mensajes por tipo para que puedan ser filtrados o mostrados de manera diferente en vistas y plantillas.

Los niveles integrados, que se pueden importar directamente desde django.contrib.messages, son:

Constante

Propósito

DEBUG

Mensajes relacionados con el desarrollo que serán ignorados (o eliminados) en una implementación de producción

INFO

Mensajes informativos para el usuario

SUCCESS

Una acción fue exitosa, por ejemplo: «Tu perfil se actualizó con éxito»

WARNING

No ocurrió un error pero puede ser inminente

ERROR

Una acción no fue exitosa o ocurrió otro tipo de error

La configuración MESSAGE_LEVEL se puede utilizar para cambiar el nivel mínimo registrado (o se puede cambiar por solicitud _). Los intentos de agregar mensajes de un nivel inferior a este serán ignorados.

Etiquetas de mensaje

Las etiquetas de mensaje son una representación en cadena del nivel de mensaje más cualquier etiqueta adicional que se agregó directamente en la vista (consulte _Agregando etiquetas de mensaje adicionales para obtener más detalles). Las etiquetas se almacenan como una cadena y están separadas por espacios. Normalmente, las etiquetas de mensaje se utilizan como clases CSS para personalizar el estilo del mensaje según su tipo. Por defecto, cada nivel tiene una sola etiqueta que es la versión en minúsculas de su propia constante:

Nivel Constante

Etiqueta

DEBUG

debug

INFO

info

SUCCESS

success

WARNING

advertencia

ERROR

error

Para cambiar los etiquetas predeterminadas para un nivel de mensaje (ya sea incorporado o personalizado), establezca la configuración MESSAGE_TAGS en un diccionario que contenga los niveles que desee cambiar. Como esto extiende las etiquetas predeterminadas, solo necesita proporcionar etiquetas para los niveles que desee sobreescribir:

from django.contrib.messages import constants as messages

MESSAGE_TAGS = {
    messages.INFO: "",
    50: "critical",
}

El uso de mensajes en vistas y plantillas

add_message(request, level, message, extra_tags='', fail_silently=False)[fuente]

Agregar un mensaje

Para agregar un mensaje, llame a:

from django.contrib import messages

messages.add_message(request, messages.INFO, "Hello world.")

Algunos métodos de atajo proporcionan una forma estándar de agregar mensajes con etiquetas comúnmente utilizadas (que suelen representarse como clases HTML para el mensaje):

messages.debug(request, "%s SQL statements were executed." % count)
messages.info(request, "Three credits remain in your account.")
messages.success(request, "Profile details updated.")
messages.warning(request, "Your account expires in three days.")
messages.error(request, "Document deleted.")

Displaying messages

get_messages(request)[fuente]

En tu plantilla, utiliza algo como:

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

Si estás utilizando el procesador de contexto, asegúrate de que la plantilla se renderice con un RequestContext. De lo contrario, asegúrate de que messages esté disponible en el contexto de la plantilla.

Incluso si sabes que solo hay un mensaje, debes seguir iterando sobre la secuencia messages, porque de otra manera no se eliminará el almacenamiento de mensajes para la siguiente solicitud.

El procesador de contexto también proporciona una variable DEFAULT_MESSAGE_LEVELS que es un mapeo de los nombres de nivel de mensaje a su valor numérico:

{% if messages %}
<ul class="messages">
    {% for message in messages %}
    <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
        {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}Important: {% endif %}
        {{ message }}
    </li>
    {% endfor %}
</ul>
{% endif %}

Fuera de las plantillas, puedes utilizar get_messages():

from django.contrib.messages import get_messages

storage = get_messages(request)
for message in storage:
    do_something_with_the_message(message)

Por ejemplo, puedes obtener todos los mensajes para devolverlos en una JSONResponseMixin en lugar de una TemplateResponseMixin.

get_messages() devolverá una instancia del backend de almacenamiento configurado.

La clase Message

class Message[fuente]

Cuando iteras sobre la lista de mensajes en una plantilla, lo que obtienes son instancias de la clase Message. Solo tienen unos pocos atributos:

  • message: El texto real del mensaje.

  • level: Un entero que describe el tipo de mensaje (consulte la sección sobre niveles de mensajes arriba).

  • tags: Una cadena combinando todos los etiquetas del mensaje (extra_tags y level_tag) separadas por espacios.

  • extra_tags: Una cadena que contiene etiquetas personalizadas para este mensaje, separadas por espacios. Está vacía por defecto.

  • level_tag: La representación en cadena del nivel. Por defecto, es la versión minúscula del nombre de la constante asociada, pero esto se puede cambiar si es necesario mediante el MESSAGE_TAGS ajuste.

Creación de niveles de mensajes personalizados

Los niveles de mensajes son nada más que enteros, por lo que puedes definir tus propios constantes de nivel y utilizarlas para crear retroalimentación del usuario más personalizada, p. ej.:

CRITICAL = 50


def my_view(request):
    messages.add_message(request, CRITICAL, "A serious error occurred.")

Cuando se crean niveles de mensajes personalizados debes tener cuidado al evitar sobrecargar los niveles existentes. Los valores para los niveles integrados son:

Nivel Constante

Valor

DEBUG

10

INFO

20

SUCCESS

25

WARNING

30

ERROR

40

Si necesitas identificar los niveles personalizados en tu HTML o CSS, debes proporcionar una mapeación a través de la configuración MESSAGE_TAGS.

Nota

Si estás creando una aplicación reutilizable, se recomienda utilizar solo los niveles message integrados y no confiar en ningún nivel personalizado.

Cambiando el nivel mínimo registrado por solicitud

El nivel mínimo registrado puede establecerse por solicitud a través del método set_level:

from django.contrib import messages

# Change the messages level to ensure the debug message is added.
messages.set_level(request, messages.DEBUG)
messages.debug(request, "Test message...")

# In another request, record only messages with a level of WARNING and higher
messages.set_level(request, messages.WARNING)
messages.success(request, "Your profile was updated.")  # ignored
messages.warning(request, "Your account is about to expire.")  # recorded

# Set the messages level back to default.
messages.set_level(request, None)

De manera similar, se puede recuperar el nivel efectivo actual con get_level:

from django.contrib import messages

current_level = messages.get_level(request)

Para obtener más información sobre cómo funcionan los niveles de mensajes, consulta Niveles de mensaje arriba.

Agregando etiquetas de mensaje adicionales

Para más control directo sobre las etiquetas de mensajes, puedes proporcionar opcionalmente una cadena que contenga etiquetas adicionales a cualquier de los métodos de agregación:

messages.add_message(request, messages.INFO, "Over 9000!", extra_tags="dragonball")
messages.error(request, "Email box full", extra_tags="email")

Las etiquetas adicionales se agregan antes de la etiqueta predeterminada para ese nivel y están separadas por espacios.

Fallando silenciosamente cuando el marco de mensajes está deshabilitado

Si estás escribiendo una aplicación reutilizable (o otra pieza de código) y quieres incluir funcionalidad de mensajería, pero no quieres requerir a tus usuarios que la habiliten si no lo quieren, puedes pasar un argumento de palabra clave adicional fail_silently=True a cualquier de los métodos de la familia add_message. Por ejemplo:

messages.add_message(
    request,
    messages.SUCCESS,
    "Profile details updated.",
    fail_silently=True,
)
messages.info(request, "Hello world.", fail_silently=True)

Nota

Establecer fail_silently=True solo oculta el MessageFailure que de otro modo ocurriría cuando el marco de mensajes está deshabilitado y se intenta usar uno de los métodos de la familia add_message. No oculta fallas que puedan ocurrir por otras razones.

Agregar mensajes en vistas basadas en clases

class views.SuccessMessageMixin

Agrega un atributo de mensaje de éxito a las clases basadas en FormView

get_success_message(cleaned_data)

cleaned_data es los datos limpios del formulario que se utilizan para la formateación de cadenas

**Ejemplo views.py:

from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.edit import CreateView
from myapp.models import Author


class AuthorCreateView(SuccessMessageMixin, CreateView):
    model = Author
    success_url = "/success/"
    success_message = "%(name)s was created successfully"

Los datos limpios del form están disponibles para la interpolación de cadenas utilizando el sintaxis %(field_name)s. Para ModelForms, si necesitas acceso a campos desde el objeto guardado, sobreescribe el método get_success_message().

**Vistas.py de ejemplo para ModelForms:

from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.edit import CreateView
from myapp.models import ComplicatedModel


class ComplicatedCreateView(SuccessMessageMixin, CreateView):
    model = ComplicatedModel
    success_url = "/success/"
    success_message = "%(calculated_field)s was created successfully"

    def get_success_message(self, cleaned_data):
        return self.success_message % dict(
            cleaned_data,
            calculated_field=self.object.calculated_field,
        )

Expiración de mensajes

Los mensajes están marcados para ser eliminados cuando se itera la instancia de almacenamiento (y se eliminan al procesar la respuesta).

Para evitar que los mensajes sean eliminados, puedes establecer el almacenamiento de mensajes en False después de iterar:

storage = messages.get_messages(request)
for message in storage:
    do_something_with(message)
storage.used = False

Comportamiento de solicitudes paralelas

Debido a la forma en que funcionan las cookies (y por lo tanto las sesiones), el comportamiento de cualquier backend que utilice cookies o sesiones es indefinido cuando el mismo cliente realiza múltiples solicitudes que establecen o recuperan mensajes en paralelo. Por ejemplo, si un cliente inicia una solicitud que crea un mensaje en una ventana (o pestaña) y luego otra que recupera cualquier mensaje no iterado en otra ventana, antes de que la primera ventana redirija, el mensaje puede aparecer en la segunda ventana en lugar de la primera ventana donde se espera.

En resumen, cuando están involucradas múltiples solicitudes simultáneas del mismo cliente, los mensajes no se garantizan para ser entregados a la misma ventana que los creó ni, en algunos casos, en absoluto. Ten en cuenta que esto suele no ser un problema en la mayoría de las aplicaciones y se convertirá en un asunto sin importancia en HTML5, donde cada ventana/pestaña tendrá su propio contexto de navegación.

Configuración

Un par de configuraciones te dan control sobre el comportamiento de los mensajes:

  • NIVEL_DE_MENSAJE

  • ALMACÉN_DE_MENSAJES

  • ETIQUETAS_DE_MENSAJES

Los textos traducidos son:

  • DOMINIO_DE_COOKIE_DE_SESION

  • COOKIE_DE_SESION_SEGURA

  • HTTPONLY_COOKIE_DE_SESION

Pruebas

Este módulo ofrece un método de aserción de prueba personalizado, para probar mensajes asociados a una HttpResponse.

Para aprovechar esta aserción, agrega MessagesTestMixin a la jerarquía de clases:

from django.contrib.messages.test import MessagesTestMixin
from django.test import TestCase


class MsgTestCase(MessagesTestMixin, TestCase):
    pass

Luego, hereda de MsgTestCase en tus pruebas.

MessagesTestMixin.assertMessages(response, expected_messages, ordered=True)[fuente]

Aserta que los mensajes messages agregados a la respuesta coinciden con expected_messages.

expected_messages es una lista de objetos Message.

Por defecto, la comparación es dependiente de la ordenación. Puedes deshabilitar esto estableciendo el argumento ordered a False.