Właściwe miejsce do przechowywania mojego pliku signal.py w projekcie Django

88

Opierając się na dokumentacji Django, którą czytałem, wydaje się, signals.pyże folder aplikacji jest dobrym miejscem do rozpoczęcia, ale problem, z którym się spotykam, polega na tym, że kiedy tworzę sygnały pre_savei próbuję zaimportować klasę z modelu, jest to importw moim modelu.

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

Ten kod nie będzie działał, ponieważ importuję Comm_Queuewewnątrz, signals.pya także importuję sygnały wewnątrz models.py.

Czy ktoś może poradzić się, jak mogę rozwiązać ten problem?

pozdrowienia

Mo J. Mughrabi
źródło

Odpowiedzi:

65

Oryginalna odpowiedź, dla Django <1.7:

Możesz zarejestrować sygnały, importując signals.pyje do __init__.pypliku aplikacji :

# __init__.py
import signals

To pozwoli na import models.pyz signals.pybez okrągłych błędów importu.

Jednym z problemów związanych z tym podejściem jest to, że zniekształca wyniki pokrycia, jeśli używasz pokrycia.

Powiązana dyskusja

Edycja: dla Django> = 1.7:

Od czasu wprowadzenia AppConfig zalecanym sposobem importowania sygnałów jest jego init()funkcja. Zobacz odpowiedź Erica Marcosa, aby uzyskać więcej informacji.

yprez
źródło
6
używając sygnałów w Django 1.9, użyj poniższej metody (zalecanej przez django). ta metoda nie działa dającAppRegistryNotReady("Apps aren't loaded yet.")
s0nskar
1
Eric Marcos, jego odpowiedź powinna być akceptowaną odpowiedzią: stackoverflow.com/a/21612050/3202958 od Django> = 1.7, używając konfiguracji aplikacji
Nrzonline,
1
Zgoda. Zmienię odpowiedź, aby wskazywała na odpowiedź Erica Marcosa dla Django
1.7+
195

Jeśli używasz Django <= 1.6, polecam rozwiązanie Kamagatos: po prostu zaimportuj sygnały na końcu modułu modeli.

W przypadku przyszłych wersji Django (> = 1.7) zalecanym sposobem jest zaimportowanie modułu sygnałów do funkcji config ready () aplikacji :

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
źródło
7
W dokumentacji 1.7 wspominają również, że czasami ready może być wywoływana wiele razy, więc aby uniknąć powielania sygnałów, dołącz unikalny identyfikator do wywołania złącza sygnału: request_finished.connect (my_callback, dispatch_uid = "my_unique_identifier") Gdzie dispatch_uid to zwykle ciąg ale może to być dowolny obiekt z możliwością mieszania. docs.djangoproject.com/en/1.7/topics/signals/…
Emeka
13
To powinna być akceptowana odpowiedź! Zaakceptowana odpowiedź powyżej generuje błąd podczas wdrażania za pomocą uwsgi
Patrick
2
Hm, nie działa dla mnie z django 2. Jeśli importuję model bezpośrednio w gotowym - wszystko w porządku. Gdybym import modelu w sygnałów i sygnałów przywozowych w gotowy Dostaję błąd doesn't declare an explicit app_label..
Aldarund
@Aldarun, możesz spróbować umieścić „my_app.apps.MyAppConfig” wewnątrz INSTALLED_APPS.
Ramil Aglyautdinov
26

Aby rozwiązać problem, wystarczy zaimportować pliki signal.py po definicji modelu. To wszystko.

Kamagatos
źródło
2
Jest to zdecydowanie najłatwiejsze i nie miałem pojęcia, że ​​zadziałaby bez cyklicznej zależności. Dzięki!
bradenm
2
Znakomity. Jak ten lepszy niż moja odpowiedź. Chociaż nie bardzo rozumiem, dlaczego nie powoduje to okrągłego importu ...
yprez
rozwiązanie nie działa z włączoną wtyczką autopep8 w Eclipse.
ramusus
5

Umieszczam również sygnały w pliku signal.py, a także mam ten fragment kodu, który ładuje wszystkie sygnały:

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

To jest dla projektu, nie jestem pewien, czy działa na poziomie aplikacji.

aisbaa
źródło
To jest moje ulubione rozwiązanie, o ile pasuje do innych wzorców (takich jak tasks.py)
dalore
1
Znalazłem problem z tym, jeśli uruchomisz powłokę, adres urls.py nie zostanie zaimportowany, a twoje sygnały nie zostaną dołączone
dalore
tak, moja odpowiedź jest trochę przestarzała, wygląda na to, że django ma obecnie klasę AppConfig. Ostatnim razem, gdy używałem django, była to wersja 1.3. Sugeruje zbadanie tego.
aisbaa
1
nadal mamy 1,6, więc musiałem przenieść wszystkie nasze sygnały .py do modeli, w przeciwnym razie polecenia selera i zarządzania nie zostały odebrane
dalore
5

W starych wersjach Django byłoby dobrze umieścić sygnały na __init__.pylub może w models.py(chociaż na koniec modele będą zdecydowanie za duże jak na mój gust).

W przypadku Django 1.9 lepiej jest, jak sądzę, umieścić sygnały w signals.pypliku i zaimportować je z plikiem apps.py, gdzie zostaną załadowane po załadowaniu modelu.

apps.py:

from django.apps import AppConfig


class PollsConfig(AppConfig):
    name = 'polls'

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

Możesz również podzielić swoje sygnały na signals.pyi handlers.pyw innym folderze w swoim modelu, również o nazwie signals, ale dla mnie to tylko kwestia inżynierii. Spójrz na Umieszczanie sygnałów

Tyson Rodez
źródło
3

Domyślam się, że robisz to, aby Twoje sygnały były rejestrowane, aby gdzieś je znaleźć. Po prostu umieszczam moje sygnały normalnie w pliku models.py.

Issac Kelly
źródło
tak, kiedy przenoszę sygnał do pliku modelu, rozwiązuje to problem. Ale mój plik model.py jest dość duży i zawiera wszystkie klasy, menedżery i reguły modelu.
Mo J. Mughrabi,
1
Z mojego doświadczenia menedżerowie są nieco łatwiejsi. Managers.py ftw.
Issac Kelly,
3

Ma to zastosowanie tylko wtedy, gdy masz sygnały w oddzielnym signals.pypliku

Całkowicie zgadzam się z odpowiedzią @EricMarcos, ale należy stwierdzić, że dokumentacja django wyraźnie radzi, aby nie używać zmiennej default_app_config (chociaż nie jest to zła). W przypadku aktualnych wersji poprawnym sposobem byłoby:

my_app / apps.py

from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'my_app'

    def ready(self):
        import my_app.signals

settings.py

(Upewnij się, że nie masz tylko nazwy aplikacji w zainstalowanych aplikacjach, ale zamiast tego względną ścieżkę do konfiguracji aplikacji)

INSTALLED_APPS = [
    'my_app.apps.MyAppConfig',
    # ...
]
Xen_mar
źródło
1

Alternatywą jest zaimportowanie funkcji zwrotnych z signals.pyi połączenie ich w models.py:

sygnały.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: Importowanie YourModelw signals.pystworzy rekurencji; użyj senderzamiast tego.

Ps2: ponowne zapisanie instancji w funkcji zwrotnej spowoduje utworzenie rekursji. Możesz utworzyć argument kontrolny w .savemetodzie, aby ją kontrolować.

Rafael
źródło