Сигналы

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

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

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

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

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

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

Signal.connect(receiver[, sender=None, weak=True, dispatch_uid=None])
Параметры:
  • receiver – Функция, которая будет привязана к этому сигналу. Смотрите Функции-получатели.
  • sender – Указывает конкретного отправителя. Смотрите Сигналы, получаемые от определённых отправителей..
  • weak – Django сохраняет обработчики сигналов используя слабые ссылки(weak references). По этому, если функция-обработчик является локальной функцией, сборщик мусора может удалить ее. Чтобы избежать этого, передайте weak=False в connect().
  • dispatch_uid – Уникальный идентификатор получателя сигнала. На случай, если назначение обработчика может вызываться несколько раз. Смотрите Предотвращение дублирования сигналов.

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

receiver(signal)
Параметры: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().

Изменено в Django 1.7:

Т.к. ready() не существует в предыдущих версиях Django, регистрацию обработчиков сигналов обычно выполняют в модуле models.

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

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