Der richtige Ort, um meine Datei files.py in einem Django-Projekt aufzubewahren

88

Basierend auf der Dokumentation von Django, die ich gelesen habe, scheint es, als wäre signals.pyder App-Ordner ein guter Anfang, aber das Problem, mit dem ich konfrontiert bin, ist, dass wenn ich Signale für erstelle pre_saveund versuche, die Klasse aus dem Modell zu importieren, dies mit dem in Konflikt steht importin meinem Modell.

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

Dieser Code wird nicht ausgeführt, da ich Comm_Queueinnen importiere signals.pyund auch die Signale darin importiere models.py.

Kann mir jemand einen Rat geben, wie ich dieses Problem lösen kann?

Grüße

Mo J. Mughrabi
quelle

Antworten:

65

Ursprüngliche Antwort für Django <1,7:

Sie können die Signale registrieren, indem Sie sie signals.pyin die App- __init__.pyDatei importieren :

# __init__.py
import signals

Dies ermöglicht den Import models.pyvon signals.pyohne Kreisimportfehler.

Ein Problem bei diesem Ansatz besteht darin, dass die Abdeckungsergebnisse durcheinander gebracht werden, wenn Sie Coverage.py verwenden.

Verwandte Diskussion

Edit: Für Django> = 1.7:

Seit der Einführung von AppConfig liegt die empfohlene Methode zum Importieren von Signalen in ihrer init()Funktion. Siehe Eric Marcos' Antwort für weitere Details.

yprez
quelle
6
Verwenden Sie die Signale in Django 1.9 (von Django empfohlen). Diese Methode funktioniert nicht gebenAppRegistryNotReady("Apps aren't loaded yet.")
s0nskar
1
Eric Marcos seine Antwort sollte die akzeptierte Antwort sein: stackoverflow.com/a/21612050/3202958 da Django> = 1.7, mit der App config
Nrzonline
1
Einverstanden. Ich werde die Antwort bearbeiten, um auf Eric Marcos 'Antwort für Django 1.7+
yprez
193

Wenn Sie Django <= 1.6 verwenden, würde ich die Kamagatos-Lösung empfehlen: Importieren Sie einfach Ihre Signale am Ende Ihres Modellmoduls.

Für zukünftige Versionen von Django (> = 1.7) wird empfohlen , Ihr Signalmodul in die config ready () -Funktion Ihrer App zu importieren :

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
quelle
7
In der Dokumentation zu 1.7 wird auch erwähnt, dass ready manchmal mehrmals aufgerufen werden kann. Um doppelte Signale zu vermeiden, fügen Sie Ihrem Signalverbindungsaufruf eine eindeutige Kennung hinzu: request_finished.connect (my_callback, dispatch_uid = "my_unique_identifier") Wobei dispatch_uid normalerweise eine Zeichenfolge ist kann aber jedes hashbare Objekt sein. docs.djangoproject.com/de/1.7/topics/signals/…
Emeka
13
Dies sollte die akzeptierte Antwort sein! Die oben akzeptierte Antwort wirft einen Fehler bei der Bereitstellung mit uwsgi
Patrick
2
Hm, funktioniert bei mir nicht mit Django 2. Wenn ich das Modell direkt fertig importiere, ist alles in Ordnung. Wenn ich Modell in Signale importiere und Signale in fertig importiere, doesn't declare an explicit app_label
erhalte
@Aldarun Sie können versuchen, 'my_app.apps.MyAppConfig' in INSTALLED_APPS einzufügen.
Ramil Aglyautdinov
26

Um Ihr Problem zu lösen, müssen Sie nach Ihrer Modelldefinition nur signal.py importieren. Das ist alles.

Kamagatos
quelle
2
Dies ist bei weitem am einfachsten, und ich hatte keine Ahnung, dass dies ohne eine zyklische Abhängigkeit funktionieren würde. Vielen Dank!
Bradenm
2
Brillant. Wie dieser besser als meine Antwort. Obwohl ich nicht wirklich verstehe,
warum
Die Lösung funktioniert nicht mit dem aktivierten Autopep8-Plugin in Eclipse.
Ramusus
5

Ich habe auch Signale in die Datei signals.py eingefügt und habe auch dieses Code-Snippet, das alle Signale lädt:

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

Dies ist für ein Projekt, ich bin mir nicht sicher, ob es auf App-Ebene funktioniert.

aisbaa
quelle
Dies ist meine Lieblingslösung, soweit sie zu den anderen Mustern passt (wie task.py)
dalore
1
Fand ein Problem mit diesem, wenn Sie Shell starten, wird die urls.py nicht importiert und Ihre Signale werden nicht
angehängt
Ja, meine Antwort ist irgendwie veraltet. Es scheint, dass Django heutzutage eine AppConfig-Klasse hat. Das letzte Mal, als ich Django verwendet habe, war es Version 1.3. Schlagen Sie vor, dies zu untersuchen.
Aisbaa
1
wir sind immer noch 1.6 und so musste ich alle unsere signale.py in modelle verschieben, sonst wurden sellerie- und managementbefehle nicht abgeholt
dalore
5

In alten Django-Versionen wäre es in Ordnung, die Signale auf das __init__.pyoder vielleicht auf das zu setzen models.py(obwohl die Modelle am Ende für meinen Geschmack viel zu groß sein werden).

Mit Django 1.9 ist es meiner Meinung nach besser, die Signale in eine signals.pyDatei zu platzieren und sie mit dem zu importieren apps.py, wo sie nach dem Laden des Modells geladen werden.

apps.py:

from django.apps import AppConfig


class PollsConfig(AppConfig):
    name = 'polls'

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

Sie können Ihre Signale auch in signals.pyund handlers.pyin einem anderen Ordner innerhalb Ihres Modells mit dem Namen aufteilen signals, aber für mich ist das etwas über das Engineering hinaus. Schauen Sie sich das Platzieren von Signalen an

Tyson Rodez
quelle
3

Ich vermute, dass Sie das tun, damit Ihre Signale registriert werden und irgendwo gefunden werden. Ich habe meine Signale normalerweise direkt in eine models.py-Datei eingefügt.

Issac Kelly
quelle
Ja, wenn ich das Signal in die Modelldatei verschiebe, wird das Problem behoben. Aber meine model.py-Datei ist ziemlich groß mit allen Klassen, Managern und Modellregeln.
Mo J. Mughrabi
1
Manager sind meiner Erfahrung nach etwas einfacher herauszuholen. Managers.py ftw.
Issac Kelly
2

Dies gilt nur, wenn Sie Ihre Signale in einer separaten signals.pyDatei haben

In völliger Übereinstimmung mit der Antwort von @EricMarcos, aber es sollte angegeben werden, dass die Django- Dokumente ausdrücklich raten, die Variable default_app_config nicht zu verwenden (obwohl es nicht falsch ist). Für aktuelle Versionen wäre der richtige Weg:

my_app / apps.py

from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'my_app'

    def ready(self):
        import my_app.signals

settings.py

(Stellen Sie sicher, dass Sie nicht nur Ihren App-Namen in installierten Apps haben, sondern stattdessen den relativen Pfad zu Ihrer AppConfig.)

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

Eine Alternative besteht darin, die Rückruffunktionen zu importieren signals.pyund zu verbinden in models.py:

Signale.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: Beim Importieren YourModelin signals.pywird eine Rekursion erstellt. Verwenden Sie senderstattdessen.

Ps2: Wenn Sie die Instanz erneut in der Rückruffunktion speichern, wird eine Rekursion erstellt. Sie können ein Steuerargument in der .saveMethode erstellen, um es zu steuern.

Rafael
quelle