← Назад к вопросам

Что такое post_save в Django?

1.3 Junior🔥 171 комментариев
#DevOps и инфраструктура#Django

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

post_save сигнал в Django

post_save — это Django сигнал, который автоматически отправляется после того, как модель сохранена в БД. Это позволяет выполнить дополнительные действия (например, отправить email, обновить кеш или создать сопутствующие объекты) без изменения кода сохранения.

Основная концепция

Джанго использует паттерн Observer через сигналы:

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=User)  # Слушаем post_save для User
def create_user_profile(sender, instance, created, **kwargs):
    """Создает профиль пользователя при регистрации"""
    if created:  # Только при создании нового пользователя
        UserProfile.objects.create(user=instance)

post_save.connect(create_user_profile, sender=User)  # Альтернативный способ

Параметры post_save

@receiver(post_save, sender=Post)
def handle_post_save(sender, instance, created, raw, using, update_fields, **kwargs):
    """
    sender - модель которая отправила сигнал (Post)
    instance - конкретный объект который был сохранен
    created - True если объект создан, False если обновлен
    raw - True если используется loaddata команда
    using - имя БД
    update_fields - набор полей которые были обновлены (или None)
    """
    
    if created:
        print(f'Создан новый пост: {instance.title}')
    else:
        print(f'Обновлен пост: {instance.title}')

Практический пример 1: Создание профиля при регистрации

# models.py
from django.db import models
from django.contrib.auth.models import User

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
    bio = models.TextField(blank=True)
    avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

# signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    """Автоматически создает профиль при регистрации пользователя"""
    if created:
        UserProfile.objects.create(user=instance)

@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    """Автоматически сохраняет профиль при сохранении пользователя"""
    instance.profile.save()

# apps.py
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'myapp'
    
    def ready(self):  # Регистрируем сигналы когда app загружается
        import myapp.signals

Практический пример 2: Отправка email при создании заказа

# models.py
class Order(models.Model):
    STATUS_CHOICES = [('pending', 'В ожидании'), ('completed', 'Завершен')]
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
    created_at = models.DateTimeField(auto_now_add=True)
    total = models.DecimalField(max_digits=10, decimal_places=2)

# signals.py
from django.core.mail import send_mail
from django.template.loader import render_to_string

@receiver(post_save, sender=Order)
def send_order_confirmation(sender, instance, created, **kwargs):
    """Отправляет email подтверждение при создании заказа"""
    if created:
        subject = f'Заказ #{instance.id} подтвержден'
        message = f'Ваш заказ на сумму {instance.total} создан'
        send_mail(
            subject,
            message,
            'noreply@example.com',
            [instance.user.email]
        )

Практический пример 3: Обновление кеша

from django.core.cache import cache

@receiver(post_save, sender=Product)
def invalidate_product_cache(sender, instance, created, **kwargs):
    """Очищает кеш при обновлении продукта"""
    cache.delete(f'product_{instance.id}')  # Удаляем кеш продукта
    cache.delete('products_list')  # Удаляем кеш списка продуктов
    print(f'Очищен кеш для продукта {instance.id}')

Практический пример 4: Логирование изменений

from django.contrib.admin.models import LogEntry, ADDITION, CHANGE

@receiver(post_save, sender=Article)
def log_article_change(sender, instance, created, **kwargs):
    """Логирует создание и обновление статей"""
    if created:
        action_flag = ADDITION
        action_type = 'Создана новая статья'
    else:
        action_flag = CHANGE
        action_type = 'Статья обновлена'
    
    LogEntry.objects.create(
        content_type_id=ContentType.objects.get_for_model(Article).id,
        object_id=instance.id,
        object_repr=instance.title,
        action_flag=action_flag,
        change_message=f'{action_type}: {instance.title}'
    )

Работа с update_fields

@receiver(post_save, sender=User)
def handle_partial_update(sender, instance, update_fields=None, **kwargs):
    """Обрабатывает частичные обновления"""
    if update_fields is None:
        # Полное сохранение
        print('Сохранены все поля')
    else:
        # Частичное обновление
        print(f'Обновлены поля: {update_fields}')
        
        if 'email' in update_fields:
            print(f'Email изменен на {instance.email}')
        
        if 'first_name' in update_fields:
            print(f'Имя изменено на {instance.first_name}')

# Использование:
user = User.objects.get(id=1)
user.email = 'new@example.com'
user.save(update_fields=['email'])  # Сигнал получит update_fields=['email']

Отключение сигналов

Иногда нужно сохранить объект БЕЗ срабатывания сигналов:

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=User)
def expensive_operation(sender, instance, **kwargs):
    print('Дорогая операция!')
    # Например, отправка email, обновление кеша и т.д.

# Отключаем сигнал временно
post_save.disconnect(expensive_operation, sender=User)

user = User.objects.create(username='test')  # Сигнал НЕ сработает

# Включаем обратно
post_save.connect(expensive_operation, sender=User)

Или используйте флаг:

class User(models.Model):
    _skip_signals = False
    # остальные поля

@receiver(post_save, sender=User)
def my_signal(sender, instance, **kwargs):
    if instance._skip_signals:
        return  # Не выполняем операцию
    
    print('Операция выполнена')

# Использование:
user = User.objects.get(id=1)
user._skip_signals = True
user.save()  # Сигнал не сработает

Ошибка: Circular dependencies

# НЕПРАВИЛЬНО: бесконечный цикл!
@receiver(post_save, sender=Post)
def update_post(sender, instance, **kwargs):
    instance.updated_count += 1
    instance.save()  # Это снова вызовет post_save!

# ПРАВИЛЬНО:
@receiver(post_save, sender=Post)
def update_post(sender, instance, created, **kwargs):
    if created:
        return  # Только при создании
    
    Post.objects.filter(id=instance.id).update(updated_count=F('updated_count')+1)
    # Используем update() которая не вызывает сигналы

Pre_save vs Post_save

from django.db.models.signals import pre_save, post_save

@receiver(pre_save, sender=User)
def validate_before_save(sender, instance, **kwargs):
    """Выполняется ДО сохранения"""
    if instance.age < 18:
        raise ValueError('Возраст должен быть 18+')

@receiver(post_save, sender=User)
def action_after_save(sender, instance, **kwargs):
    """Выполняется ПОСЛЕ сохранения"""
    print(f'Пользователь {instance.username} сохранен в БД')

Все доступные Django сигналы

from django.db.models.signals import (
    pre_save,      # Перед сохранением
    post_save,     # После сохранения
    pre_delete,    # Перед удалением
    post_delete,   # После удалением
    m2m_changed    # Изменение Many-to-Many
)

from django.core.signals import (
    request_started,   # Начало request
    request_finished   # Конец request
)

Производительность

Сигналы могут замедлить код если в них тяжелые операции:

# ПЛОХО: долгая операция в сигнале
@receiver(post_save, sender=Order)
def process_payment(sender, instance, **kwargs):
    # Это блокирует весь request!
    charge_credit_card(instance.user)  # 5 секунд ждем

# ХОРОШО: используйте Celery для асинхронных задач
from celery import shared_task

@shared_task
def async_process_payment(order_id):
    order = Order.objects.get(id=order_id)
    charge_credit_card(order.user)

@receiver(post_save, sender=Order)
def trigger_payment(sender, instance, **kwargs):
    async_process_payment.delay(instance.id)  # Асинхронно

Итого

post_save используется для:

  • Создания сопутствующих объектов
  • Отправки уведомлений
  • Обновления кеша
  • Логирования
  • Синхронизации данных

Минусы:

  • Неявная логика (сложнее отладить)
  • Может замедлить код
  • Риск бесконечных циклов
  • Не срабатывает при bulk update/delete

Альтернативы:

  • Manager методы
  • Model методы (overridе save())
  • Celery для асинхронных задач
  • Webhooks для интеграций

post_save мощный инструмент, но используйте его разумно и не для сложной бизнес-логики.