Signaux¶
Django contient un « distributeur de signaux » qui permet aux applications découplées de pouvoir plus facilement être averties quand des actions se produisent ailleurs dans un projet. En résumé, les signaux permettent à certains expéditeurs d’avertir un ensemble de destinataires qu’une action a eu lieu. Ils sont particulièrement utiles lorsque beaucoup de parties de code peuvent être intéressées aux mêmes événements.
Django intègre un ensemble de signaux permettant à du code client d’être averti par Django lui-même de certaines actions. Parmi ceux-ci, en voici quelques-uns bien utiles :
django.db.models.signals.pre_save
&django.db.models.signals.post_save
Envoyés avant et après que la méthode
save()
d’un modèle a été appelée.django.db.models.signals.pre_delete
&django.db.models.signals.post_delete
Envoyés avant et après que la méthode
delete()
d’un modèle ou que la méthodedelete()
d’unQuerySet
a été appelée.django.db.models.signals.m2m_changed
Envoyé quand un champ
ManyToManyField
d’un modèle a été modifié.django.core.signals.request_started
&django.core.signals.request_finished
Envoyés lorsque Django démarre et termine une requête HTTP.
Consultez la documentation des signaux intégrés pour une liste complète ainsi qu’une explication détaillée de chaque signal.
Vous pouvez également définir et envoyer vos propres signaux personnalisés ; voir ci-dessous.
Écoute de signaux¶
Pour recevoir un signal, inscrivez une fonction réceptrice en utilisant la méthode Signal.connect()
. Cette fonction sera appelée au moment où le signal est envoyé.
-
Signal.
connect
(receiver, sender=None, weak=True, dispatch_uid=None)[source]¶ Paramètres: - receiver – La fonction réceptrice qui sera connectée à ce signal. Voir Fonctions réceptrices pour plus d’informations.
- sender – Indique un expéditeur particulier duquel recevoir les signaux. Voir Connexion aux signaux envoyés par des expéditeurs spécifiques pour plus d’informations.
- weak – Django stocke les gestionnaires de signaux comme référence faible par défaut. Cela signifie que si le récepteur est une fonction locale, il peut être purgé de la mémoire. Pour empêcher cela, indiquez
weak=False
lors de l’appel à la méthodeconnect()
du signal. - dispatch_uid – Un identifiant unique pour un récepteur de signal afin d’éviter que certains signaux puissent être envoyés à double. Voir Prévention des signaux dupliqués pour plus d’informations.
Voyons comment ça fonctionne en inscrivant un signal qui sera appelé à la fin de chaque requête HTTP. Nous allons nous connecter au signal request_finished
.
Fonctions réceptrices¶
Tout d’abord, nous devons définir une fonction réceptrice. Celle-ci peut être n’importe quelle fonction ou méthode Python :
def my_callback(sender, **kwargs):
print("Request finished!")
Remarquez que la fonction accepte un paramètre sender
, accompagné des paramètres nommés génériques (**kwargs
) ; tous les gestionnaires de signal doivent accepter ces paramètres.
Nous aborderons le paramètre sender
un peu plus loin, mais commençons par examiner le paramètre **kwargs
. Tous les signaux envoient des paramètres nommés et peuvent changer ces paramètres nommés à tout instant. Dans le cas de request_finished
, sa documentation indique qu’il n’envoie pas de paramètre, ce qui signifie que nous pourrions être tentés d’écrire notre fonction de signal comme my_callback(sender)
.
Ce serait une erreur. En fait, Django génère une exception dans ce cas, parce que l’on doit s’attendre à ce que de nouveaux paramètres soient ajoutés dans le temps et la fonction réceptrice doit être capable d’accepter ces nouveaux paramètres.
Connexion des fonctions réceptrices¶
Il y a deux façons de connecter une fonction réceptrice à un signal. Vous pouvez choisir l’option de la connexion manuelle :
from django.core.signals import request_finished
request_finished.connect(my_callback)
L’autre possibilité est d’utiliser le décorateur receiver()
:
-
receiver
(signal)[source]¶ Paramètres: signal – Un signal ou une liste de signaux auxquels connecter la fonction.
Voici comment faire la connexion avec le décorateur :
from django.core.signals import request_finished
from django.dispatch import receiver
@receiver(request_finished)
def my_callback(sender, **kwargs):
print("Request finished!")
À partir de cet instant, la fonction my_callback
sera appelée chaque fois qu’une requête se termine.
À quel endroit ce code devrait-il se trouver ?
Strictement parlant, le code du signal et le code d’inscription peuvent se trouver n’importe où, même s’il est recommandé d’éviter le module racine de l’application et son module models
pour minimiser les effets de bord de l’importation du code.
En pratique, les gestionnaires de signaux sont généralement définis dans un sous-module signals
de l’application correspondante. Les récepteurs de signaux sont connectés dans la méthode ready()
de la classe de configuration de l’application. Si vous utilisez le décorateur receiver()
, importez simplement le sous-module signals
à l’intérieur de ready()
.
Note
Il est possible que la méthode ready()
soit exécutée plus d’une fois durant les tests. Par conséquent, il est préférable d”empêcher la duplication des signaux, particulièrement si vous pensez les exploiter dans les tests.
Connexion aux signaux envoyés par des expéditeurs spécifiques¶
Certains signaux sont envoyés de nombreuses fois, mais vous n’êtes pas toujours intéressé à tous les recevoir. Par exemple, considérez le signal django.db.models.signals.pre_save
envoyé avant chaque enregistrement de modèle. La plupart du temps, vous n’avez pas besoin de savoir quand chaque modèle est enregistré, mais seulement pour un modèle spécifique.
Dans ces situations, vous pouvez inscrire une fonction pour qu’elle ne reçoive les signaux que de certains expéditeurs. Dans le cas de django.db.models.signals.pre_save
, l’expéditeur sera la classe du modèle en cours d’enregistrement, il est donc possible d’indiquer que vous ne voulez recevoir que les signaux envoyés par certains modèles :
from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel
@receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
...
La fonction my_handler
ne sera appelée que lors de l’enregistrement d’une instance de MyModel
.
Différents signaux utilisent différents objets comme expéditeurs ; il s’agit de consulter la documentation des signaux intégrés pour plus de détails sur chaque signal.
Prévention des signaux dupliqués¶
Dans certaines circonstances, le code faisant la connexion entre les récepteurs et les signaux peut être exécuté à plusieurs reprises. En conséquence, la fonction de réception peut être inscrite plus d’une fois et être ensuite appelée plusieurs fois pour un même événement de signal.
Si ce comportement est problématique (par exemple quand des signaux sont utilisés pour envoyer des courriels quand un modèle est enregistré), indiquez un identifiant unique dans le paramètre dispatch_uid
pour identifier votre fonction réceptrice. Il s’agit généralement d’une chaîne de caractères, même si n’importe quel objet hachable peut faire l’affaire. En conséquence, la fonction réceptrice ne sera inscrite au signal qu’une seule fois par valeur unique de dispatch_uid
:
from django.core.signals import request_finished
request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")
Définition et envoi de signaux¶
Les applications peuvent profiter de l’infrastructure des signaux et fournir leurs propres signaux.
Définition de signaux¶
Tous les signaux sont des instances de django.dispatch.Signal
. Le paramètre providing_args
est une liste de noms de paramètres nommés que le signal transmettra aux récepteurs. L’objectif est toutefois uniquement documentaire, car rien ne contrôle que le signal envoie vraiment ces paramètres aux récepteurs.
Par exemple :
import django.dispatch
pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])
Cet exemple déclare un signal pizza_done
qui va informer ses récepteurs par les paramètres toppings
et size
.
Souvenez-vous que vous êtes autorisé à modifier cette liste de paramètres à tout moment, il n’est donc pas nécessaire de trouver la bonne API du premier coup.
Envoi de signaux¶
Il y a deux façons d’envoyer des signaux dans Django.
Pour envoyer un signal, appelez Signal.send()
(tous les signaux intégrés l’utilisent) ou Signal.send_robust()
. Vous devez indiquer le paramètre sender
(qui est une classe la plupart du temps) et vous pouvez ajouter autant de paramètres nommés que vous le souhaitez.
Par exemple, voici comment envoyer notre signal pizza_done
:
class PizzaStore(object):
...
def send_pizza(self, toppings, size):
pizza_done.send(sender=self.__class__, toppings=toppings, size=size)
...
Aussi bien send()
que send_robust()
renvoient uns liste de paires de tuples [(récepteur, réponse), ... ]
correspondant à la liste des fonctions réceptrices appelées et la valeur de leur réponse.
send()
diffère de send_robust()
par la manière dont les exceptions générées par les fonctions réceptrices sont traitées. send()
n’intercepte aucune exception générée par les récepteurs ; elle laisse simplement les erreurs se propager. Il est donc possible que certains récepteurs ne soient pas notifiés par le signal en cas d’erreur.
send_robust()
intercepte toutes les erreurs héritant de la classe Exception
de Python et s’assure que tous les récepteurs soient notifiés par le signal. Si une erreur survient, l’instance d’erreur est renvoyée dans le tuple correspondant au récepteur qui a généré l’erreur.
Les traces de débogage sont présentes dans l’attribut __traceback__
des erreurs renvoyées lors des appels à send_robust()
.
Déconnexion des signaux¶
Pour déconnecter un récepteur d’un signal, appelez Signal.disconnect()
. Les paramètres sont identiques à ceux décrits pour Signal.connect()
. La méthode renvoie True
si un récepteur a été déconnecté, sinon False
.
Le paramètre receiver
indique le récepteur inscrit qu’il s’agit de déconnecter. Il peut valoir None
si dispatch_uid
est utilisé pour identifier le récepteur.
Obsolète depuis la version 1.9: Le paramètre weak
est obsolète car il n’a aucun effet. Il sera supprimé dans Django 2.0.