• Aucun résultat trouvé

Notifiez avec les signaux

III. Techniques avancées 122

14. Simplifions nos templates : filtres, tags et contextes 151

15.1. Notifiez avec les signaux

Premier mécanisme : les signaux. Un signal est une notification envoyée par une application à Django lorsqu’une action se déroule, et renvoyée par le framework à toutes les autres par-ties d’applications qui se sont enregistrées pour savoir quand ce type d’action se déroule, et comment.

Reprenons l’exemple de la suppression d’un modèle : imaginons que nous ayons plusieurs fichiers sur le disque dur liés à une instance d’un modèle. Lorsque l’instance est supprimée, nous souhaitons que les fichiers associés soient également supprimés. Cependant, cette entrée peut être supprimée depuis n’importe où dans le code, et vous ne pouvez pas à chaque fois rajouter un appel vers une fonction qui se charge de la suppression des fichiers associés (parce que ce serait trop lourd ou que cela ne dépend simplement pas de vous). Les signaux sont la solution parfaite à ce problème.

Pour résoudre ce problème, une fois que vous avez écrit la fonction de suppression des fichiers associés, vous n’avez qu’à indiquer à Django d’appeler cette fonction à chaque fois qu’une entrée de modèle est supprimée. En pratique, cela se fait ainsi :

1 from django.models.signals import post_delete 2

3 post_delete.connect(ma_fonction_de_suppression, sender=MonModele) La méthode est plutôt simple : il suffit d’importer le signal et d’utiliser la méthode connect pour connecter une fonction à ce signal. Le signal ici importé est post_delete, et comme son nom l’indique il est notifié à chaque fois qu’une instance a été supprimée. Chaque fois que Django recevra le signal, il le transmettra en appelant la fonction passée en argument (ma_fonc tion_de_suppression ici). Cette méthode peut prendre plusieurs paramètres, comme par exemple icisender, qui permet de restreindre l’envoi de signaux à un seul modèle (MonModele donc), sans quoi la fonction sera appelée pour toute entrée supprimée, et quel que soit le modèle dont elle dépend.

Une fonction appelée par un signal prend souvent plusieurs arguments. Généralement, elle prend presque toujours un argument appelé sender. Son contenu dépend du type de signal en lui-même (par exemple, pour post_delete, la variable sender passée en argument sera toujours le modèle concerné, comme vu précédemment). Chaque type de signal possède ses propres arguments. post_delete en prend trois :

— sender : le modèle concerné, comme vu précédemment ;

— instance: l’instance du modèle supprimée (celle-ci étant supprimée, il est très déconseillé de modifier ses données ou de tenter de la sauvegarder) ;

— using : l’alias de la base de données utilisée (si vous utilisez plusieurs bases de données, il s’agit d’un point particulier et inutile la plupart du temps).

Notre fonction ma_fonction_de_suppression pourrait donc s’écrire de la sorte :

1 def ma_fonction_de_suppression(sender, instance, **kwargs):

2 # processus de suppression selon les données fournies par instance

?

Pourquoi spécifier un **kwargs?

Vous ne pouvez jamais être certains qu’un signal renverra bien tous les arguments possibles, cela dépend du contexte. Dès lors, il est toujours important de spécifier un dictionnaire pour récupérer les valeurs supplémentaires, et si vous avez éventuellement besoin d’une de ces valeurs, il suffit de vérifier si la clé est bien présente dans le dictionnaire.

?

Où faut-il mettre l’enregistrement des signaux ?

Nous pouvons mettre l’enregistrement n’importe où, tant que Django charge le fichier afin qu’il puisse faire la connexion directement. Le framework charge déjà par défaut certains fichiers comme les models.py,urls.py, etc. Le meilleur endroit serait donc un de ces fichiers.

Généralement, nous choisissons un models.py (étant donné que certains signaux agissent à partir d’actions sur des modèles, c’est plutôt un bon choix !).

Petit détail, il est également possible d’enregistrer une fonction à un signal directement lors de sa déclaration avec un décorateur. En reprenant l’exemple ci-dessus :

1 from django.models.signals import post_delete 2 from django.dispatch import receiver

34 @receiver(post_delete, sender=MonModele)

5 def ma_fonction_de_suppression(sender, instance, **kwargs):

6 #processus de suppression selon les données fournies par instance

Il existe bien entendu d’autres types de signaux, voici une liste non exhaustive en contenant les principaux, avec les arguments transmis avec la notification :

Nom Description Arguments

django.db.models.si gnals.pre_save

Envoyé avant qu’une instance de modèle ne soit

enregistrée. sender : le modèle concerné

de modèle a été enregistrée. — sender: le modèle concerné

modèle ne soit supprimée. — sender : le modèle concerné

modèle a été supprimée. — sender : le modèle concerné

— instance: l’instance du modèle concernée

— using: l’alias de la BDD utilisée

django.core.signals.re quest_started

Envoyé à chaque fois que Django reçoit une nouvelle requête

quest_finished Envoyé à chaque fois que Django termine de répondre à une re-quête HTTP.

Il existe d’autres signaux inclus par défaut. Ils sont expliqués dans la documentation officielle : https ://docs.djangoproject.com/en/1.4/ref/signals/ .

Sachez que vous pouvez tester tous ces signaux simplement en créant une fonction affichant une ligne dans la console (avec print) et en liant cette fonction aux signaux désirés.

Heureusement, si vous vous sentez limités par la (maigre) liste de types de signaux fournis par Django, sachez que vous pouvez en créer vous-mêmes. Le processus est plutôt simple.

Chaque signal est en fait une instance de django.dispatch.Signal. Pour créer un nouveau signal, il suffit donc de créer une nouvelle instance, et de lui dire quels arguments le signal peut transmettre :

1 import django.dispatch

23 crepe_finie = django.dispatch.Signal(providing_args=["adresse",

"prix"])

Ici, nous créons un nouveau signal nommé crepe_finie. Nous lui indiquons une liste contenant les noms d’éventuels arguments (les arguments de signaux n’étant jamais fixes, vous pouvez la modifier à tout moment) qu’il peut transmettre, et c’est tout !

Nous pourrions dès lors enregistrer une fonction sur ce signal, comme vu précédemment :

1 crepe_finie.connect(faire_livraison)

Lorsque nous souhaitons lancer une notification à toutes les fonctions enregistrées au signal, il suffit simplement d’utiliser la méthode send, et ceci depuis n’importe où. Nous l’avons fait depuis un modèle :

1 class Crepe(models.Model):

2 nom_recette = models.CharField(max_length=255) 3 prix = models.IntegerField()

4 #d'autres attributs 5

6 def preparer(self, adresse):

7 # Nous préparons la crêpe pour l'expédier à l'adresse transmise

8 crepe_finie.send(sender=self, adresse=adresse, prix=self.prix)

À chaque fois que la méthode preparerd’une crêpe sera appelée, la fonction faire_livrai son le sera également avec les arguments adéquats. Notons ici qu’il est toujours obligatoire de préciser un argument senderlorsque nous utilisons la méthode send. Libre à vous de choisir ce que vous souhaitez transmettre, mais il est censé représenter l’entité qui est à l’origine du signal. Nous avons ici choisi d’envoyer directement l’instance du modèle.

Aussi, la fonction send retourne une liste de paires de variables, chaque paire étant un tuple de type (receveur, retour), où le receveur est la fonction appelée, et le retour est la variable retournée par la fonction. Par exemple, si nous n’avons que la fonction faire_livraison connectée au signalcrepe_finie, et que celle-ci retourne Truesi la livraison s’est bien déroulée (considérons que c’est le cas maintenant), la liste renvoyée parsendserait[(faire_livraison,

True)].

Pour terminer, il est également possible de déconnecter une fonction d’un signal. Pour ce faire, il faut utiliser la méthode disconnect du signal, cette dernière s’utilise commeconnect :

1 crepe_finie.disconnect(faire_livraison)

crepe_finie n’appellera plus faire_livraison si une notification est envoyée. Sachez que, si vous avez soumis un argument sender lors de la connexion, vous devez également le préciser lors de la déconnexion.