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

Примечание

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

    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) доступны в атрибуте __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/2.1/topics/signals/

Spec-Zone.ru

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