📖 Теория — Урок 45
⚡ Ключевые концепции урока
- Сигнал — механизм уведомлений внутри Django-приложения (паттерн «наблюдатель»)
- post_save — срабатывает после сохранения; содержит параметр
created - pre_save — срабатывает до сохранения; позволяет изменить поля экземпляра
- @receiver(signal, sender=Model) — декоратор для регистрации обработчика
- signal.connect(handler, sender=Model) — альтернативная регистрация
- AppConfig.ready() — правильное место для импорта signals.py
- EMAIL_BACKEND = '...console...' — письма в консоль при разработке
- send_mail(subject, message, from_email, recipient_list) — отправка письма
Часть 1: Что такое сигналы Django
Декомпозиция — это процесс разбиения сложной системы или задачи на более простые, управляемые компоненты или подзадачи, чтобы облегчить анализ, разработку и управление.
Практический смысл сигналов: когда в одном месте приложения происходит событие (например, создаётся пользователь), другие компоненты (например, модуль email-уведомлений) могут на него среагировать — без прямой связи между ними в коде. Это улучшает читаемость и поддерживаемость кода.
Основные встроенные сигналы
| Сигнал | Когда срабатывает |
|---|---|
pre_save | Перед сохранением объекта модели |
post_save | После сохранения объекта модели |
pre_delete | Перед удалением объекта модели |
post_delete | После удаления объекта модели |
m2m_changed | При изменении ManyToMany-отношения |
pre_migrate | Перед выполнением миграций |
post_migrate | После выполнения миграций |
Часть 2: Параметры встроенных сигналов
Каждый сигнал передаёт своим обработчикам набор именованных аргументов. Зная их, можно строить точную логику обработки.
pre_save и post_save
sender— класс модели, отправляющий сигналinstance— экземпляр модели, который сохраняетсяraw—True, если модель загружена напрямую из БД без десериализации (по умолчаниюFalse)using— база данных, используемая для сохраненияupdate_fields— поля, которые обновляются (если указано при вызовеsave(update_fields=[...]))created— только для post_save:Trueесли объект создан,Falseесли обновлён
pre_delete и post_delete
sender— класс моделиinstance— экземпляр, который удаляетсяusing— используемая база данных
m2m_changed
action— тип действия:pre_add,post_add,pre_remove,post_remove,pre_clear,post_clearinstance— экземпляр модели, инициирующий изменениеmodel— класс модели, участвующей в отношении ManyToManypk_set— набор первичных ключей, добавленных или удалённыхusing— используемая база данных
pre_migrate и post_migrate
sender— приложение, отправляющее сигналapp_config— конфигурация приложения, для которого выполняется миграцияverbosity— уровень детализации выводаinteractive— интерактивно ли выполняется миграцияusing,plan,apps— дополнительный контекст миграции
Часть 3: Обработчики и регистрация сигналов
@receiver или метода connect.
Способ 1: декоратор @receiver
Рекомендуемый современный подход. Декоратор читается как «эта функция — обработчик сигнала post_save для модели Book».
# signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Book
@receiver(post_save, sender=Book)
def book_saved(sender, instance, created, **kwargs):
if created:
print(f'New book created: {instance.title}')
else:
print(f'Book updated: {instance.title}')
Объяснение параметров функции:
sender— класс модели, отправивший сигналinstance— экземпляр модели, который был сохранёнcreated—Trueесли объект создан,Falseесли обновлён**kwargs— любые дополнительные аргументы (обязателен для совместимости)
Способ 2: метод connect()
Альтернативный способ — явная регистрация через метод сигнала.
from django.db.models.signals import post_save
from .models import Book
def book_saved(sender, instance, created, **kwargs):
if created:
print(f'New book created: {instance.title}')
else:
print(f'Book updated: {instance.title}')
# Подключение функции-обработчика к сигналу
post_save.connect(book_saved, sender=Book)
Поток обработки сигнала
- Создание или обновление объекта — вызов
Book.objects.create(...)илиbook.save() - Автоматическая отправка сигнала — Django отправляет
post_saveпосле сохранения объекта - Вызов обработчиков — все зарегистрированные для данного сигнала и отправителя функции вызываются
- Обработка информации — функция-обработчик выполняет бизнес-логику (логирование, email, создание связанных объектов)
Часть 4: Регистрация сигналов в приложении (apps.py)
apps.py в методе ready(). Без этого сигналы не будут подключены!
# apps.py
from django.apps import AppConfig
class FirstAppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'first_app'
def ready(self):
import first_app.signals # Импортируем модуль сигналов
Почему именно ready()?
- Метод
ready()вызывается когда Django полностью загрузил все модели и приложения - Импорт
signals.pyв этом месте гарантирует, что все модели уже доступны и нет циклических импортов - Это гарантирует, что обработчики сигналов будут активны при выполнении соответствующих событий
INSTALLED_APPS указан именно 'first_app.apps.FirstAppConfig' (или краткое имя 'first_app', если в AppConfig задан default_app_config), иначе ready() не будет вызван с вашим классом конфигурации.
Часть 5: Настройка параметров электронной почты
Чтобы функция send_mail работала корректно, необходимо настроить email-бэкенд в settings.py. Django поддерживает несколько бэкендов.
Вариант 1: ConsoleBackend — для разработки и тестирования
Письма не отправляются по-настоящему, а выводятся прямо в консоль сервера. Удобно при локальной разработке.
# settings.py
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Вариант 2: SmtpBackend — для боевого окружения
Реальная отправка через SMTP-сервер (например, Gmail, Yandex, корпоративный SMTP).
# settings.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'your_email@gmail.com'
EMAIL_HOST_PASSWORD = 'your_password'
EMAIL_HOST_PASSWORD в коде напрямую. Используйте переменные окружения (os.environ.get('EMAIL_HOST_PASSWORD')) или файл .env с библиотекой python-decouple / django-environ.
Функция send_mail()
from django.core.mail import send_mail
send_mail(
subject='Тема письма',
message='Текст письма',
from_email='sender@example.com',
recipient_list=['recipient@example.com'],
fail_silently=False, # True — не бросать исключение при ошибке отправки
)
Параметры send_mail():
subject— тема письма (строка)message— текст письма (строка, plain text)from_email— адрес отправителяrecipient_list— список получателейfail_silently— еслиTrue, ошибки при отправке игнорируются; по умолчаниюFalsehtml_message— HTML-версия письма (опционально)
Пример вывода ConsoleBackend
При создании объекта с настроенным ConsoleBackend в консоли появится примерно следующее:
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: New Book Created
From: admin@gmail.com
To: admin@gmail.com
Date: Tue, 16 Jun 2024 00:03:08 -0000
Message-ID: <171928458886.12044.11920313322863035917@DESKTOP-CI123U1>
Book 60 has been created.
Это означает, что email-инфраструктура работает корректно и при смене бэкенда на SMTP письмо будет реально отправлено.
Резюме
Типичные сценарии применения:
- Автоматическое создание связанных объектов (токены для пользователей)
- Обновление полей при сохранении (
updated_at) - Email-уведомления администратора или пользователя
- Логирование событий удаления, изменения
- Инвалидация кэша при изменении модели