Où les gestionnaires de signaux devraient-ils vivre dans un projet django?

144

Je viens de commencer à implémenter des écouteurs de signal dans un projet django. Bien que je comprenne ce qu'ils sont et comment les utiliser. J'ai du mal à trouver où je devrais les mettre. La documentation du site django dit ceci:

Où devrait vivre ce code?

Vous pouvez mettre la gestion du signal et le code d'enregistrement où vous le souhaitez. Cependant, vous devrez vous assurer que le module dans lequel il se trouve est importé tôt afin que la gestion du signal soit enregistrée avant que des signaux ne soient envoyés. Cela fait de models.py de votre application un bon endroit pour enregistrer les gestionnaires de signaux.

Bien que ce soit une bonne suggestion, avoir des classes ou des méthodes non modèles dans mon models.py me frotte dans le mauvais sens.

Alors, quelle est la meilleure pratique / règle pour stocker et enregistrer les gestionnaires de signaux?

Jason Webb
la source

Réponses:

41

J'aime en fait en faire des méthodes de classe du modèle lui-même. Cela garde tout dans une seule classe et signifie que vous n'avez pas à vous soucier d'importer quoi que ce soit.

Daniel Roseman
la source
2
Et où connectez-vous habituellement les gestionnaires aux signaux?
DataGreed
1
@DataGreed: en bas des models.py pertinents.
Daniel Roseman
102
Si vous écoutez les signaux émis par ce modèle, y placer tous les auditeurs rend tout l'exercice inutile, n'est-ce pas? Le but des signaux est de se découpler. Les auditeurs ne devraient-ils pas vivre avec le code qui s'intéresse à ces événements à distance? La question est de savoir comment s'assurer que les auditeurs sont chargés avant les émetteurs.
John Mee
Dans mon cas, je veux écouter un signal de modèle Fooqui fait partie de fooapp. Mais le récepteur de signal est une extension et vit dans une application différente (par exemple otherapp).
guettli
2
Pour le point de John Mee, ce n'est pas très différent de simplement remplacer save (), etc.
Matt
246

Cela a été ajouté à la documentation lors de la sortie de Django 1.7 :

À proprement parler, la gestion du signal et le code d'enregistrement peuvent vivre où vous le souhaitez, bien qu'il soit recommandé d'éviter le module racine de l'application et son module de modèles pour minimiser les effets secondaires de l'importation de code.

En pratique, les gestionnaires de signaux sont généralement définis dans un sous-module de signaux de l'application à laquelle ils se rapportent. Les récepteurs de signaux sont connectés dans la méthode ready () de la classe de configuration de votre application. Si vous utilisez le décorateur receiver (), importez simplement le sous-module signaux dans ready ().

Modifié dans Django 1.7: Puisque ready () n'existait pas dans les versions précédentes de Django, l'enregistrement du signal se produisait généralement dans le module models.

La meilleure pratique consiste à définir vos gestionnaires dans handlers.py dans un sous-module de signaux, par exemple un fichier qui ressemble à:

yourapp / signaux / handlers.py :

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):
    pass

Le meilleur endroit pour enregistrer votre gestionnaire de signaux est alors dans l'AppConfig de l'application qui le définit, en utilisant la méthode ready () . Cela ressemblera à ceci:

yourapp / apps.py :

from django.apps import AppConfig

class TasksConfig(AppConfig):
    name = 'tasks'
    verbose_name = "Tasks"

    def ready(self):
        import yourproject.yourapp.signals.handlers #noqa

Assurez-vous que vous chargez votre AppConfig en le spécifiant soit directement dans votre INSTALLED_APPS settings.py, soit dans le __init__de votre application. Voir la documentation ready () pour plus d'informations.

Remarque: si vous fournissez également des signaux à d'autres applications pour les écouter, placez-les __init__dans votre module de signaux, par exemple un fichier qui ressemble à:

votreapp / signaux / __ init__.py

import django.dispatch

task_generate_pre_save = django.dispatch.Signal(providing_args=["task"])

Une autre application peut alors écouter votre signal en l'important et en l'enregistrant, par exemple from yourapp.signals import task_generate_pre_save. Séparer vos signaux de vos gestionnaires permet de garder les choses propres.

Instructions pour Django 1.6:

Si vous êtes toujours bloqué sur Django 1.6 ou une version antérieure, vous feriez la même chose (définissez vos gestionnaires dans votre application / signaux / handlers.py) mais plutôt que d'utiliser AppConfig, vous chargeriez les gestionnaires via le __init__.py de votre application, par exemple quelque chose comme:

votreapp / __ init__.py

import signals

Ce n'est pas aussi agréable que d'utiliser la méthode ready () car cela provoque souvent des problèmes d'importation circulaire.

Aidan
la source
3
comme la documentaiton dit que vous remplacez prêt, vous pouvez faire quelque chose comme super (ReportsConfig, self) .ready () au cas où django déciderait de remplir ready () avec quelque chose (à partir de la 1.7.0, il est actuellement vide)
w- -
3
Je pense que cette réponse est la meilleure car elle est la seule à traiter les effets secondaires des importations. Je suis venu ici à la recherche des meilleures pratiques, car je nettoie une application qui est cassée exactement à cause de ce type d'effets secondaires. Hélas, l'application fonctionne sur django 1.6, et les meilleures pratiques ne fonctionnent que sur django 1.7. La solution temporaire consistant à laisser __init__importer des signaux ne fonctionnerait pas pour moi, donc je me demande s'il y a un autre endroit d'où je pourrais importer des signaux jusqu'à ce que nous soyons prêts à passer à une version ultérieure de django.
kasperd
Ne devrait-il pas y en avoir from . import handlers(ou similaire) yourapp/signals/__init__.py?
dhobbs
Ne devriez-vous pas également importer le module handlers.py quelque part? J'essaye ceci et cela ne semble pas définir le gestionnaire du signal.
Andrés
1
fwiw Je n'avais pas besoin du yourproject.dans la dernière ligne du bloc de code de classe TaskConfig. Cela fonctionne avec exactement cette structure, alors considérez ceci qa :)
Greg Kaleka
40

Je viens juste de rencontrer cela, et comme mes signaux ne sont pas liés au modèle, j'ai pensé ajouter ma solution.

J'enregistre diverses données autour de la connexion / déconnexion, et je devais me connecter django.contrib.auth.signals.

J'ai mis les gestionnaires de signaux dans un signals.pyfichier, puis __init__.pyj'ai importé des signaux à partir du fichier de module, car je pense que cela est appelé dès que l'application démarre (le test avec une printinstruction suggère qu'elle est appelée avant même que le fichier de paramètres ne soit lu.)

# /project/__init__.py
import signals

et dans signaux.py

# /project/signals.py
from django.contrib.auth.signals import user_logged_in

def on_logged_in(sender, user, request, **kwargs):
    print 'User logged in as: \'{0}\''.format(user)

user_logged_in.connect(on_logged_in)

Je suis assez nouveau sur Django (/ python), donc je suis ouvert à quiconque me dit que c'est une idée terrible!

Hugo Rodger-Marron
la source
3
Cela semble logique, mais je suggérerais de le faire au niveau de l'application.
Nils
2
Attention, cette logique entraînera très probablement des signaux dupliqués. user_logged_in.connect(on_logged_in)devrait probablement passer l' dispatch_uidargument. Plus d'informations sur docs.djangoproject.com/en/dev/topics/signals/… .
Scott Coates
Merci pour cela - bon à savoir. J'enregistre toutes les connexions en utilisant cette méthode (enregistrement IP / agent utilisateur), et je n'ai eu aucun doublon jusqu'à présent - bien que cela ne signifie pas qu'un petit changement sur toute la ligne ne posera pas de problème!
Hugo Rodger-Brown
13

Je viens de lire cet article sur les meilleures pratiques en matière de présentation de vos projets / applications, et il suggère que tous vos signaux de répartiteur personnalisés doivent être placés dans un fichier appelé signals.py. Cependant, cela ne résout pas complètement votre problème, car vous devez toujours les importer quelque part, et plus ils sont importés tôt, mieux c'est.

La suggestion de modèle est bonne. Puisque vous avez déjà tout défini dans votre signals.pyfichier, cela ne devrait pas prendre plus d'une ligne en haut du fichier. Ceci est similaire à la façon dont le admin.pyfichier est présenté (avec les définitions de classe en haut et le code d'enregistrement de toutes les classes d'administration personnalisées en bas), si vous définissez vos signaux, connectez-les dans le même fichier.

J'espère que cela pourra aider! En fin de compte, cela dépend de ce que vous préférez.

hora
la source
1
Je voulais aussi mettre mes gestionnaires de signaux dans un signals.pyfichier, mais je ne savais pas comment il devait être appelé par la suite. En l'important dans mon models.pyfichier, j'ai obtenu une solution très propre, sans "polluer" mon fichier models.py. Je vous remercie! :)
Danilo Bargen
10
il y a une importation croisée là-bas: signaux.py essaie d'importer le modèle de models.py
Ivan Virabyan
8

models.py et signaux.py dans chaque application ont été les endroits recommandés pour connecter les signaux, cependant, ils ne sont pas la meilleure solution, à mon avis, pour garder les signaux et les gestionnaires répartis. La distribution devrait être la raison pour laquelle les signaux et les gestionnaires ont été inventés dans django.

J'ai lutté pendant longtemps et nous avons finalement trouvé la solution.

créer un module de connecteur dans le dossier de l'application

donc nous avons:

app/
    __init__.py
    signals.py
    models.py
    connectors.py

dans app / connecteurs.py, nous avons défini des gestionnaires de signaux et les avons connectés. Un exemple est fourni:

from signals import example_signal
from models import ExampleModel
from django.db.models.signals import post_save, post_delete

def hanndler(sender, *args, **kwargs):
    pass

post_save.connect(hander, sender=ExampleModel)

puis dans models.py, nous ajoutons la ligne suivante à la fin du fichier:

from app import connector

Tout est fait ici.

De cette façon, nous pouvons placer les signaux dans signaux.py et tous les gestionnaires dans connecteurs.py. Pas de gâchis dans les modèles et les signaux.

J'espère que cela fournit une autre solution.

Samuel
la source
1
Alors, que se passe-t-il dans signaux.py? D'après votre exemple, il ne s'agit que des signaux personnalisés. Habituellement, nous combinons simplement les signaux et les connecteurs car la plupart n'auront pas de signaux personnalisés.
dalore le
@dalore oui, tous les signaux personnalisés sont placés dans signaux.py. Nous avons de nombreux signaux personnalisés. Mais si vous n'en avez pas beaucoup, ce fichier peut être omis.
samuel
même question que @dal
olleh
1
notez que tout cela est maintenant un vieux conseil, la méthode django consiste maintenant à utiliser appconfig pour importer des gestionnaires là où vivent les gestionnaires de signaux. Et dans signaux.py, allez aux signaux personnalisés
dalore
3

Je les garde dans un fichier séparé signals.py, models.pyaprès que tous les modèles soient définis. Je les importe et je connecte des modèles à des signaux.

signaux.py

#  necessary imports

def send_mail_on_save(<args>):
    # code here 

models.py

# imports
class mymodel(models.Model):
    # model here

# import signals
from signals import send_mail_on_save
# connect them 
post_save.connect(send_mail_on_save,sender=mymodel)

Cela me fournit une séparation logique, bien sûr, il n'y a rien de mal à les conserver dans models.py , mais c'est plus gérable de cette façon.

J'espère que cela t'aides!!

allsyed
la source
vous mettez des gestionnaires de signaux dans "signaux.py", et si nous l'appelons "handlers.py"
Abdul Fatah
1
Peu importe si vous nommez le fichier signaux.py ou handler.py. C'est juste une convention et non une règle.
allsyed
3

Petit rappel à propos de AppConfig. N'oubliez pas de définir:

# yourapp/__init__.py

default_app_config = 'yourapp.apps.RockNRollConfig'
Valex
la source