Правильне місце для зберігання мого файлу signal.py у проекті Django


88

На основі документації Django, яку я читав, здається, що signals.pyз папки програми - це гарне місце для початку, але проблема, з якою я стикаюся, полягає в тому, що коли я створюю сигнали для pre_saveі намагаюся імпортувати клас із моделі, він конфліктує з importв моїй моделі.

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

Цей код не буде запущений, оскільки я імпортую Comm_Queueвсередину, signals.pyа також імпортую сигнали всередині models.py.

Хто-небудь може порадити, як я можу перейти до цього питання?

З повагою


Відповіді:


65

Оригінальна відповідь для Django <1,7:

Ви можете зареєструвати сигнали, імпортуючи signals.pyу __init__.pyфайл програми:

# __init__.py
import signals

Це дозволить імпортувати models.pyз signals.pyбез кругових помилок імпорту.

Однією з проблем цього підходу є те, що він псує результати покриття, якщо ви використовуєте покриття.py.

Пов'язана дискусія

Редагувати: для Django> = 1,7:

З моменту введення AppConfig рекомендований спосіб імпорту сигналів є у його init()функції. Докладніше див. У відповіді Еріка Маркоса .


6
використовуючи сигнали в Django 1.9, використовуйте метод нижче (рекомендований django). цей метод не працює, даючиAppRegistryNotReady("Apps aren't loaded yet.")
s0nskar

1
Ерік Маркос його відповіддю має бути прийнятою відповіддю: stackoverflow.com/a/21612050/3202958, оскільки Django> = 1.7, за допомогою конфігурації програми
Nrzonline

1
Домовились. Я відредагую відповідь, щоб вказати на відповідь Еріка Маркоса для Django 1.7+
yprez

195

Якщо ви використовуєте Django <= 1,6, я б рекомендував рішення Kamagatos: просто імпортуйте свої сигнали в кінці модуля ваших моделей.

Для майбутніх версій Django (> = 1,7) рекомендованим способом є імпорт модуля сигналів у функцію config ready () програми :

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
Вони також згадують в документації 1.7, що іноді готові можна викликати кілька разів, і тому, щоб уникнути дублюючих сигналів, приєднайте унікальний ідентифікатор до виклику вашого з'єднувача сигналу: request_finished.connect (my_callback, dispatch_uid = "my_unique_identifier") Де dispatch_uid зазвичай є рядком але може бути будь-яким об'єктом, що розширюється. docs.djangoproject.com/en/1.7/topics/signals/…
Емека

13
Це має бути прийнятою відповіддю! Прийнята відповідь видає помилку під час розгортання за допомогою uwsgi
Патрік

2
Хм, мені не вдається з django 2. Якщо я імпортую модель безпосередньо в готовому вигляді - все добре. Якщо я імпортую модель у сигналах та імпортую сигнали в готовому вигляді, я отримую помилку doesn't declare an explicit app_label..
Алдарунд

@Aldarun, ви можете спробувати помістити "my_app.apps.MyAppConfig" всередину INSTALLED_APPS.
Раміль Аглейтдінов

26

Щоб вирішити вашу проблему, вам просто потрібно імпортувати signal.py після визначення вашої моделі. Це все.


2
Це на сьогоднішній день найпростіше, і я не уявляв, що це спрацює без циклічної залежності. Дякую!
bradenm

2
Блискуче. Подобається цей краще, ніж моя відповідь. Хоча я насправді не розумію, як це не викликає кругового імпорту ...
yprez

рішення не працює з увімкненим плагіном autopep8 в Eclipse.
ramusus

5

Я також розміщую сигнали у файлі signal.py, а також маю цей фрагмент коду, який завантажує всі сигнали:

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

Це для проекту, я не впевнений, чи працює він на рівні програми.


Це моє улюблене рішення, наскільки воно відповідає іншим шаблонам (наприклад, task.py)
dalore

1
Знайшов проблему з цим, якщо ви запустите оболонку, urls.py не імпортується, і ваші сигнали не
приєднаються

так, моя відповідь якась застаріла, схоже, що в наші дні django має клас AppConfig. Востаннє я використовував django, це була версія 1.3. Пропоную дослідити навколо нього.
aisbaa

1
ми все ще 1.6, і тому мені довелося перенести всі наші сигнали.py в моделі, інакше команди селери та управління не отримували
dalore

5

У старих версіях Django було б непогано подавати сигнали на __init__.pyабо, можливо, на models.py(хоча в кінці моделі на мій смак будуть великими).

З Django 1.9, на мою думку, краще розмістити сигнали у signals.pyфайлі та імпортувати їх разом із apps.py, де вони будуть завантажуватися після завантаження моделі.

apps.py:

from django.apps import AppConfig


class PollsConfig(AppConfig):
    name = 'polls'

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

Ви також можете розділити свої сигнали на signals.pyта handlers.pyв іншій папці вашої моделі signals, але також для мене це більше, ніж інженерія. Погляньте на Розміщення сигналів


3

Я припускаю, що ви робите це, щоб ваші сигнали були зареєстровані, щоб вони десь знаходились. Я просто вкладаю свої сигнали прямо у файл models.py, як правило.


так, коли я переміщую сигнал всередину файлу моделі, це вирішує проблему. Але мій файл model.py досить великий із усіма класами, менеджерами та правилами моделей.
Mo J. Mughrabi

1
З мого досвіду, менеджери трохи прагнуть вирватися. Managers.py ftw.
Іссак Келлі,

3

Це стосується лише тих випадків, коли ваші сигнали signals.pyмістяться в окремому файлі

Цілком погоджуючись з відповіддю @EricMarcos, але слід зазначити, що django docs явно радить не використовувати змінну default_app_config (хоча це не неправильно). Для поточних версій правильним буде такий спосіб:

my_app / apps.py

from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'my_app'

    def ready(self):
        import my_app.signals

settings.py

(Переконайтеся, що у вашому додатку не просто ім’я програми, а натомість відносний шлях до вашого AppConfig)

INSTALLED_APPS = [
    'my_app.apps.MyAppConfig',
    # ...
]

1

Альтернативою є імпорт функцій зворотного виклику з signals.pyта підключення їх у models.py:

сигнали.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: Імпорт YourModelв signals.pyстворить рекурсії; використовувати senderзамість цього.

Ps2: Збереження екземпляра знову у функції зворотного виклику створить рекурсію. Ви можете зробити аргумент управління в .saveметоді, щоб керувати ним.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.