Il posto giusto per conservare il mio file signal.py in un progetto Django


88

Sulla base della documentazione di Django che stavo leggendo, sembra che signals.pynella cartella dell'app sia un buon punto di partenza, ma il problema che sto affrontando è che quando creo segnali per pre_savee provo a importare la classe dal modello, è in conflitto con il importnel mio modello.

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

Questo codice non verrà eseguito perché importa Comm_Queueall'interno signals.pye importa anche i segnali all'interno models.py.

Qualcuno può consigliarmi su come ho potuto superare questo problema?

Saluti


Risposte:


65

Risposta originale, per Django <1.7:

Puoi registrare i segnali importando signals.pynel __init__.pyfile dell'app :

# __init__.py
import signals

Ciò consentirà di importare models.pyda signals.pysenza errori di importazione circolare.

Un problema con questo approccio è che incasina i risultati di copertura se utilizzi coverage.py.

Discussione correlata

Modifica: per Django> = 1.7:

Da quando è stato introdotto AppConfig, il modo consigliato di importare i segnali è nella sua init()funzione. Vedi la risposta di Eric Marcos per maggiori dettagli.


6
usando i segnali in Django 1.9, usa il seguente metodo (consigliato da django). questo metodo non funziona dandoAppRegistryNotReady("Apps aren't loaded yet.")
s0nskar

1
Eric Marcos la sua risposta dovrebbe essere la risposta accettata: stackoverflow.com/a/21612050/3202958 da Django> = 1.7, utilizzando la configurazione dell'app
Nrzonline

1
Concordato. Modificherò la risposta per indicare la risposta di Eric Marcos per Django 1.7+
yprez

194

Se stai usando Django <= 1.6 ti consiglio la soluzione Kamagatos: importa semplicemente i tuoi segnali alla fine del modulo dei tuoi modelli.

Per le versioni future di Django (> = 1.7), il modo consigliato è importare il modulo dei segnali nella funzione config ready () della tua app :

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'

7
Nella documentazione 1.7 menzionano anche che a volte ready può essere chiamato più volte e quindi per evitare segnali duplicati, allegare un identificatore univoco alla chiamata del connettore del segnale: request_finished.connect (my_callback, dispatch_uid = "my_unique_identifier") Dove dispatch_uid è solitamente una stringa ma può essere qualsiasi oggetto hashable. docs.djangoproject.com/en/1.7/topics/signals/…
Emeka

13
Questa dovrebbe essere la risposta accettata! La risposta accettata sopra genera un errore durante la distribuzione utilizzando uwsgi
Patrick

2
Hm, non funziona per me con django 2. Se importa il modello direttamente in ready, tutto bene. Se doesn't declare an explicit app_label
importi il

@ Aldarun puoi provare a mettere "my_app.apps.MyAppConfig" all'interno di INSTALLED_APPS.
Ramil Aglyautdinov

26

Per risolvere il tuo problema devi solo importare signal.py dopo la definizione del tuo modello. È tutto.


2
Questo è di gran lunga il più semplice e non avevo idea che avrebbe funzionato senza una dipendenza ciclica. Grazie!
bradenm

2
Brillante. Come questo meglio della mia risposta. Anche se non capisco davvero come mai non provochi un'importazione circolare ...
yprez

la soluzione non funziona con il plug-in autopep8 abilitato in Eclipse.
ramusus

5

Ho anche inserito i segnali nel file signal.py e ho anche questo frammento di codice che carica tutti i segnali:

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

Questo è per il progetto, non sono sicuro che funzioni a livello di app.


Questa è la mia soluzione preferita per quanto si adatta agli altri schemi (come tasks.py)
dalore

1
Trovato un problema con questo, se avvii la shell, urls.py non viene importato e i tuoi segnali non si collegano
dalore

sì, la mia risposta è un po 'obsoleta, sembra che django abbia la classe AppConfig in questi giorni. L'ultima volta che ho usato django era la versione 1.3. Suggerendo di indagare intorno ad esso.
aisbaa

1
siamo ancora 1.6 e quindi ho dovuto spostare tutti i nostri signal.py nei modelli altrimenti i comandi di sedano e di gestione non
venivano

5

Nelle vecchie versioni di Django andrebbe bene mettere i segnali su __init__.pyo forse su models.py(anche se alla fine i modelli saranno troppo grandi per i miei gusti).

Con Django 1.9, credo sia meglio posizionare i segnali su un signals.pyfile e importarli con il apps.py, dove verranno caricati dopo aver caricato il modello.

apps.py:

from django.apps import AppConfig


class PollsConfig(AppConfig):
    name = 'polls'

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

Puoi anche dividere i tuoi segnali su signals.pye handlers.pyin un'altra cartella all'interno del tuo modello con il nome signals, ma per me è solo un'ingegneria. Dai un'occhiata a Posizionamento dei segnali


3

Immagino che tu lo stia facendo in modo che i tuoi segnali siano registrati, in modo che vengano trovati da qualche parte. Normalmente metto i miei segnali in un file models.py.


si quando sposto il segnale all'interno del file modello risolvo il problema. Ma il mio file model.py è piuttosto grande con tutte le classi, i gestori e le regole del modello.
Mo J. Mughrabi

1
I manager sono un po 'più facili da tirare fuori secondo la mia esperienza. Managers.py ftw.
Issac Kelly

3

Questo vale solo se hai i tuoi segnali in un signals.pyfile separato

Sono completamente d'accordo con la risposta di @EricMarcos ma va detto che la documentazione di django consiglia esplicitamente di non utilizzare la variabile default_app_config (anche se non è sbagliata). Per le versioni attuali, il modo corretto sarebbe:

my_app / apps.py

from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'my_app'

    def ready(self):
        import my_app.signals

settings.py

(Assicurati di non avere solo il nome della tua app nelle app installate, ma invece il percorso relativo alla tua AppConfig)

INSTALLED_APPS = [
    'my_app.apps.MyAppConfig',
    # ...
]

1

Un'alternativa è importare le funzioni di callback da signals.pye collegarle in models.py:

signal.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'importazione YourModelin signals.pycreerà una ricorsione; usa sender, invece.

Ps2: il nuovo salvataggio dell'istanza nella funzione di callback creerà una ricorsione. Puoi creare un argomento di controllo nel .savemetodo per controllarlo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.