💻 Примеры — Урок 45
⚡ Ключевые примеры
- Пример 1:
post_save→ автоматически создать токен при регистрации пользователя - Пример 2:
pre_save→ обновитьupdated_atперед каждым сохранением - Пример 3:
post_save→ отправить email администратору при создании объекта - Пример 4:
post_delete→ залогировать удаление объекта
Пример 1: Автоматическое создание токена для нового пользователя
При регистрации нового пользователя автоматически создаётся токен для аутентификации через DRF TokenAuthentication.
# signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import User
@receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
Разбор:
@receiver(post_save, sender=User)— регистрируем обработчик для сигналаpost_saveмоделиUsercreated=False— значение по умолчанию в сигнатуре функции; реальное значение передаёт Django- Проверка
if created— токен создаётся только при первом создании пользователя, не при обновлении Token.objects.create(user=instance)— создаём токен, связанный с конкретным пользователем
# apps.py
from django.apps import AppConfig
class UsersConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'users'
def ready(self):
import users.signals # Активируем обработчики сигналов
Пример 2: Обновление поля updated_at перед сохранением
При каждом сохранении объекта Book автоматически обновляется поле updated_at. Используется сигнал pre_save, чтобы изменить экземпляр ещё до записи в БД.
Модель (models.py)
# models.py
from django.db import models
from django.contrib.auth.models import User
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=100)
published_date = models.DateField()
created_at = models.DateTimeField(null=True, blank=True)
updated_at = models.DateTimeField(null=True, blank=True)
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='books')
Обработчик через connect() (signals.py)
# signals.py
from django.utils import timezone
from django.db.models.signals import pre_save
from .models import Book
def update_timestamp(sender, instance, **kwargs):
instance.updated_at = timezone.now()
# Подключение функции-обработчика к сигналу
pre_save.connect(update_timestamp, sender=Book)
Разбор:
- Сигнал
pre_saveсрабатывает до сохранения — изменениеinstance.updated_atсразу попадёт в запись БД - Метод
pre_save.connect(update_timestamp, sender=Book)регистрирует обработчик без декоратора timezone.now()возвращает timezone-aware datetime — правильно для Django сUSE_TZ = True
Пример 3: Email-уведомление администратора при создании объекта
Когда создаётся новый объект Book, на почту администратора отправляется уведомление.
Настройки (settings.py)
# settings.py — для тестирования (письма в консоль)
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Обработчик (signals.py)
# signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.mail import send_mail
from .models import Book
@receiver(post_save, sender=Book)
def notify_admin_on_new_book(sender, instance, created, **kwargs):
if created:
send_mail(
subject='New Book Created',
message=f'Book {instance.id} "{instance.title}" has been created.',
from_email='admin@gmail.com',
recipient_list=['admin@gmail.com'],
)
Разбор:
- Условие
if created— письмо отправляется только при первичном создании, не при каждомsave() send_mail()— стандартная Django-функция; с ConsoleBackend выведет письмо в консоль вместо реальной отправкиinstance.idиinstance.title— доступны, поскольку обработчик post_save вызывается уже после записи в БД
Ожидаемый вывод в консоли
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 "Django for Beginners" has been created.
Пример 4: Логирование удаления объектов
При удалении объекта Genre информация об этом фиксируется в логах приложения.
# signals.py
import logging
from django.db.models.signals import post_delete
from django.dispatch import receiver
from .models import Genre
logger = logging.getLogger(__name__)
@receiver(post_delete, sender=Genre)
def log_genre_deletion(sender, instance, **kwargs):
logger.info(f'Genre deleted: {instance.name}')
Разбор:
logging.getLogger(__name__)— создаёт логгер с именем текущего модуля (например,first_app.signals)post_delete— срабатывает после удаления;instanceещё доступен (хотя pk уже None в некоторых версиях)logger.info()— запись в лог; настройтеLOGGINGвsettings.pyдля сохранения в файл
Пример 5: Полный поток — создание объекта и сигнал
Демонстрация полного цикла: создание объекта → автоматический сигнал → вызов обработчика.
# В Django shell или views.py
from first_app.models import Book
# Создание объекта запускает сигнал post_save с created=True
book = Book.objects.create(
title="New Book",
author="Author Name",
published_date="2000-01-01",
owner_id=1
)
# → Автоматически вызывается notify_admin_on_new_book()
# → В консоли появляется mock-email
# Обновление объекта запускает post_save с created=False
book.title = "Updated Title"
book.save()
# → Обработчик вызывается, но условие if created пропускает send_mail()
Пример 6: Настройка реального SMTP (Gmail)
Для Gmail необходимо использовать App Password (пароль приложения), а не обычный пароль аккаунта. Двухфакторная аутентификация должна быть включена.
# settings.py — настройка для Gmail SMTP
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 = 'abcd efgh ijkl mnop' # App Password из Google Account
# Лучше — через переменные окружения:
import os
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD')
⚠️ Проверить по документации: настройки Gmail для App Passwords и OAuth2 могут меняться. Актуальные инструкции см. в справке Google.