Сигналы¶
Django включает в себя «диспетчер сигналов», который помогает развязанным приложениям получать уведомления, когда действия происходят в другом месте платформы. Короче говоря, сигналы позволяют определенным отправителям уведомить группу получателей о том, что произошло какое-то действие. Они особенно полезны, когда одни и те же события могут интересовать множество фрагментов кода.
Django provides a set of built-in signals that let user code get notified by Django itself of certain actions. These include some useful notifications:
django.db.models.signals.pre_save&django.db.models.signals.post_saveSent before or after a model’s
save()method is called.django.db.models.signals.pre_delete&django.db.models.signals.post_deleteSent before or after a model’s
delete()method or queryset’sdelete()method is called.django.db.models.signals.m2m_changedSent when a
ManyToManyFieldon a model is changed.django.core.signals.request_started&django.core.signals.request_finishedSent when Django starts or finishes an HTTP request.
See the built-in signal documentation for a complete list, and a complete explanation of each signal.
You can also define and send your own custom signals; see below.
Прослушивание сигналов¶
Для того, чтобы принять сигнал, Вам необходимо с помощью метода 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, чтобы сократить побочный эффект при импорте приложения.
In practice, signal handlers are usually defined in a signals
submodule of the application they relate to. Signal receivers are
connected in the ready() method of your
application configuration class. If you’re using the receiver()
decorator, import the signals submodule inside
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.
Отправителями различных сигналов могут быть различные объекты. Для получения детальной информации по каждому такому сигналу обращайтесь к документации по встроенным сигналам.
Предотвращение дублирования сигналов¶
В некоторых случаях код, соединяющий приемники с сигналами, может выполняться несколько раз. Это может привести к тому, что ваша функция приемника будет зарегистрирована более одного раза и, следовательно, будет вызвана столько же раз для сигнального события. Например, метод ready() может выполняться более одного раза во время тестирования. В более общем смысле, это происходит везде, где ваш проект импортирует модуль, в котором вы определяете сигналы, поскольку регистрация сигнала выполняется столько раз, сколько он импортируется.
Такое поведение может приводить к проблемам (например, если происходит отправка электронной почты всякий раз, когда посылается сигнал о сохранении модели), поэтому передавайте некоторый уникальный идентификатор в качестве значения аргумента dispatch_uid для идентификации в функции-получателе. Обычно, этот идентификатор является строкой, хотя подойдёт любой хешируемый объект. В итоге функция-получатель будет привязана к сигналу единожды для каждого уникального значения dispatch_uid.
from django.core.signals import request_finished
request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")
Создание и посылка сигналов.¶
Вы можете создавать свои собственные сигналы в ваших приложениях.
Когда использовать специальные сигналы
Сигналы — это неявные вызовы функций, которые усложняют отладку. Если отправитель и получатель вашего пользовательского сигнала находятся внутри вашего проекта, вам лучше использовать явный вызов функции.
Создание сигналов¶
- class Signal¶
Все сигналы являются экземплярами django.dispatch.Signal.
Например:
import django.dispatch
pizza_done = django.dispatch.Signal()
Это объявляет сигнал «pizza_done».
Отправка сигналов¶
There are two ways to send signals in Django.
- Signal.send(sender, **kwargs)¶
- Signal.send_robust(sender, **kwargs)¶
To send a signal, call either Signal.send() (all built-in signals use
this) or Signal.send_robust(). You must provide the sender argument
(which is a class most of the time) and may provide as many other keyword
arguments as you like.
Например, вот как может выглядеть отправка сигнала pizza_done:
class PizzaStore:
...
def send_pizza(self, toppings, size):
pizza_done.send(sender=self.__class__, toppings=toppings, size=size)
...
Both send() and send_robust() return a list of tuple pairs
[(receiver, response), ... ], representing the list of called receiver
functions and their response values.
send() отличается от send_robust() способом обработки исключений, генерируемых функцией-получателем. send() не ловит никаких исключений, сгенерированных в получателе, позволяя исключению проваливаться дальше. Таким образом, не все получатели могут получить сигнал при возникновении ошибки.
send_robust() перехватывает все ошибки, наследуемые от класса Exception языка Python, и гарантирует, что сигнал дойдёт до всех получателей. Если произойдёт ошибка в одном из них, экземпляр исключения будет помещён в кортежную пару, для получателя, который соответствует вызываемой ошибке.
Трассировочная информация доступна через атрибут __traceback__ ошибок, возвращаемых при вызове send_robust().
Отключение сигнала¶
- Signal.disconnect(receiver=None, sender=None, dispatch_uid=None)¶
To disconnect a receiver from a signal, call Signal.disconnect(). The
arguments are as described in Signal.connect(). The method returns
True if a receiver was disconnected and False if not.
В аргументе receiver указывается получатель, который должен перестать получать сигнал. Аргумент может содержать None, если для идентификации получателя используется dispatch_uid.