Spec-Zone.ru › Django 2.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) [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, и гарантирует, что все получатели будут уведомлены о сигнале. Если произошла ошибка, экземпляр ошибки возвращается в паре кортежей для получателя, поднявшего ошибку.

Обратные трассировки (tracebacks) присутствуют в атрибуте __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.2/topics/signals/

Spec-Zone.ru

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