Spec-Zone.ru › Django 4.0

Сигналы

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 a 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.0/topics/signals/

Spec-Zone.ru

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