Сигналы

Django содержит “диспетчер сигналов”, который позволяет одним приложениям фреймворка получать уведомления от других после того, как в последних произойдут некоторые события. Вкратце, сигналы позволяют определённым отправителям уведомлять некоторый набор получателей о совершении действий. Сигналы особенно полезны, когда поведение многих фрагментов кода зависит от инициации одних и тех же событий.

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

Полный список этих сигналов, а также описание каждого сигнала, см. в документации по встроенным сигналам.

Вы можете также определять и посылать свои собственные сигналы; см. ниже.

Прослушивание сигналов

Для того, чтобы принять сигнал, Вам необходимо с помощью метода Signal.connect() зарегистрировать функцию receiver, которая вызывается, когда сигнал послан:

Signal.connect(receiver[, sender=None, weak=True, dispatch_uid=None])
Параметры:

Давайте посмотрим, как это работает, зарегистрировав сигнал 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 при определении вашего получателя:

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 будет вызываться каждый раз, когда запрос завершается.

receiver может также принимать список сигналов.

Changed in Django 1.5:

The ability to pass a list of signals was added.

Where should this code live?

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

Сигналы, получаемые от определённых отправителей.

Некоторые сигналы могу быть посланы много раз, но Вам будет нужно получать только определённое подмножество этих сигналов. Например, рассмотрим 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])

Все сигналы являются экземплярами класса django.dispatch.Signal, где providing_args – список названий аргументов сигнала, которые будут доступны слушателям. Этот аргумент предназначен просто для документирования, никакой проверки, передаёт ли сигнал эти параметры, не выполняется.

Например:

import django.dispatch

pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])

Это объявление сигнала pizza_done, который предоставит получателям аргументы toppings и size.

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

Отправка сигналов

В Django существует два способа отправки сигналов.

Signal.send(sender, **kwargs)
Signal.send_robust(sender, **kwargs)

Для отправки сигнала необходимо вызвать Signal.send() или Signal.send_robust(). Вы обязательно должны указать аргумент sender, кроме того можно указать сколько угодно других именованных аргументов.

Например, вот как может выглядать отправка сигнала pizza_done.

class PizzaStore(object):
    ...

    def send_pizza(self, toppings, size):
        pizza_done.send(sender=self, toppings=toppings, size=size)
        ...

И send(), и send_robust() возвращают список кортежей пар [(receiver, response), ... ]. Каждый кортеж содержит вызываемую функцию и ее ответ.

send() отличается от send_robust() способом обработки исключений, генерируемых функцией-получателем. send() не ловит никаких исключений, сгенерированных в получателе, позволяя исключению проваливаться дальше. Таким образом, не все получатели могут получить сигнал при возникновении ошибки.

send_robust() перехватывает все ошибки, наследуемые от класса Exception языка Python, и гарантирует, что сигнал дойдёт до всех получателей. Если произойдёт ошибка в одном из них, экземпляр исключения будет помещён в кортежную пару, для получателя, который соответствует вызываемой ошибке.

Отключение сигнала

Signal.disconnect([receiver=None, sender=None, weak=True, dispatch_uid=None])

Чтобы отключить получатель от сигнала, вызовите Signal.disconnect(). Аргументы те же, что и у Signal.connect().

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