Spec-Zone.ru › Django 1.10

Сигналы

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(object):
    ...

    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() перехватывает все ошибки, происходящие от класса Exception Python, и гарантирует, что все получатели будут уведомлены о сигнале. Если возникнет ошибка, экземпляр ошибки возвращается в паре кортежей для получателя, который поднял ошибку.

Трейсы находятся в атрибуте __traceback__ ошибок, возвращаемых при вызове send_robust().

Отключение сигналов

Signal.disconnect(receiver=None, sender=None, dispatch_uid=None) [source]

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

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

Устаревшее с версии 1.9: Аргумент weak устарел, так как не имеет эффекта. Он будет удалён в Django 2.0.

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

Spec-Zone.ru

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