Le bon endroit pour conserver mon fichier signaux.py dans un projet Django

88

D'après la documentation de Django que je lisais, il semble que signals.pyle dossier de l'application soit un bon point de départ, mais le problème auquel je suis confronté est que lorsque je crée des signaux pour pre_saveet que j'essaye d'importer la classe à partir du modèle, il entre en conflit avec le importdans mon modèle.

# models.py

from django.contrib.auth.models import User
from django.db import models
from django.utils.translation import gettext as _
from signals import *

class Comm_Queue(CommunicatorAbstract):
    queue_statuses = (
        ('P', _('Pending')),
        ('S', _('Sent')),
        ('E', _('Error')),
        ('R', _('Rejected')),
    )
    status          = models.CharField(max_length=10, db_index=True, default='P')
    is_html         = models.BooleanField(default=False)
    language        = models.CharField(max_length=6, choices=settings.LANGUAGES)
    sender_email    = models.EmailField()
    recipient_email = models.EmailField()
    subject         = models.CharField(max_length=100)
    content         = models.TextField()

# signals.py

from django.conf import settings
from django.db.models.signals import pre_save
from django.dispatch import receiver
from models import Comm_Queue

@receiver(pre_save, sender=Comm_Queue)
def get_sender_email_from_settings(sender, **kwargs):
    obj=kwargs['instance']
    if not obj.sender_email:
        obj.sender_email='%s' % settings.ADMINS[0][1]

Ce code ne fonctionnera pas car j'importe à l' Comm_Queueintérieur signals.pyet j'importe également les signaux à l'intérieur models.py.

Quelqu'un peut-il me donner des conseils sur la façon dont je pourrais surmonter ce problème?

Cordialement

Mo J. Mughrabi
la source

Réponses:

65

Réponse originale, pour Django <1.7:

Vous pouvez enregistrer les signaux en les important signals.pydans le __init__.pyfichier de l'application :

# __init__.py
import signals

Cela permettra d'importer models.pydepuis signals.pysans erreurs d'importation circulaires.

Un problème avec cette approche est qu'elle gâche les résultats de la couverture si vous utilisez coverage.py.

Discussion connexe

Edit: Pour Django> = 1.7:

Depuis l'introduction d'AppConfig, la méthode recommandée pour importer des signaux est dans sa init()fonction. Voir la réponse d'Eric Marcos pour plus de détails.

yprez
la source
6
en utilisant des signaux dans Django 1.9, utilisez la méthode ci-dessous (recommandée par django). cette méthode ne fonctionne pas en donnantAppRegistryNotReady("Apps aren't loaded yet.")
s0nskar
1
Eric Marcos sa réponse devrait être la réponse acceptée: stackoverflow.com/a/21612050/3202958 depuis Django> = 1.7, en utilisant la configuration de l'application
Nrzonline
1
D'accord. Je vais modifier la réponse pour indiquer la réponse d'Eric Marcos pour Django 1.7+
yprez
194

Si vous utilisez Django <= 1.6, je vous recommande la solution Kamagatos: importez simplement vos signaux à la fin de votre module de modèles.

Pour les futures versions de Django (> = 1.7), la méthode recommandée est d'importer votre module de signaux dans la fonction config ready () de votre application :

my_app/apps.py

from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'my_app'

    def ready(self):
        import my_app.signals

my_app/__init__.py

default_app_config = 'my_app.apps.MyAppConfig'
Eric Marcos
la source
7
Ils mentionnent également dans la documentation 1.7 que parfois ready peut être appelé plusieurs fois et donc pour éviter les signaux en double, attachez un identifiant unique à votre appel de connecteur de signal: request_finished.connect (my_callback, dispatch_uid = "my_unique_identifier") Où dispatch_uid est généralement une chaîne mais peut être n'importe quel objet hachable. docs.djangoproject.com/en/1.7/topics/signals/…
Emeka
13
Cela devrait être la réponse acceptée! La réponse acceptée ci-dessus génère une erreur lors du déploiement à l'aide de uwsgi
Patrick
2
Hm, ne fonctionne pas pour moi avec django 2. Si j'importe le modèle directement prêt - tout va bien. Si j'importe le modèle dans les signaux et que doesn't declare an explicit app_label
j'importe
@Aldarun vous pouvez essayer de mettre 'my_app.apps.MyAppConfig' dans INSTALLED_APPS.
Ramil Aglyautdinov
26

Pour résoudre votre problème, il vous suffit d'importer signaux.py après la définition de votre modèle. C'est tout.

Kamagatos
la source
2
C'est de loin le plus simple et je n'avais aucune idée que cela fonctionnerait sans une dépendance cyclique. Merci!
bradenm
2
Brillant. Comme celui-ci mieux que ma réponse. Bien que je ne comprenne pas vraiment comment cela se fait, cela ne provoque pas d'importation circulaire ...
yprez
La solution ne fonctionne pas avec le plugin autopep8 activé dans Eclipse.
ramusus
5

J'ai également mis des signaux dans le fichier signaux.py et j'ai également cet extrait de code qui charge tous les signaux:

# import this in url.py file !

import logging

from importlib import import_module

from django.conf import settings

logger = logging.getLogger(__name__)

signal_modules = {}

for app in settings.INSTALLED_APPS:
    signals_module = '%s.signals' % app
    try:
        logger.debug('loading "%s" ..' % signals_module)
        signal_modules[app] = import_module(signals_module)
    except ImportError as e:
        logger.warning(
            'failed to import "%s", reason: %s' % (signals_module, str(e)))

Ceci est pour le projet, je ne suis pas sûr que cela fonctionne au niveau de l'application.

aisbaa
la source
C'est ma solution préférée dans la mesure où elle correspond aux autres modèles (comme tasks.py)
dalore
1
J'ai trouvé un problème avec celui-ci, si vous démarrez le shell, urls.py n'est pas importé et vos signaux ne seront pas
attachés
oui, ma réponse est un peu dépassée, il semble que django ait la classe AppConfig ces jours-ci. La dernière fois que j'ai utilisé django, c'était la version 1.3. Suggérant d'enquêter autour de lui.
aisbaa
1
nous sommes toujours 1,6 et j'ai donc dû déplacer tous nos signaux.py dans des modèles, sinon les commandes de céleri et de gestion n'ont pas été
prises
5

Dans les anciennes versions de Django, ce serait bien de mettre les signaux sur le __init__.pyou peut-être dans le models.py(bien qu'à la fin, les modèles seront bien trop gros à mon goût).

Avec Django 1.9, je pense qu'il vaut mieux placer les signaux sur un signals.pyfichier et les importer avec le apps.py, où ils vont être chargés après le chargement du modèle.

apps.py:

from django.apps import AppConfig


class PollsConfig(AppConfig):
    name = 'polls'

    def ready(self):
        from . import signals  # NOQA

Vous pouvez également diviser vos signaux sur signals.pyet handlers.pydans un autre dossier de votre modèle nommé signalségalement, mais pour moi, c'est juste au-dessus de l'ingénierie. Jetez un œil à Placer des signaux

Tyson Rodez
la source
3

Je suppose que vous faites cela pour que vos signaux soient enregistrés, afin qu'ils soient trouvés quelque part. Je viens de mettre mes signaux directement dans un fichier models.py normalement.

Issac Kelly
la source
ouais, lorsque je déplace le signal dans le fichier modèle, cela résout le problème. Mais mon fichier model.py est assez volumineux avec toutes les classes, les gestionnaires et les règles du modèle.
Mo J. Mughrabi
1
Les gestionnaires sont un peu plus faciles à tirer de mon expérience. Managers.py ftw.
Issac Kelly
3

Cela ne s'applique que si vous avez vos signaux dans un signals.pyfichier séparé

Tout à fait d'accord avec la réponse de @EricMarcos mais il faut préciser que la documentation django conseille explicitement de ne pas utiliser la variable default_app_config (bien que ce ne soit pas faux). Pour les versions actuelles, la manière correcte serait:

my_app / apps.py

from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'my_app'

    def ready(self):
        import my_app.signals

settings.py

(Assurez-vous que vous n'avez pas seulement le nom de votre application dans les applications installées, mais plutôt le chemin relatif à votre AppConfig)

INSTALLED_APPS = [
    'my_app.apps.MyAppConfig',
    # ...
]
Xen_mar
la source
1

Une alternative consiste à importer les fonctions de rappel depuis signals.pyet à les connecter dans models.py:

signaux.py

def pre_save_callback_function(sender, instance, **kwargs):
    # Do stuff here

model.py

# Your imports here
from django.db.models.signals import pre_save
from yourapp.signals import pre_save_callback_function

class YourModel:
    # Model stuff here
pre_save.connect(pre_save_callback_function, sender=YourModel)

Ps: l'importation YourModeldans signals.pycréera une récursivité; utilisez senderplutôt.

Ps2: la sauvegarde de l'instance à nouveau dans la fonction de rappel créera une récursivité. Vous pouvez créer un argument de contrôle dans .savemethod pour le contrôler.

Rafael
la source