Сигналы
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.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, dispatch_uid=None)[source]
Для отключения приёмника от сигнала вызовите Signal.disconnect(). Аргументы описаны в Signal.connect(). Метод возвращает True если приёмник был отключен и False если нет.
Аргумент receiver указывает зарегистрированный приёмник для отключения. Он может быть None если используется dispatch_uid для идентификации приёмника.
Было добавлено булевое значение возврата.
Устарело начиная с версии 1.9: Аргумент weak устарел, так как он не оказывает никакого влияния. Он будет удален в Django 2.0.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.9/topics/signals/