• Aucun résultat trouvé

Notifiez avec les signau

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 parties 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 :

Code : Python

from django.models.signals import post_delete

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_fonction_de_suppression ici). Cette méthode peut prendre plusieurs paramètres, comme par exemple ici sender, 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 :

Code : Python

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

#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 :

Code : Python

from django.models.signals import post_delete

from django.dispatch import receiver

@receiver(post_delete, sender=MonModele)

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

#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.signals.pre_save

Envoyé avant qu'une instance de modèle ne soit enregistrée.

sender : le modèle concerné

instance : l'instance du modèle concernée using : l'alias de la BDD utilisée

raw : un booléen, mis à True si l'instance sera enregistrée telle qu'elle est présentée depuis l'argument

django.db.models.signals.post_save

Envoyé après qu'une instance de modèle a été enregistrée.

sender : le modèle concerné

instance : l'instance du modèle concernée using : l'alias de la BDD utilisée

raw : un booléen, mis à True si l'instance sera enregistrée telle qu'elle est présentée depuis l'argument created : un booléen, mis à True si l'instance a été correctement enregistrée

django.db.models.signals.pre_delete

Envoyé avant qu'une instance de modèle ne soit supprimée.

sender : le modèle concerné

instance : l'instance du modèle concernée using : l'alias de la BDD utilisée

django.db.models.signals.post_delete

Envoyé après qu'une instance de 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.request_started

Envoyé à chaque fois que Django

nouvelle requête HTTP.

django.core.handlers.wsgi.WsgiHandler

django.core.signals.request_finished

Envoyé à chaque fois que Django termine de répondre à une requête HTTP.

sender : la classe qui a envoyé la requête, par exemple

django.core.handlers.wsgi.WsgiHandler

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 :

Code : Python

import django.dispatch

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 :

Code : Python

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 :

Code : Python

class Crepe(models.Model):

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

#d'autres attributs

def preparer(self, adresse):

# Nous préparons la crêpe pour l'expédier à l'adresse transmise crepe_finie.send(sender=self, adresse=adresse, prix=self.prix)

À chaque fois que la méthode preparer d'une crêpe sera appelée, la fonction faire_livraison le sera également avec les arguments adéquats.

Notons ici qu'il est toujours obligatoire de préciser un argument sender lorsque 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 signal crepe_finie, et que celle-ci retourne True si la livraison s'est bien déroulée (considérons que c'est le cas maintenant), la liste renvoyée par send serait [(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 comme connect :

Code : Python

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.