DjangoMiddleTechnical
Что такое сигналы (signals) в Django, когда их стоит использовать и когда следует избегать?
Сигналы — встроенный pub-sub в Django: post_save, pre_delete и др. Подключаются через @receiver, регистрируются в AppConfig.ready(). Хороши для инвалидации кэша и создания связанных объектов, но скрывают бизнес-логику и усложняют отладку.
Сигналы (signals) в Django
Сигналы — механизм publish-subscribe внутри процесса Django. Отправитель (sender) публикует событие, один или несколько получателей (receivers) реагируют на него. Это позволяет компонентам взаимодействовать, не зная друг о друге напрямую.
Встроенные сигналы Django
pre_save/post_save— до и после вызоваModel.save()pre_delete/post_delete— до и после удаления объектаm2m_changed— при изменении ManyToMany-отношенияpre_migrate/post_migrate— до и после применения миграцийrequest_started/request_finished— жизненный цикл HTTP-запроса
Подключение сигнала: декоратор и connect()
# myapp/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import UserProfile
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
"""Автоматически создаёт профиль при создании пользователя."""
if created:
UserProfile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
"""Сохраняет профиль вместе с пользователем."""
if hasattr(instance, "profile"):
instance.profile.save()
Сигналы нужно зарегистрировать в AppConfig.ready():
# myapp/apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = "myapp"
default_auto_field = "django.db.models.BigAutoField"
def ready(self):
import myapp.signals # noqa: F401 — регистрация сигналов
Кастомный сигнал
# myapp/signals.py
from django.dispatch import Signal, receiver
# Объявление сигнала
article_published = Signal() # sender передаётся при отправке
# Отправка из модели или сервиса
class Article(models.Model):
def publish(self):
self.status = "published"
self.save()
article_published.send(sender=self.__class__, article=self)
# Подписка
@receiver(article_published)
def notify_subscribers(sender, article, **kwargs):
# отправить уведомления подписчикам
from .tasks import send_notifications
send_notifications.delay(article.pk)
Когда использовать сигналы
- Создание связанных объектов при сохранении (например, профиль пользователя).
- Инвалидация кэша после изменения модели.
- Логирование изменений в audit-таблице.
- Уведомления между decoupled приложениями внутри монолита.
Когда избегать сигналов
- Бизнес-логика, которая должна быть явной — лучше вынести в сервисный слой или метод модели.
- Сложные цепочки зависимостей: сигнал A → сигнал B → сигнал C трудно отлаживать.
- Вызов внешних API или тяжёлых операций — используйте Celery tasks, запускаемые из
post_saveмаксимально лаконично. - Критичные транзакционные операции — сигнал может выполниться, но транзакция откатиться.
Сигналы и транзакции: on_commit()
from django.db import transaction
@receiver(post_save, sender=Article)
def schedule_indexing(sender, instance, created, **kwargs):
# Запускать задачу только после успешного коммита транзакции
transaction.on_commit(lambda: index_article.delay(instance.pk))
Подводные камни
- Сигналы не работают при
QuerySet.update()иQuerySet.delete()— эти методы обходятModel.save()иModel.delete(). - Дублирование регистрации: если
import myapp.signalsвызывается дважды (например, при горячей перезагрузке), обработчик подпишется дважды. Используйте параметрdispatch_uid:@receiver(post_save, sender=User, dispatch_uid="create_profile"). - Сигналы синхронны и выполняются в том же потоке и транзакции. Исключение в сигнале откатит всю транзакцию.
- Забытый
import myapp.signalsвready()— сигналы не регистрируются; код выглядит рабочим, но ничего не происходит. - Тяжёлые операции в
post_saveбезon_commitмогут выполниться при откате транзакции — always usetransaction.on_commit()для side effects. - Сигналы усложняют тестирование: при тесте одной модели неожиданно срабатывают обработчики другого приложения. Используйте
disconnect()илиmock.patchв тестах. - Отладка: невозможно легко понять, кто и где подписан на сигнал — в больших проектах это создаёт «магию», усложняющую онбординг.
Common mistakes
- Описывать signals только как термин и не показывать механизм на минимальном примере.
- Игнорировать ошибки, пустые данные, конкурентный доступ или границы транзакции.
- Не связывать поведение с официальным контрактом Django и реальной эксплуатацией.
What the interviewer is testing
- Объясняет signals через последовательность действий, а не через набор ключевых слов.
- Приводит короткий кодовый пример или production-сценарий с ожидаемым поведением.
- Называет хотя бы один риск: производительность, безопасность, транзакции, память или сопровождение.