Сигналы
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(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() перехватывает все ошибки, производные от класса 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 для идентификации получателя.
Устарело начиная с версии 1.9: Аргумент weak устарел, так как он не оказывает никакого влияния. Он будет удалён в Django 2.0.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.11/topics/signals/