Где должны находиться обработчики сигналов в проекте django?

144

Я только начал внедрять слушателей сигналов в проект django. Пока понимаю, что это такое и как ими пользоваться. Мне трудно понять, куда мне их положить. В документации с сайта django сказано следующее:

Где должен жить этот код?

Вы можете разместить код обработки сигнала и регистрационный код где угодно. Однако вам необходимо убедиться, что модуль, в котором он находится, импортируется на ранней стадии, чтобы обработка сигналов была зарегистрирована до отправки каких-либо сигналов. Это делает файл models.py вашего приложения хорошим местом для регистрации обработчиков сигналов.

Хотя это хорошее предложение, наличие в моем models.py не модельных классов или методов просто меня не так.

Итак, каковы лучшие практики / правила для хранения и регистрации обработчиков сигналов?

Джейсон Уэбб
источник

Ответы:

41

Мне вообще нравится делать их методами классов самой модели. Это сохраняет все в рамках одного класса и означает, что вам не нужно беспокоиться об импорте чего-либо.

Дэниел Розман
источник
2
А где вы обычно к сигналам подключаете обработчики?
DataGreed
1
@DataGreed: внизу соответствующего файла models.py.
Дэниел Роузман
105
Если вы слушаете сигналы, излучаемые этой моделью, то размещение всех слушателей делает все упражнение бессмысленным, не так ли? Смысл сигналов - развязать. Разве слушатели не должны жить с кодом, который интересуется этими удаленными событиями? Вопрос в том, как обеспечить загрузку слушателей перед эмиттерами.
Джон Ми
В моем случае я хочу слушать сигнал модели, Fooкоторая является частью fooapp. Но приемник сигнала является расширением и находится в другом приложении (например otherapp).
Guettli
2
По мнению Джона Ми, это не сильно отличается от простого переопределения save () и т. Д.
Мэтт
250

Это было добавлено в документацию при выпуске Django 1.7 :

Строго говоря, код обработки сигналов и регистрации может находиться где угодно, хотя рекомендуется избегать корневого модуля приложения и его модуля моделей, чтобы минимизировать побочные эффекты импорта кода.

На практике обработчики сигналов обычно определяются в подмодуле сигналов приложения, к которому они относятся. Приемники сигналов подключаются в методе ready () класса конфигурации вашего приложения. Если вы используете декоратор Receiver (), просто импортируйте подмодуль сигналов внутри ready ().

Изменено в Django 1.7: поскольку ready () не существовало в предыдущих версиях Django, регистрация сигналов обычно происходила в модуле моделей.

Лучше всего определять обработчики в handlers.py в подмодуле сигналов, например, в файле, который выглядит так:

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

Лучшее место для регистрации обработчика сигнала - это AppConfig приложения, которое его определяет, с помощью метода ready () . Это будет выглядеть так:

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

Убедитесь, что вы загружаете свой AppConfig, указав его либо непосредственно в INSTALLED_APPS вашего settings.py, либо в __init__вашем приложении. См . Дополнительную информацию в документации по ready () .

Примечание. Если вы предоставляете сигналы другим приложениям, чтобы они тоже слушали, поместите их в __init__модуль сигналов, например, в файл, который выглядит так:

yourapp / сигналы / __ init__.py

import django.dispatch

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

Затем другое приложение может прослушивать ваш сигнал, импортировав и зарегистрировав его, например from yourapp.signals import task_generate_pre_save. Разделение ваших сигналов от ваших обработчиков сохраняет чистоту.

Инструкция для Django 1.6:

Если вы все еще застряли на Django 1.6 или ниже, вы бы сделали то же самое (определите свои обработчики в yourapp / signal / handlers.py), но вместо использования AppConfig вы загрузили бы обработчики через __init__.py из ваше приложение, например, что-то вроде:

yourapp / __ init__.py

import signals

Это не так хорошо, как использование метода ready (), потому что он часто вызывает проблемы с циклическим импортом.

Эйдан
источник
3
поскольку в документе говорится, что вы переопределяете готовность, вы можете сделать что-то вроде super (ReportsConfig, self) .ready () в случае, если django когда-либо решит заполнить ready () чем-то (с версии 1.7.0 он в настоящее время пуст)
w- -
3
Я думаю, что это лучший ответ, потому что он единственный, который устраняет побочные эффекты от импорта. Я пришел сюда в поисках передового опыта, потому что я очищаю приложение, которое не работает именно из-за такого рода побочных эффектов. Увы, приложение работает на django 1.6, а лучшие практики работают только на django 1.7. Временный обходной путь, позволяющий разрешить __init__импорт сигналов, не сработает для меня, поэтому мне интересно, есть ли другое место, из которого я мог бы импортировать сигналы, пока мы не будем готовы перейти на более позднюю версию django.
kasperd
1
Разве там не должно быть from . import handlers(или подобного) yourapp/signals/__init__.py?
dhobbs
1
Разве вы не должны также импортировать куда-нибудь модуль handlers.py? Я пытаюсь это сделать, и, похоже, он не определяет обработчик сигнала.
Andrés
1
fwiw Мне не нужна была yourproject.последняя строка блока кода класса TaskConfig. У меня это работает именно с этой структурой, так что рассмотрите этот вопрос :)
Грег Калека
40

Я только что столкнулся с этим, и, поскольку мои сигналы не связаны с моделью, я подумал, что добавлю свое решение.

Я регистрирую различные данные о входе / выходе, и мне нужно подключиться к django.contrib.auth.signals .

Я поместил обработчики сигналов в signals.pyфайл, а затем импортировал сигналы из __init__.pyфайла модуля, так как я считаю, что это вызывается, как только приложение запускается (тестирование с помощью printоператора предполагает, что он вызывается еще до того, как файл настроек будет прочитан).

# /project/__init__.py
import signals

и в signal.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)

Я новичок в Django (/ python), поэтому открыт для всех, кто скажет мне, что это ужасная идея!

Хьюго Роджер-Браун
источник
3
Это кажется логичным, но я бы посоветовал сделать это на уровне приложения.
Nils
2
Будьте осторожны, такая логика, скорее всего, приведет к срабатыванию дублирующих сигналов. user_logged_in.connect(on_logged_in)скорее всего, должен быть передан в dispatch_uidаргумент. Подробнее см . Docs.djangoproject.com/en/dev/topics/signals/… .
Скотт Коутс
Спасибо за это - полезно знать. Я регистрирую все входы в систему, используя этот метод (запись IP / пользовательского агента), и пока у меня не было дубликатов - хотя это не значит, что небольшое изменение в дальнейшем не вызовет проблем!
Хьюго Роджер-Браун
13

Я недавно прочитал эту статью о лучших практиках, когда дело доходит до размещения ваших проектов / приложений, и в ней предлагается, чтобы все ваши настраиваемые сигналы диспетчера помещались в файл с именемsignals.py . Однако это не решает полностью вашу проблему, поскольку вам все равно нужно куда-то их импортировать, и чем раньше они будут импортированы, тем лучше.

Предложение модели хорошее. Поскольку вы уже определили все в своем signals.pyфайле, это не должно занимать больше строки в верхней части файла. Это похоже на то, какadmin.py файла (с определениями классов вверху и кодом для регистрации всех настраиваемых классов администратора внизу), если вы определяете свои сигналы, затем подключаете их в одном файле.

Надеюсь, это поможет! В конечном итоге все сводится к тому, что вы предпочитаете.

гора
источник
1
Я также хотел поместить свои обработчики сигналов в signals.pyфайл, но не знал, как их потом вызывать. Импортируя его в свой models.pyфайл, я получил очень чистое решение, не «загрязняющее» мой файл models.py. Спасибо! :)
Данило Барген
10
там есть кросс-импорт: signal.py пытается импортировать модель из models.py
Иван Вирабян
9

models.py и signal.py в каждом приложении были рекомендованными местами для подключения сигналов, однако, на мой взгляд, они не лучшее решение для отправки сигналов и обработчиков. Диспетчеризация должна быть причиной изобретения сигналов и обработчиков в django.

Я долго боролся, и наконец мы нашли решение.

создать модуль коннектора в папке приложения

так что у нас есть:

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

в app / connector.py мы определили обработчики сигналов и подключили их. Приведен пример:

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)

затем в models.py добавляем в конец файла следующую строку:

from app import connector

Здесь все сделано.

Таким образом, мы можем поместить сигналы в signal.py, а все обработчики - в connector.py. Никакого беспорядка в моделях и сигналах.

Надеюсь, это даст другое решение.

Самуэль
источник
1
Итак, что входит в signal.py? Похоже, что из вашего примера это просто настраиваемые сигналы. Обычно мы просто комбинируем сигналы и разъемы, так как у большинства из них нет специальных сигналов.
dalore
@dalore да, все пользовательские сигналы помещаются в signal.py. У нас есть много настраиваемых сигналов. Но если у вас их немного, этот файл можно не указывать.
Samuel
тот же вопрос, что и @dal
olleh
1
обратите внимание, что все это теперь старый совет, теперь способ django заключается в использовании appconfig для импорта обработчиков, в которых находятся обработчики сигналов. И в signal.py идут пользовательские сигналы
dalore
4

Небольшое напоминание о AppConfig. Не забудьте установить:

# yourapp/__init__.py

default_app_config = 'yourapp.apps.RockNRollConfig'
валекс
источник
3

Я храню их в отдельном файле signals.py, models.pyпосле того как все модели определены. Я импортирую их и подключаю модели к сигналам.

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

Это обеспечивает мне логическое разделение, конечно, нет ничего плохого в том, чтобы хранить их в models.py , но таким образом легче управлять .

Надеюсь это поможет!!

Allsyed
источник
вы помещаете обработчики сигналов в "signal.py", что если мы назовем его "handlers.py"
Абдул Фатх
1
Неважно, назовете ли вы файл signal.py или handler.py. Это просто условность, а не правило.
allsyed