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


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.


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'

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.


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.


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


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.


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',
    # ...
]

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ć.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.