Spec-Zone.ru › Django 3.2

Сигналы

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

Разные сигналы используют разные объекты в качестве отправителей; вам нужно будет обратиться к документации по встроенным сигналам для получения подробностей о каждом конкретном сигнале.

Предотвращение дублирующих сигналов

В некоторых ситуациях код подключения получателей к сигналам может выполняться несколько раз. Это может привести к тому, что ваша функция-получатель будет зарегистрирована более одного раза и, следовательно, вызвана столько раз для события сигнала. Например, метод 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.

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

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

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

Для отправки сигнала вызовите либо 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)

Для отключения получателя от сигнала вызовите Signal.disconnect(). Аргументы описываются в Signal.connect(). Метод возвращает True , если получатель был отключен, и False — в противном случае.

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

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

Spec-Zone.ru

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