⚖️ Старый vs Новый — Урок 45
⚡ Ключевые изменения
- Регистрация сигналов в
models.py→ правильно вapps.py ready() - Пароль SMTP в коде напрямую → переменные окружения (
os.environ.get()) - Только
send_mail()→ такжеEmailMessageс вложениями, cc, bcc - Метод
.connect()в globals →@receiverдекоратор вsignals.py
1. Место регистрации сигналов
Из лекции (старое)
# models.py — ПЛОХО
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=Book)
def book_saved(sender, instance, created, **kwargs):
...
# Или в конце models.py:
post_save.connect(book_saved, sender=Book)
Проблема: при импорте models.py происходит регистрация сигнала. Если модуль импортируется несколько раз — обработчик подключается несколько раз, письмо/логика выполняется дважды.
Современное (Django 5.x)
# 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):
...
# apps.py — регистрация при старте
from django.apps import AppConfig
class BookConfig(AppConfig):
name = 'books'
def ready(self):
import books.signals # Один импорт при старте Django
Преимущество: ready() вызывается ровно один раз при полной загрузке всех приложений. Сигналы зарегистрированы безопасно, без дублирования.
2. Хранение пароля EMAIL_HOST_PASSWORD
Из лекции (небезопасно)
# settings.py
EMAIL_HOST_PASSWORD = 'your_password'
Проблема: пароль в открытом виде в коде → попадает в git-репозиторий → утечка учётных данных.
Современное (Django 5.x)
# settings.py
import os
EMAIL_HOST_PASSWORD = os.environ.get(
'EMAIL_HOST_PASSWORD', ''
)
# Или через python-decouple:
from decouple import config
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')
Преимущество: пароль хранится в переменных окружения или файле .env (добавлен в .gitignore).
3. Регистрация AppConfig (default_app_config)
Устаревший способ (Django < 3.2)
# books/__init__.py
default_app_config = 'books.apps.BookConfig'
Проблема: устаревший механизм, удалён в Django 4.x.
Современное (Django 3.2+)
# settings.py
INSTALLED_APPS = [
'books.apps.BookConfig', # Указываем явно
# или краткое имя если default_auto_field настроен:
'books',
]
Преимущество: явная регистрация AppConfig в INSTALLED_APPS. Django 3.2+ автоматически ищет AppConfig без __init__.py.
4. Отправка email только при создании (не при обновлении)
Без проверки created (плохо)
@receiver(post_save, sender=Book)
def notify_admin(sender, instance, **kwargs):
# Отправляет при каждом save()!
send_mail(
'Book changed',
f'Book {instance.id} changed.',
'admin@example.com',
['admin@example.com'],
)
Проблема: письмо отправляется при каждом вызове .save(), в том числе при обновлении.
С проверкой created (правильно)
@receiver(post_save, sender=Book)
def notify_admin(sender, instance, created, **kwargs):
if created: # Только при первичном создании
send_mail(
'New Book Created',
f'Book {instance.id} has been created.',
'admin@example.com',
['admin@example.com'],
)
Преимущество: чёткий контроль — письмо отправляется только при создании нового объекта.
5. Защита от raw=True (загрузка фикстур)
Без проверки raw
@receiver(post_save, sender=User)
def create_token(sender, instance, created, **kwargs):
if created:
Token.objects.create(user=instance)
# При loaddata fixtures — создаёт токены для тестовых данных
С проверкой raw
@receiver(post_save, sender=User)
def create_token(sender, instance, created, raw, **kwargs):
if created and not raw:
# raw=True при loaddata — пропускаем
Token.objects.create(user=instance)
Преимущество: при загрузке фикстур (manage.py loaddata) токены не создаются повторно для уже существующих пользователей.