Spec-Zone.ru › Django 4.1

Сигналы

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

from django.apps import AppConfig
from django.core.signals import request_finished

class MyAppConfig(AppConfig):
    ...

    def ready(self):
        # Implicitly connect signal handlers decorated with @receiver.
        from . import signals
        # Explicitly connect a signal handler.
        request_finished.connect(signals.my_callback)

Примечание

Метод 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 [source]

Все сигналы являются экземплярами django.dispatch.Signal.

Например:

import django.dispatch

pizza_done = django.dispatch.Signal()

Это объявляет сигнал pizza_done.

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

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

Signal.send(sender, **kwargs) [source]
Signal.send_robust(sender, **kwargs) [source]

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

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

class PizzaStore:
    ...

    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 если нет. Когда sender передаётся как леничная ссылка на <app label>.<model>, этот метод всегда возвращает None.

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

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/4.1/topics/signals/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API