Wo sollen Signalhandler in einem Django-Projekt leben?

143

Ich habe gerade angefangen, Signal-Listener in einem Django-Projekt zu implementieren. Während ich verstehe, was sie sind und wie man sie benutzt. Es fällt mir schwer herauszufinden, wo ich sie hinstellen soll. Die Dokumentation von der Django-Site enthält Folgendes:

Wo soll dieser Code leben?

Sie können den Signalverarbeitungs- und Registrierungscode an einer beliebigen Stelle eingeben. Sie müssen jedoch sicherstellen, dass das Modul, in dem es sich befindet, frühzeitig importiert wird, damit die Signalverarbeitung registriert wird, bevor Signale gesendet werden müssen. Dies macht die models.py Ihrer App zu einem guten Ort für die Registrierung von Signalhandlern.

Es ist zwar ein guter Vorschlag, aber nicht modellierte Klassen oder Methoden in meinen Modellen zu haben. Py reibt mich einfach in die falsche Richtung.

Was ist dann die beste Vorgehensweise / Regel zum Speichern und Registrieren von Signalhandlern?

Jason Webb
quelle

Antworten:

41

Eigentlich mag ich es, sie zu Klassenmethoden des Modells selbst zu machen. Das hält alles in einer Klasse und bedeutet, dass Sie sich keine Gedanken über den Import machen müssen.

Daniel Roseman
quelle
2
Und wo verbinden Sie normalerweise Handler mit den Signalen?
DataGreed
1
@DataGreed: am Ende der relevanten models.py.
Daniel Roseman
102
Wenn Sie die von diesem Modell ausgesendeten Signale hören, macht es die ganze Übung sinnlos, alle Zuhörer dort zu platzieren, nicht wahr? Der Punkt der Signale ist zu entkoppeln. Sollten die Listener nicht mit dem Code leben, der an diesen Remote-Ereignissen interessiert ist? Die Frage ist, wie sichergestellt werden kann, dass die Listener vor den Emittern geladen werden.
John Mee
In meinem Fall möchte ich ein Signal des Modells hören, Foodas Teil von ist fooapp. Der Signalempfänger ist jedoch eine Erweiterung und lebt (zum Beispiel otherapp) in einer anderen App .
Guettli
2
Für John Mee ist es nicht viel anders als nur save () usw. zu überschreiben
Matt
245

Dies wurde der Dokumentation hinzugefügt, als Django 1.7 veröffentlicht wurde:

Genau genommen können Signalverarbeitungs- und Registrierungscode überall verwendet werden. Es wird jedoch empfohlen, das Root-Modul der Anwendung und das Modellmodul zu vermeiden, um die Nebenwirkungen des Codeimports zu minimieren.

In der Praxis werden Signalhandler normalerweise in einem Signal-Submodul der Anwendung definiert, auf die sie sich beziehen. Signalempfänger werden in der ready () -Methode Ihrer Anwendungskonfigurationsklasse verbunden. Wenn Sie den Receiver () -Dekorator verwenden, importieren Sie einfach das Signal-Submodul in ready ().

In Django 1.7 geändert: Da ready () in früheren Versionen von Django nicht vorhanden war, erfolgte die Signalregistrierung normalerweise im Modellmodul.

Die beste Vorgehensweise besteht darin, Ihre Handler in handlers.py in einem Signal-Submodul zu definieren, z. B. in einer Datei, die wie folgt aussieht:

yourapp / signales / 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

Der beste Ort, um Ihren Signalhandler zu registrieren, ist dann in der AppConfig der App, die ihn definiert, mithilfe der ready () -Methode. Das wird so aussehen:

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

Stellen Sie sicher, dass Sie Ihre AppConfig laden, indem Sie sie entweder direkt in INSTALLED_APPS von settings.py oder in __init__Ihrer App angeben . Siehe die bereit () Dokumentation für weitere Informationen.

Hinweis: Wenn Sie Signale bereitstellen, die auch von anderen Apps abgehört werden sollen, fügen Sie diese in das __init__Signalmodul ein, z. B. eine Datei, die wie folgt aussieht:

Ihre App / Signale / __ init__.py

import django.dispatch

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

Eine andere App kann dann Ihr Signal abhören, indem Sie es importieren und registrieren, z from yourapp.signals import task_generate_pre_save. Wenn Sie Ihre Signale von Ihren Handlern trennen, bleiben die Dinge sauber.

Anleitung für Django 1.6:

Wenn Sie immer noch mit Django 1.6 oder niedriger arbeiten, tun Sie dasselbe (definieren Sie Ihre Handler in yourapp / signales / handlers.py), aber anstatt AppConfig zu verwenden, laden Sie die Handler über __init__.py von Ihre App, zB so etwas wie:

yourapp / __ init__.py

import signals

Dies ist nicht so schön wie die Verwendung der ready () -Methode, da dies häufig zu Problemen beim zirkulären Import führt.

Aidan
quelle
3
Da die Dokumentation besagt, dass Sie Ready überschreiben, möchten Sie möglicherweise etwas wie Super (ReportsConfig, self) .ready () tun, falls Django jemals beschließt, Ready () mit etwas zu füllen (ab 1.7.0 ist es derzeit leer)
w- -
3
Ich denke, diese Antwort ist die beste, weil sie die einzige ist, die die Nebenwirkungen von Importen angeht. Ich bin hierher gekommen, um nach Best Practices zu suchen, weil ich eine Anwendung bereinige, die genau aufgrund dieser Art von Nebenwirkungen defekt ist. Leider läuft die Anwendung auf Django 1.6 und Best Practices funktionieren nur auf Django 1.7. Die vorübergehende __init__Problemumgehung, Importsignale zuzulassen, würde für mich nicht funktionieren. Daher frage ich mich, ob es einen anderen Ort gibt, von dem ich Signale importieren kann, bis wir bereit sind, auf eine spätere Django-Version zu aktualisieren.
Kasperd
Sollte es nicht from . import handlers(oder ähnliches) geben yourapp/signals/__init__.py?
Dhobbs
Sollten Sie das Modul handlers.py nicht auch irgendwo importieren? Ich versuche dies und es scheint nicht den Handler für das Signal zu definieren.
Andrés
1
fwiw Ich brauchte den yourproject.in der letzten Zeile des TaskConfig-Klassencodeblocks nicht. Ich habe dies mit genau dieser Struktur arbeiten, also betrachten Sie diese qa :)
Greg Kaleka
40

Ich bin gerade erst darauf gestoßen, und da meine Signale nicht modellbezogen sind, dachte ich, ich würde meine Lösung hinzufügen.

Ich protokolliere verschiedene Daten rund um das Anmelden / Abmelden und musste mich einbinden django.contrib.auth.signals.

Ich habe die Signalhandler in eine signals.pyDatei eingefügt und dann Signale aus der __init__.pyModuldatei importiert , da ich glaube, dass dies aufgerufen wird, sobald die App startet (Tests mit einer printAnweisung legen nahe, dass sie aufgerufen wird, noch bevor die Einstellungsdatei gelesen wird.)

# /project/__init__.py
import signals

und in signals.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)

Ich bin ziemlich neu in Django (/ python), also bin ich offen für jeden, der mir sagt, dass dies eine schreckliche Idee ist!

Hugo Rodger-Brown
quelle
3
Das fühlt sich logisch an, aber ich würde vorschlagen, es auf App-Ebene zu tun.
Nils
2
Vorsicht, diese Logik führt höchstwahrscheinlich dazu, dass doppelte Signale ausgelöst werden. user_logged_in.connect(on_logged_in)sollte höchstwahrscheinlich in dem dispatch_uidArgument übergeben werden. Mehr unter docs.djangoproject.com/de/dev/topics/signals/… .
Scott Coates
Danke dafür - gut zu wissen. Ich protokolliere alle Anmeldungen mit dieser Methode (IP- / Benutzeragent aufzeichnen) und hatte bisher keine Duplikate - obwohl dies nicht bedeutet, dass eine kleine Änderung auf der ganzen Linie kein Problem verursacht!
Hugo Rodger-Brown
13

Ich habe kürzlich diesen Artikel über Best Practices für das Layout Ihrer Projekte / Anwendungen gelesen und er schlägt vor, dass alle Ihre benutzerdefinierten Dispatcher-Signale in einer Datei namens gespeichert werden sollten signals.py. Dies löst Ihr Problem jedoch nicht vollständig, da Sie diese noch irgendwo importieren müssen. Je früher sie importiert werden, desto besser.

Der Modellvorschlag ist gut. Da Sie bereits alles in Ihrer signals.pyDatei definiert haben, sollte es nicht mehr als eine Zeile oben in der Datei dauern. Dies ähnelt der Anordnung der admin.pyDatei (mit Klassendefinitionen oben und dem Code zum Registrieren aller benutzerdefinierten Administratorklassen unten). Wenn Sie Ihre Signale definieren, verbinden Sie sie in derselben Datei.

Hoffentlich hilft das! Letztendlich kommt es darauf an, was Sie bevorzugen.

hora
quelle
1
Ich wollte auch meine Signalhandler in eine signals.pyDatei einfügen , wusste aber nicht, wie sie danach aufgerufen werden sollte. Durch den Import in meine models.pyDatei erhielt ich eine sehr saubere Lösung, ohne meine Datei models.py zu "verschmutzen". Danke dir! :)
Danilo Bargen
10
Dort gibt es einen Cross-Import: Signals.py versucht, ein Modell aus Models.py zu importieren
Ivan Virabyan
8

Modelle.py und Signale.py in jeder App waren die empfohlenen Orte zum Verbinden von Signalen. Sie sind jedoch meiner Meinung nach nicht die beste Lösung, um Signale und Handler weiterzuleiten. Dispatching sollte der Grund sein, warum Signale und Handler in Django erfunden wurden.

Ich hatte lange Probleme, und schließlich fanden wir die Lösung heraus.

Erstellen Sie ein Connector-Modul im App-Ordner

also haben wir:

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

In app / connectors.py haben wir Signalhandler definiert und verbunden. Ein Beispiel wird bereitgestellt:

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)

Dann fügen wir in models.py die folgende Zeile am Ende der Datei hinzu:

from app import connector

Alles hier gemacht.

Auf diese Weise können wir Signale in signal.py und alle Handler in connectors.py einfügen. Kein Durcheinander bei Modellen und Signalen.

Hoffe, es bietet eine andere Lösung.

Samuel
quelle
1
Also, was geht in signals.py? In Ihrem Beispiel sind es nur die benutzerdefinierten Signale. Normalerweise kombinieren wir nur die Signale und Anschlüsse, da die meisten keine benutzerdefinierten Signale haben.
Bangalore
@dalore ja, alle benutzerdefinierten Signale werden in signal.py abgelegt. Wir haben viele kundenspezifische Signale. Wenn Sie jedoch nicht viele haben, kann diese Datei weggelassen werden.
Samuel
gleiche Frage wie @dal
olleh
1
Beachten Sie, dass all dies jetzt ein alter Rat ist. Der Django-Weg besteht jetzt darin, appconfig zu verwenden, um Handler zu importieren, in denen die Signalhandler leben. Und in signals.py gehen benutzerdefinierte Signale
dalore
3

Ich bewahre sie in einer separaten Datei auf signals.py, models.pynachdem alle Modelle definiert wurden. Ich importiere sie und verbinde Modelle mit Signalen.

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

Dies bietet mir eine logische Trennung, natürlich ist nichts falsch daran, sie in Modellen zu behalten. Py , aber es ist auf diese Weise leichter zu handhaben .

Hoffe das hilft!!

allsyed
quelle
Sie setzen Signalhandler in "signal.py", was ist, wenn wir es als "handlers.py" bezeichnen
Abdul Fatah
1
Es spielt keine Rolle, ob Sie die Datei als signal.py oder handler.py benennen. Es ist nur eine Konvention, keine Regel.
allsyed
3

Kleine Erinnerung an AppConfig. Vergessen Sie nicht einzustellen:

# yourapp/__init__.py

default_app_config = 'yourapp.apps.RockNRollConfig'
Valex
quelle