📖 Теория — Урок 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 — экземпляр модели, который сохраняется
  • rawTrue, если модель загружена напрямую из БД без десериализации (по умолчанию 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_clear
  • instance — экземпляр модели, инициирующий изменение
  • model — класс модели, участвующей в отношении ManyToMany
  • pk_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 — экземпляр модели, который был сохранён
  • createdTrue если объект создан, 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)

Поток обработки сигнала

  1. Создание или обновление объекта — вызов Book.objects.create(...) или book.save()
  2. Автоматическая отправка сигнала — Django отправляет post_save после сохранения объекта
  3. Вызов обработчиков — все зарегистрированные для данного сигнала и отправителя функции вызываются
  4. Обработка информации — функция-обработчик выполняет бизнес-логику (логирование, 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, ошибки при отправке игнорируются; по умолчанию False
  • html_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 письмо будет реально отправлено.

Резюме

Сигналы в Django обеспечивают механизм для реагирования на события и изменения в приложении. Они позволяют декомпозировать код, улучшая его читаемость и поддерживаемость. Использование сигналов может существенно упростить выполнение повторяющихся задач и автоматизацию процессов.

Типичные сценарии применения:

  • Автоматическое создание связанных объектов (токены для пользователей)
  • Обновление полей при сохранении (updated_at)
  • Email-уведомления администратора или пользователя
  • Логирование событий удаления, изменения
  • Инвалидация кэша при изменении модели