Сигналы
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:
...
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 используется для идентификации приёмника.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/2.0/topics/signals/