L’infrastructure des messages

Il est fréquent que les applications Web ont besoin d’afficher des messages de notification (aussi appelés « messages flash ») pour les utilisateurs après le traitement d’un formulaire ou de tout autre type de saisie d’utilisateur.

Pour cela, Django offre une prise en charge complète des messages stockés dans des cookies ou dans les sessions, aussi bien pour des utilisateurs anonymes que des utilisateurs connectés. L’infrastructure des messages permet de stocker temporairement des messages dans une requête et de les récupérer ensuite dans une prochaine requête (en générale la suivante) pour les afficher. Chaque message est étiqueté avec un niveau spécifique déterminant sa priorité (par ex. info, warning ou error).

Activation des messages

Les messages sont implémentés par une classe d’intergiciel accompagnée d’un processeur de contexte.

Le fichier settings.py par défaut créé par django-admin startproject contient déjà tous les réglages nécessaires pour activer la fonctionnalité des messages :

  • 'django.contrib.messages' figure dans INSTALLED_APPS.

  • MIDDLEWARE_CLASSES contient 'django.contrib.sessions.middleware.SessionMiddleware' et 'django.contrib.messages.middleware.MessageMiddleware'.

    Le moteur de stockage par défaut dépend des sessions. C’est pourquoi SessionMiddleware doit être actif et apparaître avant MessageMiddleware dans MIDDLEWARE_CLASSES.

  • L’option 'context_processors' du moteur DjangoTemplates défini dans le réglage TEMPLATES contient 'django.contrib.messages.context_processors.messages'.

Si vous ne souhaitez pas utiliser les messages, vous pouvez enlever 'django.contrib.messages' du réglage INSTALLED_APPS, enlever la ligne MessageMiddleware du réglage MIDDLEWARE_CLASSES et enlever le processeur de contexte messages du réglage TEMPLATES.

Configuration du moteur de message

Moteurs de stockage

L’infrastructure des messages peut utiliser différents moteurs pour stocker temporairement les messages.

Django fournit trois classes de stockage intégrées dans django.contrib.messages:

class storage.session.SessionStorage

Cette classe stocke tous les messages à l’intérieur de la session de la requête. Elle a donc besoin de l’application contrib.sessions de Django.

class storage.cookie.CookieStorage

Cette classe stocke les données de messages dans un cookie (signé par l’empreinte secrète pour éviter d’être manipulé) pour que les notifications soient conservées d’une requête à l’autre. Les anciens messages sont effacés si la taille des données du cookie dépasse 2048 octets.

class storage.fallback.FallbackStorage

Cette classe utilise d’abord CookieStorage, puis se rabat sur SessionStorage pour les messages qui ne rentrent pas dans un seul cookie. Elle exige également la présence de l’application contrib.sessions de Django.

Ce comportement évite d’écrire dans la session autant que possible. Cela donne en général les meilleures performances.

FallbackStorage est la classe de stockage par défaut. Si elle ne correspond pas à vos besoins, vous pouvez choisir une autre classe de stockage en définissant MESSAGE_STORAGE au chemin complet d’importation de la classe, par exemple :

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

Pour écrire votre propre classe de stockage, héritez de la classe BaseStorage dans django.contrib.messages.storage.base et implémentez les méthodes _get et _store.

Niveaux de messages

L’infrastructure des messages est basée sur une architecture de niveaux configurable et semblable à celle du module de journalisation de Python. Les niveaux de messages permettent de grouper des messages par type afin qu’ils puissent être filtrés ou affichés différemment dans les vues et les gabarits.

Les niveaux intégrés, qui peuvent être importés directement depuis django.contrib.messages, sont :

Constante

But

DEBUG

Messages liés au développement qui seront ignorés (ou supprimés) dans un déploiement en production.

INFO

Messages d’information pour l’utilisateur.

SUCCESS

Une action a réussi, par ex. « votre profil a été mis à jour avec succès ».

WARNING

Il n’y a pas eu d’erreur, mais il pourrait y en avoir une sous peu.

ERROR

Une action n’a pas réussi ou une autre erreur quelconque s’est produite.

Le réglage MESSAGE_LEVEL peut être utilisé pour modifier le niveau minimum enregistré (ou il peut être modifié selon la requête). Les tentatives d’ajouter des messages à un niveau inférieur à celui-ci seront ignorées.

Étiquettes de messages

Les étiquettes de messages sont une représentation textuelle du niveau de message additionnée d’éventuelles étiquettes complémentaires ajoutées directement dans la vue (voir Ajout d’étiquettes de messages supplémentaires ci-dessous pour plus de détails). Les étiquettes sont stockées dans une chaîne et sont séparées par des espaces. Typiquement, les étiquettes de messages sont utilisées comme classes CSS pour personnaliser le style des messages en fonction de leur type. Par défaut, chaque niveau possède une seule étiquette sous forme d’une version en minuscules de sa constante :

Constante de niveau

Étiquette

DEBUG debug
INFO info
SUCCESS success
WARNING warning
ERROR error

Pour modifier les étiquettes par défaut d’un niveau de message (intégré ou personnalisé), définissez le réglage MESSAGE_TAGS à un dictionnaire contenant les niveaux que vous souhaitez modifier. Comme cela étend les étiquettes par défaut, il suffit d’indiquer les étiquettes des niveaux que l’on souhaite remplacer :

from django.contrib.messages import constants as messages
MESSAGE_TAGS = {
    messages.INFO: '',
    50: 'critical',
}

Utilisation des messages dans les vues et les gabarits

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

Ajout d’un message

Pour ajouter un message, appelez :

from django.contrib import messages
messages.add_message(request, messages.INFO, 'Hello world.')

Quelques méthodes de raccourci fournissent une manière standard d’ajouter des messages avec les étiquettes couramment utilisées (qui sont généralement représentées sous formes de classes HTML des messages) :

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.')

Affichage des messages

get_messages(request)[source]

Dans votre gabarit, utilisez quelque chose comme :

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

Si vous utilisez le processeur de contexte, le gabarit devrait être produit avec un RequestContext. Sinon, assurez-vous que messages soit disponible dans le contexte du gabarit.

Même si vous savez qu’il n’y a qu’un message, il faut tout de même passer en boucle la liste des messages, sinon le message ne sera pas effacé de son stockage pour la requête suivante.

Le processeur de contexte fournit également une variable DEFAULT_MESSAGE_LEVELS qui fait correspondre les niveaux de messages à leur valeur numérique :

{% 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 %}

En dehors des gabarits, vous pouvez utiliser get_messages():

from django.contrib.messages import get_messages

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

Par exemple, vous pouvez récupérer tous les messages pour les renvoyer dans une réponse JSONResponseMixin au lieu d’une réponse TemplateResponseMixin.

get_messages() renvoie une instance du moteur de stockage configuré.

La classe Message

class storage.base.Message

Lorsque vous parcourez en boucle la liste des messages dans un gabarit, vous obtenez des instances de la classe Message. Il s’agit d’un objet très simple, ne possédant que quelques attributs :

  • message: le contenu textuel du message.

  • level: un nombre entier décrivant le type du message (voir la section sur les niveaux de messages ci-dessus).

  • tags: une chaîne combinant toutes les étiquettes de messages (extra_tags et level_tag) séparées par des espaces.

  • extra_tags: une chaîne contenant les étiquettes personnalisées du message, séparées par des espaces. Vide par défaut.

  • level_tag: la représentation textuelle du niveau. Par défaut, il s’agit de la version en minuscules du nom de la constante associée, mais cela peut être modifié en cas de besoin par l’emploi du réglage MESSAGE_TAGS.

Création de niveaux de messages personnalisés

Les niveaux de messages ne sont en fait que des nombres entiers, vous pouvez donc facilement définir vos propres niveaux et les utiliser pour créer des retours d’information plus personnalisés, par exemple :

CRITICAL = 50

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

Si vous créez des niveaux de messages personnalisés, vous devriez éviter de remplacer des niveaux existants. Les valeurs des niveaux intégrés sont :

Constante de niveau

Valeur

DEBUG 10
INFO 20
SUCCESS 25
WARNING 30
ERROR 40

Si vous avez besoin d’identifier les niveaux personnalisés dans du code HTML ou CSS, il est nécessaire de fournir une correspondance dans le réglage MESSAGE_TAGS.

Note

Si vous créez une application réutilisable, il est recommandé de n’utiliser que les niveaux de messages intégrés et de ne pas compter sur des niveaux personnalisés.

Modification du niveau minimal enregistré par requête

Le niveau minimal enregistré peut être défini par requête au travers de la méthode 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 même, le niveau actuel effectif peut être obtenu par get_level:

from django.contrib import messages
current_level = messages.get_level(request)

Pour plus d’informations sur le fonctionnement du niveau minimal enregistré, voir Niveaux de messages ci-dessus.

Ajout d’étiquettes de messages supplémentaires

Pour un contrôle plus direct sur les étiquettes de messages, on peut indiquer une chaîne contenant des étiquettes supplémentaires aux diverses méthodes d’ajout :

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

Les étiquettes supplémentaires sont ajoutées avant l’étiquette par défaut du niveau et sont séparées par des espaces.

Échec silencieux quand l’infrastructure des messages n’est pas installée

Lors de l’écriture d’une application réutilisable (ou autre bout de code) et que l’on veut inclure la fonctionnalité des messages mais sans exiger des utilisateurs son activation s’ils ne le souhaitent pas, il est possible de transmettre un paramètre nommé supplémentaire fail_silently=True aux diverses méthodes de la famille add_message. Par exemple :

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

Note

En définissant fail_silently=True, cela ne fait que masquer les erreurs MessageFailure qui apparaissent normalement lorsque l’infrastructure des messages n’est pas active et que quelqu’un essaie d’utiliser l’une des méthodes de la famille add_message. Cela ne masque pas les erreurs qui pourraient se produire pour d’autres raisons.

Ajout de messages dans les vues fondées sur les classes

class views.SuccessMessageMixin

Ajoute un attribut de message de succès aux classes basées sur FormView.

get_success_message(cleaned_data)

cleaned_data correspond aux données nettoyées du formulaire utilisé pour la mise en forme de la chaîne.

Exemple de fichier views.py:

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

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

Les données nettoyées de form sont disponibles pour l’interpolation de chaîne avec la syntaxe %(nom_champ)s. Pour les ModelForm, si vous avez besoin d’accéder aux champs de l’objet enregistré, surchargez la méthode get_success_message().

Exemple de fichier views.py pour ModelForms :

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

class ComplicatedCreate(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,
        )

Expiration des messages

Les messages sont marqués pour leur suppression lorsqu’on boucle sur leur instance de stockage (puis effacés lorsque la réponse est traitée).

Pour empêcher que les messages soient effacés, il est possible de définir le stockage des messages à False après l’itération :

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

Comportement des requêtes parallèles

En raison du fonctionnement des cookies (et donc des sessions), le comportement des moteurs qui se basent sur des cookies ou des sessions n’est pas défini lorsqu’un même client effectue plusieurs requêtes qui définissent ou lisent des messages en parallèle. Par exemple, si un client démarre une requête qui crée un message dans une fenêtre (ou un onglet) puis une autre qui récupère les messages non lus dans une autre fenêtre avant que la première fenêtre ait reçu sa redirection, il se peut que le message apparaisse dans la seconde fenêtre au lieu de la première où il aurait dû apparaître.

En bref, lorsque plusieurs requêtes simultanées proviennent du même client, il n’y a aucune garantie que les messages soient affichés dans la même fenêtre qui les a produits, ou qu’ils soient effectivement affichés quelque part. Notez que cela ne pose habituellement pas de problème dans la plupart des applications et que ce problème va disparaître dans HTML5 où chaque fenêtre/onglet possède son propre contexte de navigation.

Réglages

Quelques réglages permettent de contrôler le comportement des messages :

Pour les moteurs qui utilisent les cookies, les réglages du cookie sont tirés des réglages du cookie de session :

Back to Top