Spec-Zone.ru › Django 3.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().

Примечание

Метод 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(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:
    ...

    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 используется для идентификации получателя.

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

Spec-Zone.ru

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