Что такое сигналы (signals) в Django


telephone-exchange

Что такое сигнал в Django Framework ?

На бытовом уровне это система (диспетчер сигналов) которая обрабатывает некоторые виды событий которые генерирует система. По сути система сигналов разделяется на два компонента:

  • sender  — компонент посылающий сигнал;
  • receiver — компонент отвечающий за обработку сигнала.

Опять же, пример на бытовом уровне.

Допустим у нас есть три модели:

class UserProfile(models.Model):
    nick = models.CharField(max_length = 30)
    score = models.FloatField(default = 0)

class Post(models.Model):
    user_profile = models.ForeignKey(UserProfile)
    text = models.TextFiled()

class UserAvatar(models.Model):
    user_profile = models.ForeignKey(UserProfile)
    image = models.UrlField()

И допустим у нас есть следующее условие: при каждом добавлении записи Post или UserAvatar должно обновляться поле UserProfile.score увеличиваясь на единицу.

Самый простой и первый приходящий в голову способ — это перегрузить методы save моделей Post и UserAvatar. Но пионэры не ищут простых путей, так что я заюзаю систему сигналов джанги, да и к тому-же зачем засорять лишней логикой save да еще и в двух моделях.

Вообще, сигналы, условно можно разделить на два типа:

Встроенные разделяются на несколько подгрупп:

  • Сигналы моделей. Данная группа самая обширная и, наверное, самая часто используемая. Она состоит из следующих сигналов:
    • pre_init, post_init — посылаются соответственно до и после вызова метода  __init__ модели;
    • pre_save, post_save — соответственно посылаются до и после вызова метода save модели;
    • pre_delete, post_delete — по аналогии, до и после удаления объекта (вызов метода delete);
    • m2m_changed — отправляется при изменении объектов m2m (ManyToMany) связи;
    • class_prepared — отправляется после регистрации модели в Djange. Как главсит документация  it’s not generally used in third-party applications.
  • Менеджментные сигналы (я не знаю как из нормально перевести на русский язык)
    • pre_syncdb, post_syncdb — вызваются до и после запуска команды manage.py syncdb
  • Request/response сигналы (аналогично, фантазия на перевод не работает)
    • request_started — посылается когда Django начинает обработку request;
    • request_finished — посылается после завершения обработки request;
  • Тестовые сигналы — запускаются только когда запущены юнит тесты, группа состоит из 2-х сигналов setting_changed, template_rendered
  • Сигналы генерируемые движком работы с БД (не путать с ORM). Содержит только сигнал connection_created который запускается после создания соединения с БД.

Для нашего примера, нам вполне хватит одного сигнала post_save.  Почему именно его ? Потому что нам важно сделать инкрементацию поля score  после сохранения моделей UserAvatar или Post, данный сигнал как раз за это и отвечает.

Сам сигнал «испускается» в кишках ORM Django (точнее за это отвечает Signal Dispatcher), наша задача только реализовать приемник (ни или обработчик сигнала) данного типа. Для этого в Django есть декоратор reciever(signal, **kwargs).

Вот код нашего обработчика:

from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender = Post)
@receiver(post_save, sender = UserAvatar)
def add_score(instance, **kwargs):
    profile = instance.user_profile
    profile.score += 1
    profile.save()

Как видно, я использовал два декоратора для сигнала post_save от разных моделей. В метод add_save, который после «оборачивания» декоратором receiver стал приемником событий post_save, передается собственно объект который вызвал это событие. В нашем случае это могут быть  объекты класса UserAvatar и Post.  Так как обе модели имеют поле user_profile, то инкрментация поля score не представляет сложности (иначе бы пришлось проверять тип пришедшего объекта и делать условия, но имхо проще сделать 2 разных сигнала).

Кроме встроенных сигналов есть возможность создавать свои сигналы. Но это тема следующей заметки.

С сигналами связано два неприятных момента с которыми можно столкнуться при излишне активном использовании:

  • Рекурсивный вызов сигналов. Когда один сигнал может запустить другой который в свою очередь вызовет первый. Тут важно внимательно следить за вызовами;
  • «Размазывание» логики. Из за того что встроенные сигналы явным образом нигде не вызываются, то очень легко забыть, что например, сохранение какого либо объекта запускает череду сигналов. Из за этого бывают непредвиденные и сложно отлаживаемые баги (все вроде работает, ничего не падает а результат не тот что ожидается)

И последнее. Сигналы удобно отслеживать через  Django Debug Toolbar. Для этого в настройках DEBUG_TOOLBAR_PANELS  нужно добавить ‘debug_toolbar.panels.signals.SignalDebugPanel’

Реклама

Что такое сигналы (signals) в Django: 7 комментариев

  1. Получается если использовать этот пример, то score будет изменяться не только при добавлении, а и при изменении записей в БД?

  2. Спасибо за статью, сигналы весьма полезная штука.

    Менеджментные сигналы -> Управляющие сигналы
    Как главсит документация -> Как гласит документация
    вызваются до и после запуска -> вызываются до и после запуска
    reciever(signal, **kwargs) -> receiver(signal, **kwargs)

  3. » В метод
    add_save,
    который после «оборачивания» декоратором receiver стал приемником событий post_save, передается собственно объект который вызвал это событие. »

    Имелось ввиду метод
    add_score
    наверное?

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход /  Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

w

Connecting to %s