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() класса QuerySet.
django.db.models.signals.m2m_changed
Отправляются после изменения ManyToManyField в модели.
django.core.signals.request_started & django.core.signals.request_finished
Отправляются, когда Django начинает или заканчивает HTTP запрос.
Полный список этих сигналов, а также описание каждого сигнала, см. в документации по встроенным сигналам.
Вы можете также определять и посылать свои собственные сигналы; см. ниже.
Для того, чтобы принять сигнал, Вам необходимо с помощью метода Signal.connect() зарегистрировать функцию receiver, которая вызывается, когда сигнал послан:
Параметры: |
|
---|
Давайте посмотрим, как это работает, зарегистрировав сигнал request_finished, который вызывается после завершения выполнения HTTP запроса.
Во-первых, мы должны определить функцию-получатель. Получатель должен быть Python функцией или методом:
def my_callback(sender, **kwargs):
print("Request finished!")
Заметьте, что функция принимает аргумент sender, а также аргументы (**kwargs) в формате словаря; все обработчики сигналов должны принимать подобные аргументы.
Отправителей мы рассмотрим чуть позже, а сейчас обратите внимание на аргументы **kwargs. Все сигналы имеют возможность посылать именованные аргументы и могут изменить их набор в любой момент. Сигнал request_finished документирован как не посылающий аргументов, и у нас может появиться искушение записывать наш обработчик сигнала в виде my_callback(sender).
Это было бы неверно – на самом деле, Django выдаст ошибку, если Вы это сделаете. Так произойдёт, потому что при любом вызове аргументы могут быть добавлены к сигналу и получатель должен быть в состоянии обработать эти новые аргументы.
Есть два способа, которыми Вы можете подключить получатель к сигналу. Вы можете вручную вызвать connect:
from django.core.signals import request_finished
request_finished.connect(my_callback)
Кроме того, вы можете использовать декоратор receiver() при определении вашего получателя:
Параметры: | 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")
Вы можете создавать свои собственные сигналы в ваших приложениях.
Все сигналы являются экземплярами класса django.dispatch.Signal, где providing_args – список названий аргументов сигнала, которые будут доступны слушателям. Этот аргумент предназначен просто для документирования, никакой проверки, передаёт ли сигнал эти параметры, не выполняется.
Например:
import django.dispatch
pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])
Это объявление сигнала pizza_done, который предоставит получателям аргументы toppings и size.
Вы можете задать в этом списке аргументов любые значения передаваемых параметров.
В Django существует два способа отправки сигналов.
Для отправки сигнала необходимо вызвать 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() перехватывает все ошибки, наследуемые от класса Exception языка Python, и гарантирует, что сигнал дойдёт до всех получателей. Если произойдёт ошибка в одном из них, экземпляр исключения будет помещён в кортежную пару, для получателя, который соответствует вызываемой ошибке.
Трассировочная информация доступна через атрибут __traceback__ ошибок, возвращаемых при вызове send_robust().
Чтобы отключить получатель от сигнала, вызовите Signal.disconnect(). Аргументы те же, что и у Signal.connect(). Метод возвращает True в случае, если получатель был отключен и False - если нет.
В аргументе receiver указывается получатель, который должен перестать получать сигнал. Аргумент может содержать None, если для идентификации получателя используется dispatch_uid.
Было добавлено возвращение булева значения.
Не рекомендуется, начиная с версии 1.9: Аргумент weak устарел и не имеет никакого эффекта. Он будет удален в Django 2.0.
Mar 31, 2016