El lugar correcto para guardar mi archivo signal.py en un proyecto de Django

88

Según la documentación de Django que estaba leyendo, parece que signals.pyla carpeta de la aplicación es un buen lugar para comenzar, pero el problema al que me enfrento es que cuando creo señales para pre_savee intento importar la clase desde el modelo, entra en conflicto con la importen mi modelo.

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

Este código no se ejecutará porque importo Comm_Queuedentro signals.pyy también importo las señales dentro models.py.

¿Alguien puede darme un consejo sobre cómo podría superar este problema?

Saludos

Mo J. Mughrabi
fuente

Respuestas:

65

Respuesta original, para Django <1.7:

Puede registrar las señales importando signals.pyen el __init__.pyarchivo de la aplicación :

# __init__.py
import signals

Esto permitirá importar models.pydesde signals.pysin errores de importación circulares.

Un problema con este enfoque es que estropea los resultados de la cobertura si está utilizando el archivo cover.py.

Discusión relacionada

Editar: para Django> = 1.7:

Desde que se introdujo AppConfig, la forma recomendada de importar señales es en su init()función. Consulte la respuesta de Eric Marcos para obtener más detalles.

yprez
fuente
6
usando señales en Django 1.9, use el método siguiente (recomendado por django). este método no funciona dandoAppRegistryNotReady("Apps aren't loaded yet.")
s0nskar
1
Eric Marcos, su respuesta debería ser la respuesta aceptada: stackoverflow.com/a/21612050/3202958 desde Django> = 1.7, usando la configuración de la aplicación
Nrzonline
1
Convenido. Editaré la respuesta para señalar la respuesta de Eric Marcos para Django 1.7+
yprez
195

Si está utilizando Django <= 1.6, recomendaría la solución Kamagatos: simplemente importe sus señales al final de su módulo de modelos.

Para futuras versiones de Django (> = 1.7), la forma recomendada es importar su módulo de señales en la función config ready () de su aplicación :

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
fuente
7
También mencionan en la documentación 1.7 que a veces se puede llamar a ready varias veces y, por lo tanto, para evitar señales duplicadas, adjunte un identificador único a su llamada de conector de señal: request_finished.connect (my_callback, dispatch_uid = "my_unique_identifier") Donde dispatch_uid suele ser una cadena pero puede ser cualquier objeto hash. docs.djangoproject.com/en/1.7/topics/signals/…
Emeka
13
¡Esta debería ser la respuesta aceptada! La respuesta aceptada anterior arroja un error al implementar con uwsgi
Patrick
2
Hm, no me funciona con django 2. Si importo el modelo directamente en ready, todo bien. Si importo el modelo en señales y las señales de importación en listo, recibo un error doesn't declare an explicit app_label..
Aldarund
@Aldarun puedes intentar poner 'my_app.apps.MyAppConfig' dentro de INSTALLED_APPS.
Ramil Aglyautdinov
26

Para resolver su problema, solo tiene que importar señales.py después de la definición de su modelo. Eso es todo.

Kamagatos
fuente
2
Este es, con mucho, el más fácil, y no tenía idea de que esto funcionaría sin una dependencia cíclica. ¡Gracias!
bradenm
2
Brillante. Como este mejor que mi respuesta. A pesar de que realmente no entiendo cómo es que no causa una importación circular ...
yprez
La solución no funciona con el complemento autopep8 habilitado en Eclipse.
ramusus
5

También puse señales en el archivo señales.py y también tengo este fragmento de código que carga todas las señales:

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

Esto es para el proyecto, no estoy seguro de si funciona a nivel de aplicación.

Aisbaa
fuente
Esta es mi solución favorita en la medida en que se ajusta a los otros patrones (como tasks.py)
dalore
1
Encontré un problema con este, si inicia el shell, urls.py no se importa y sus señales no se adjuntan
dalore
sí, mi respuesta está algo desactualizada, parece que django tiene la clase AppConfig en estos días. La última vez que usé django fue la versión 1.3. Sugiere investigar a su alrededor.
Aisbaa
1
todavía somos 1.6, así que tuve que mover todas nuestras señales.py a modelos, de lo contrario, los comandos de administración y apio no se captaron
dalore
5

En versiones antiguas de Django estaría bien poner las señales en __init__.pyo tal vez en models.py(aunque al final los modelos serán demasiado grandes para mi gusto).

Con Django 1.9, creo que es mejor colocar las señales en un signals.pyarchivo e importarlas con el apps.py, donde se cargarán después de cargar el modelo.

apps.py:

from django.apps import AppConfig


class PollsConfig(AppConfig):
    name = 'polls'

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

También puede dividir sus señales de signals.pyy handlers.pyen otra carpeta dentro de su modelo llamado signalsasí, pero para mí eso es un poco más de la ingeniería. Eche un vistazo a Colocación de señales

Tyson Rodez
fuente
3

Supongo que lo está haciendo para que sus señales estén registradas, de modo que se encuentren en alguna parte. Solo pongo mis señales en un archivo models.py normalmente.

Issac Kelly
fuente
sí, cuando muevo la señal dentro del archivo del modelo, resuelve el problema. Pero mi archivo model.py es bastante grande con todas las clases, administradores y reglas del modelo.
Mo J. Mughrabi
1
Los gerentes son un poco más fáciles de sacar en mi experiencia. Managers.py ftw.
Issac Kelly
3

Esto solo se aplica si tiene sus señales en un signals.pyarchivo separado

Estoy completamente de acuerdo con la respuesta de @EricMarcos, pero debe indicarse que los documentos de django recomiendan explícitamente no usar la variable default_app_config (aunque no está mal). Para las versiones actuales, la forma correcta sería:

my_app / apps.py

from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'my_app'

    def ready(self):
        import my_app.signals

settings.py

(Asegúrese de no tener solo el nombre de su aplicación en las aplicaciones instaladas, sino la ruta relativa a su AppConfig)

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

Una alternativa es importar las funciones de devolución de llamada signals.pyy conectarlas en models.py:

señales.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: La importación YourModelen signals.pycrearán una recursividad; utilizar sender, en su lugar.

Ps2: Guardar la instancia nuevamente en la función de devolución de llamada creará una recursividad. Puede hacer un argumento de control en el .savemétodo para controlarlo.

Rafael
fuente