¿Dónde deberían vivir los manejadores de señales en un proyecto django?

143

Acabo de comenzar a implementar oyentes de señal en un proyecto de django. Si bien entiendo qué son y cómo usarlos. Me está costando mucho averiguar dónde debo ponerlos. La documentación del sitio django tiene esto que decir:

¿Dónde debería vivir este código?

Puede poner el código de registro y manejo de señal en cualquier lugar que desee. Sin embargo, deberá asegurarse de que el módulo en el que se encuentra se importe desde el principio para que el manejo de la señal se registre antes de que se deba enviar cualquier señal. Esto hace que la aplicación models.py sea un buen lugar para colocar el registro de los manejadores de señal.

Si bien es una buena sugerencia, tener clases o métodos que no sean modelos en mis models.py simplemente me molesta.

Entonces, ¿cuál es la mejor práctica / regla para almacenar y registrar manejadores de señales?

Jason Webb
fuente

Respuestas:

41

De hecho, me gusta hacerlos métodos de clase del modelo en sí. Eso mantiene todo dentro de una clase y significa que no tiene que preocuparse por importar nada.

Daniel Roseman
fuente
2
¿Y dónde sueles conectar controladores a las señales?
DataGreed
1
@DataGreed: en la parte inferior de los modelos relevantes.py.
Daniel Roseman
102
Si estás escuchando las señales emitidas por ese modelo, poner a todos los oyentes allí también hace que todo el ejercicio no tenga sentido, ¿no? El punto de las señales es desacoplar. ¿No deberían los oyentes vivir con el código que está interesado en estos eventos remotos? La pregunta es cómo asegurarse de que los oyentes estén cargados antes que los emisores.
John Mee
En mi caso quiero escuchar una señal de modelo de la Fooque forma parte fooapp. Pero el receptor de señal es una extensión y vive en una aplicación diferente (por ejemplo otherapp).
guettli
2
Para el punto de John Mee, no es muy diferente a anular save (), etc.
Matt
246

Esto se agregó a la documentación cuando se lanzó Django 1.7 :

Estrictamente hablando, el código de registro y manejo de señal puede vivir en cualquier lugar que desee, aunque se recomienda evitar el módulo raíz de la aplicación y su módulo de modelos para minimizar los efectos secundarios de la importación de código.

En la práctica, los manejadores de señales generalmente se definen en un submódulo de señales de la aplicación con la que se relacionan. Los receptores de señal se conectan en el método ready () de la clase de configuración de su aplicación. Si está utilizando el decorador de receptor (), simplemente importe el submódulo de señales dentro de ready ().

Cambiado en Django 1.7: Dado que ready () no existía en versiones anteriores de Django, el registro de la señal generalmente ocurría en el módulo de modelos.

La mejor práctica es definir sus controladores en handlers.py en un submódulo de señales, por ejemplo, un archivo que se vea así:

yourapp / señales / 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

El mejor lugar para registrar su manejador de señal es en el AppConfig de la aplicación que lo define, utilizando el método ready () . Esto se verá así:

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

Asegúrese de que está cargando su AppConfig especificándolo directamente en INSTALLED_APPS de settings.py, o en el __init__de su aplicación. Consulte la documentación de ready () para obtener más información.

Nota: Si también proporciona señales para que otras aplicaciones también escuchen, colóquelas en el __init__módulo de señales, por ejemplo, un archivo que se vea así:

yourapp / señales / __ init__.py

import django.dispatch

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

Luego, otra aplicación puede escuchar su señal importándola y registrándola, por ejemplo from yourapp.signals import task_generate_pre_save. Separar sus señales de sus manejadores mantiene las cosas limpias.

Instrucciones para Django 1.6:

Si todavía está atascado en Django 1.6 o inferior, entonces haría lo mismo (defina sus controladores en yourapp / signal / handlers.py) pero en lugar de usar AppConfig, cargaría los controladores a través de __init__.py de su aplicación, por ejemplo, algo como:

yourapp / __ init__.py

import signals

Esto no es tan bueno como usar el método ready () porque a menudo causa problemas de importación circular.

Aidan
fuente
3
como dice el documento que anula listo, es posible que desee hacer algo como super (ReportsConfig, self) .ready () en caso de que django decida poblar ready () con algo (a partir de 1.7.0 está actualmente vacío)
w- -
3
Creo que esta respuesta es la mejor porque es la única que aborda los efectos secundarios de las importaciones. Vine aquí buscando las mejores prácticas, porque estoy limpiando una aplicación, que está rota exactamente debido a este tipo de efectos secundarios. Por desgracia, la aplicación se ejecuta en django 1.6, y las mejores prácticas solo funcionan en django 1.7. La solución temporal de permitir que las __init__señales de importación no funcionen para mí, por lo que me pregunto si hay otro lugar desde el que pueda importar señales hasta que estemos listos para actualizar a una versión posterior de Django.
kasperd
¿No debería haber from . import handlers(o similar) en yourapp/signals/__init__.py?
dhobbs
¿No debería importar también el módulo handlers.py en alguna parte? Estoy intentando esto y no parece estar definiendo el controlador para la señal.
Andrés
1
fwiw No necesitaba el yourproject.en la última línea del bloque de código de clase TaskConfig. Tengo esto trabajando exactamente con esta estructura, así que considere esto qa :)
Greg Kaleka
40

Acabo de encontrar esto, y como mis señales no están relacionadas con el modelo, pensé en agregar mi solución.

Estoy registrando varios datos alrededor de iniciar / cerrar sesión, y necesitaba conectarme django.contrib.auth.signals.

Puse los manejadores de señales en un signals.pyarchivo y luego importé las señales del __init__.pyarchivo del módulo, ya que creo que esto se llama tan pronto como se inicia la aplicación (las pruebas con una printdeclaración sugieren que se llama incluso antes de que se lea el archivo de configuración).

# /project/__init__.py
import signals

y en señales.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)

Soy bastante nuevo en Django (/ python), ¡así que estoy abierto a cualquiera que me diga que es una idea terrible!

Hugo Rodger-Brown
fuente
3
Esto parece lógico, pero sugeriría hacerlo a nivel de la aplicación.
Nils
2
Cuidado, esta lógica probablemente dará como resultado señales duplicadas que se disparen. user_logged_in.connect(on_logged_in)Lo más probable es que pase el dispatch_uidargumento. Más en docs.djangoproject.com/en/dev/topics/signals/… .
Scott Coates
Gracias por eso, es bueno saberlo. Estoy registrando todos los inicios de sesión con este método (registro de IP / agente de usuario), y hasta ahora no he tenido duplicados, ¡aunque eso no significa que un pequeño cambio en la línea no cause un problema!
Hugo Rodger-Brown
13

Hace poco leí este artículo sobre las mejores prácticas cuando se trata de diseñar sus proyectos / aplicaciones, y sugiere que todas sus señales de despachador personalizadas deben ir en un archivo llamado signals.py. Sin embargo, eso no resuelve completamente su problema, ya que aún necesita importarlos en algún lugar, y cuanto antes se importen, mejor.

La sugerencia del modelo es buena. Como ya ha definido todo en su signals.pyarchivo, no debería tomar más de una línea en la parte superior del archivo. Esto es similar a la forma en admin.pyque se presenta el archivo (con las definiciones de clase en la parte superior y el código para registrar todas las clases de administración personalizadas en la parte inferior), si define sus señales, conéctelas en el mismo archivo.

¡Espero que ayude! En definitiva, se reduce a lo que prefieres.

hora
fuente
1
También quería poner mis manejadores de señal en un signals.pyarchivo, pero no sabía cómo debería llamarse después. Al importarlo en mi models.pyarchivo, obtuve una solución muy limpia, sin "contaminar" mi archivo models.py. ¡Gracias! :)
Danilo Bargen
10
hay una importación cruzada allí: signal.py intenta importar el modelo de models.py
Ivan Virabyan
8

Los modelos.py y signal.py en cada aplicación han sido los lugares recomendados para conectar señales, sin embargo, no son la mejor solución, en mi opinión, para mantener las señales y los controladores enviados. El despacho debe ser la razón por la cual las señales y los manejadores inventados en django.

Estuve luchando por mucho tiempo, y finalmente descubrimos la solución.

crear un módulo conector en la carpeta de la aplicación

entonces tenemos:

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

en app / Connectors.py, definimos manejadores de señal y los conectamos. Se proporciona un ejemplo:

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)

luego, en models.py, agregamos la siguiente línea al final del archivo:

from app import connector

Todo hecho aquí.

De esta manera, podemos poner señales en signal.py, y todos los controladores en connector.py. Sin desorden en modelos y señales.

Espero que brinde otra solución.

Samuel
fuente
1
Entonces, ¿qué pasa en signal.py? Parece que de su ejemplo son solo las señales personalizadas. Por lo general, solo combinamos las señales y los conectores, ya que la mayoría no tendrá señales personalizadas.
reto
@dalore sí, todas las señales personalizadas se colocan en signal.py. Tenemos muchas señales personalizadas. Pero si no tiene muchos, este archivo podría omitirse.
samuel
misma pregunta que @dal
olleh
1
tenga en cuenta que todo esto ahora es un viejo consejo, la forma de django ahora es usar appconfig para importar controladores donde viven los controladores de señal. Y en signal.py go señales personalizadas
dalore
3

Los guardo en un archivo separado signals.py, en models.pydespués de que todos los modelos estén definidos. Los importo y conecto modelos a señales.

señales.py

#  necessary imports

def send_mail_on_save(<args>):
    # code here 

modelos.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)

Esto me proporciona una separación lógica, por supuesto, no hay nada de malo en mantenerlos en models.py , pero es más manejable de esta manera.

¡¡Espero que esto ayude!!

allsyed
fuente
está poniendo manejadores de señales en "signal.py", ¿qué pasa si lo nombramos como "handlers.py"
Abdul Fatah
1
No importa si nombra el archivo como signal.py o handler.py. Es solo una convención, no una regla.
allsyed
3

Pequeño recordatorio sobre AppConfig. No olvides configurar:

# yourapp/__init__.py

default_app_config = 'yourapp.apps.RockNRollConfig'
valex
fuente