Сигналы
Django включает «диспетчер сигналов», который помогает разнесённым приложениям получать уведомления о действиях, происходящих в других частях фреймворка. Вкратце, сигналы позволяют определённым отправителям уведомлять набор получателей о том, что произошло какое-то действие. Они особенно полезны, когда многие части кода могут быть заинтересованы в одних и тех же событиях.
Django предоставляет набор встроенных сигналов, которые позволяют коду пользователя получать уведомления от самого Django о определённых действиях. Они включают некоторые полезные уведомления:
-
django.db.models.signals.pre_saveиdjango.db.models.signals.post_saveОтправляются до или после вызова метода
save()модели. -
django.db.models.signals.pre_deleteиdjango.db.models.signals.post_deleteОтправляются до или после вызова метода
delete()модели или методаdelete()набора запросов. -
django.db.models.signals.m2m_changedОтправляется при изменении
ManyToManyFieldмодели. -
django.core.signals.request_startedиdjango.core.signals.request_finishedОтправляется при запуске или завершении HTTP-запроса Django.
См. документацию по встроенным сигналам для полного списка и полного объяснения каждого сигнала.
Вы также можете определять и отправлять свои собственные пользовательские сигналы; см. ниже.
Прослушивание сигналов
Чтобы получить сигнал, вам необходимо зарегистрировать функцию-получатель, которая вызывается при отправке сигнала, используя метод Signal.connect():
-
Signal.connect(receiver, sender=None, weak=True, dispatch_uid=None)[source] -
Параметры: - receiver – Функция обратного вызова, которая будет подключена к этому сигналу. См. Функции-получатели для получения дополнительной информации.
- sender – Указывает конкретного отправителя для получения сигналов. См. Подключение к сигналам, отправляемым конкретными отправителями для получения дополнительной информации.
-
weak – Django хранит обработчики сигналов как слабые ссылки по умолчанию. Таким образом, если ваш получатель является локальной функцией, он может быть удалён сборщиком мусора. Чтобы предотвратить это, передайте
weak=Falseпри вызове метода сигналаconnect(). - dispatch_uid – Уникальный идентификатор для получателя сигнала в случаях, когда могут быть отправлены дублирующиеся сигналы. См. Предотвращение дублирующих сигналов для получения дополнительной информации.
Посмотрим, как это работает, зарегистрировав сигнал, который вызывается после завершения каждого HTTP-запроса. Мы будем подключаться к сигналу request_finished.
Функции-получатели
Сначала нам нужно определить функцию-получателя. Получателем может быть любая Python-функция или метод:
def my_callback(sender, **kwargs):
print("Request finished!")
Обратите внимание, что функция принимает аргумент sender вместе с произвольными именованными аргументами (**kwargs); все обработчики сигналов должны принимать эти аргументы.
Мы рассмотрим отправителей чуть позже, но сейчас взглянем на аргумент **kwargs. Все сигналы отправляют именованные аргументы и могут изменить эти именованные аргументы в любое время. В случае с request_finished, в документации указано, что он не отправляет аргументы, что означает, что мы можем попытаться написать обработку сигнала как my_callback(sender).
Это будет неправильно – на самом деле Django выбросит ошибку, если вы это сделаете. Это потому, что в любой момент могут быть добавлены аргументы к сигналу, и ваш получатель должен уметь обрабатывать эти новые аргументы.
Подключение функций-получателей
Существует два способа подключения получателя к сигналу. Вы можете использовать ручное подключение:
from django.core.signals import request_finished request_finished.connect(my_callback)
В качестве альтернативы, вы можете использовать декоратор receiver():
-
receiver(signal)[source] -
Параметры: signal – Сигнал или список сигналов для подключения к функции.
Вот как вы подключаетесь с помощью декоратора:
from django.core.signals import request_finished
from django.dispatch import receiver
@receiver(request_finished)
def my_callback(sender, **kwargs):
print("Request finished!")
Теперь наша функция my_callback будет вызываться каждый раз, когда запрос завершается.
Где должен находиться этот код?
Строго говоря, код обработки и регистрации сигналов может находиться где угодно, хотя рекомендуется избегать корневого модуля приложения и его модуля models для минимизации побочных эффектов импорта кода.
На практике обработчики сигналов обычно определяются в подмодуле signals приложения, к которому они относятся. Получатели сигналов подключаются в методе ready() вашего класса конфигурации приложения. Если вы используете декоратор receiver(), просто импортируйте подмодуль signals в ready().
Поскольку ready() не существовал в предыдущих версиях Django, регистрация сигналов обычно происходила в модуле models.
Примечание
Метод ready() может выполняться более одного раза во время тестирования, поэтому вам может потребоваться защитить свои сигналы от дублирования, особенно если вы планируете отправлять их в тестах.
Подключение к сигналам, отправляемым конкретными отправителями
Некоторые сигналы отправляются много раз, но вас будет интересовать только определённый подмножество этих сигналов. Например, рассмотрим сигнал django.db.models.signals.pre_save, отправляемый перед сохранением модели. В большинстве случаев вам не нужно знать, когда сохраняется любая модель – только когда сохраняется конкретная модель.
В таких случаях вы можете зарегистрироваться для получения сигналов, отправляемых только определёнными отправителями. В случае с django.db.models.signals.pre_save, отправителем будет класс модели, которая сохраняется, поэтому вы можете указать, что вы хотите получать сигналы, отправляемые только какой-то моделью:
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):
...
Функция my_handler будет вызываться только при сохранении экземпляра MyModel.
Разные сигналы используют разные объекты в качестве отправителей; вам нужно будет обратиться к документации по встроенным сигналам для получения подробностей о каждом конкретном сигнале.
Предотвращение дублирующих сигналов
В некоторых ситуациях код, подключающий получателей к сигналам, может выполняться несколько раз. Это может привести к тому, что ваша функция-получатель будет зарегистрирована более одного раза и, следовательно, вызываться несколько раз для одного события сигнала.
Если такое поведение проблематично (например, когда вы используете сигналы для отправки электронного письма всякий раз, когда модель сохраняется), передайте уникальный идентификатор как аргумент dispatch_uid для идентификации вашей функции-получателя. Этот идентификатор обычно будет строкой, хотя подойдёт любой хешируемый объект. В конечном итоге ваша функция-получатель будет связана с сигналом только один раз для каждого уникального значения dispatch_uid:
from django.core.signals import request_finished request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")
Определение и отправка сигналов
Ваши приложения могут использовать инфраструктуру сигналов и предоставлять собственные сигналы.
Определение сигналов
-
class Signal(providing_args=list)[source]
Все сигналы являются экземплярами django.dispatch.Signal. providing_args — это список имён аргументов, которые сигнал предоставит слушателям. Однако это чисто справочная информация, так как ничего не проверяет, что сигнал фактически предоставляет эти аргументы своим слушателям.
Например:
import django.dispatch pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])
Это объявляет сигнал pizza_done, который предоставит получателям аргументы toppings и size.
Помните, что вы можете изменять этот список аргументов в любое время, поэтому нет необходимости идеально подбирать API с первого раза.
Отправка сигналов
Существует два способа отправки сигналов в Django.
-
Signal.send(sender, **kwargs)[source]
-
Signal.send_robust(sender, **kwargs)[source]
Для отправки сигнала вызовите либо Signal.send(), либо Signal.send_robust(). Вы должны указать аргумент sender (который в большинстве случаев является классом) и можете добавить любые другие аргументы по своему усмотрению.
Например, вот как может выглядеть отправка нашего сигнала pizza_done.
class PizzaStore(object):
...
def send_pizza(self, toppings, size):
pizza_done.send(sender=self.__class__, toppings=toppings, size=size)
...
И send(), и send_robust() возвращают список пар кортежей [(receiver, response), ... ], представляющих список вызываемых функций-получателей и их значения ответов.
send() отличается от send_robust() тем, как обрабатываются исключения, генерируемые функциями-получателями. send() не перехватывает исключения, генерируемые получателями; он просто позволяет ошибкам распространяться. В результате не все получатели могут быть уведомлены о сигнале в случае ошибки.
send_robust() перехватывает все ошибки, выводимые из класса Python Exception, и гарантирует, что все получатели будут уведомлены о сигнале. Если произошла ошибка, экземпляр ошибки возвращается в паре кортежей для получателя, который вызвал ошибку.
Обработка трассировок присутствует в атрибуте __traceback__ ошибок, возвращённых при вызове send_robust().
Отключение сигналов
-
Signal.disconnect(receiver=None, sender=None, weak=True, dispatch_uid=None)[source]
Чтобы отключить получателя от сигнала, вызовите Signal.disconnect(). Аргументы описываются в Signal.connect(). Метод возвращает True, если получатель был отключен, и False, если нет.
Аргумент receiver указывает зарегистрированного получателя для отключения. Он может быть None, если для идентификации получателя используется dispatch_uid.
Было добавлено булево значение возвращаемого значения.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.8/topics/signals/