Как работают сигналы pre_save и post_save в Django
Что проверяют
Знаете ли вы, в какой момент и кем отправляются эти сигналы и когда они не сработают вовсе. Следом обычно спрашивают, почему письмо из post_save иногда уходит для записи, которой в базе так и не оказалось.
Как отвечать
Оба сигнала отправляет Model.save(): pre_save до SQL-запроса, post_save сразу после него. Обработчики выполняются синхронно, в том же потоке и в той же транзакции. Исключение в pre_save отменяет сохранение, исключение в post_save вылетает уже после записи в базу.
Аргументы обработчика: sender (класс модели), instance, raw (True при загрузке фикстур через loaddata, трогать другие записи в этом случае нельзя), using, update_fields. У post_save есть ещё created, по нему отличают создание от обновления.
Как подключать правильно:
- регистрация: обработчики лежат в
signals.pyи импортируются вAppConfig.ready(). Иначе модуль никто не импортирует, и@receiverне сработает; dispatch_uid: страхует от двойной регистрации, если модуль импортируется дважды;- побочные эффекты: письма, задачи Celery и запросы во внешние системы оборачивают в
transaction.on_commit(), чтобы они выполнились только после коммита; - асинхронность: с Django 5.0 у сигналов есть
asend(), а обработчики могут быть async-функциями.
Скажите и о том, когда сигналы не нужны. Для своей модели логику понятнее положить в переопределённый save() или сервисный слой: сигнал прячет связь, и новый человек в проекте её не найдёт. Сигналы хороши, когда модель чужая, например чтобы создать профиль при появлении нового User.
Где спотыкаются
Ждут сигнал от массовых операций. QuerySet.update(), bulk_create(), bulk_update() и сырой SQL не вызывают save(), значит и сигналов не будет.
Вызывают instance.save() внутри post_save. Сигнал срабатывает снова, получается рекурсия. Нужно обновлять через QuerySet.update() или проверять update_fields.
Читают связи многие-ко-многим в post_save. Админка и формы сохраняют M2M после самого объекта, поэтому там ещё старые значения, для этого есть m2m_changed.