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

Внутри ли транзакции вызывается post_save

3.0 Senior🔥 141 комментариев
#Django#Базы данных (SQL)

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

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

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

Вызывается ли post_save внутри транзакции

Это важный вопрос при работе с Django сигналами. Ответ: ДА, post_save вызывается ВНУТРИ транзакции, пока она ещё не завершена.

Основное поведение

from django.db import transaction
from django.db.models.signals import post_save
from django.dispatch import receiver
from myapp.models import User

@receiver(post_save, sender=User)
def on_user_created(sender, instance, created, **kwargs):
    print(f"post_save вызван для {instance.id}")
    # Мы ЕЩЁ ВНУТРИ транзакции!
    # Если здесь будет ошибка, вся транзакция откатится

# Использование
with transaction.atomic():
    user = User.objects.create(name='John')
    # post_save вызывается ЗДЕСЬ, всё ещё внутри atomic()
    print(f"User создан: {user.id}")
    # Только после выхода из with откатывается/фиксируется транзакция

Временная шкала:

1. transaction.atomic() начинается
2. User.objects.create() сохраняет в БД
3. post_save сигнал вызывается ← ВСЕ ЕЩЕ ВНУТРИ транзакции
4. Обработчик post_save выполняется
5. Выход из atomic() блока
6. Транзакция коммитится (COMMIT) или откатывается (ROLLBACK)

Опасность: откат транзакции

from django.db import transaction
from django.db.models.signals import post_save
from myapp.models import User, UserProfile

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)
        # Если здесь ошибка — ВЕСЬ User также откатится!

# Это опасно:
try:
    with transaction.atomic():
        user = User.objects.create(name='John')
        # post_save создаёт UserProfile
        # Если создание UserProfile вызывает ошибку, User НЕ СОЗДАН!
except Exception as e:
    print(f"Ошибка: {e}")
    # User не существует в БД, потому что откатилась вся транзакция

Проверка: post_save вызывается внутри или после?

from django.db import transaction
from django.db.models.signals import post_save
from myapp.models import User
import time

@receiver(post_save, sender=User)
def on_user_save(sender, instance, **kwargs):
    # Проверяем, есть ли текущая транзакция
    current_transaction = transaction.get_connection().in_atomic_block
    print(f"post_save: in_atomic_block = {current_transaction}")

# Тест 1: с atomic()
print("=== С atomic() ===")
with transaction.atomic():
    user = User.objects.create(name='John')
    # Выведет: post_save: in_atomic_block = True

# Тест 2: без atomic()
print("\n=== Без atomic() ===")
user = User.objects.create(name='Jane')
# Выведет: post_save: in_atomic_block = False

Решение: transaction.on_commit()

Чтобы выполнить действие ПОСЛЕ завершения транзакции, используй transaction.on_commit():

from django.db import transaction
from django.db.models.signals import post_save
from myapp.models import User
from myapp.tasks import send_welcome_email

@receiver(post_save, sender=User)
def on_user_created(sender, instance, created, **kwargs):
    if created:
        # Отправить письмо ПОСЛЕ завершения транзакции
        transaction.on_commit(
            lambda: send_welcome_email.delay(instance.id)
        )

Временная шкала с on_commit():

1. transaction.atomic() начинается
2. User.objects.create() сохраняет в БД
3. post_save сигнал вызывается
4. on_commit() регистрирует callback (но не выполняет!)
5. Выход из atomic() блока
6. Транзакция КОММИТИТСЯ
7. ТЕ ТОЛЬКО ТЕПЕРЬ send_welcome_email.delay() выполняется

Практический пример: опасный vs безопасный код

Опасный код (внутри транзакции):

from django.db import transaction
from django.db.models.signals import post_save
from myapp.models import User, Notification

@receiver(post_save, sender=User)
def notify_admins(sender, instance, created, **kwargs):
    if created:
        # ОПАСНО: если это вызывает ошибку, User откатится!
        Notification.objects.create(
            type='new_user',
            message=f'New user: {instance.name}',
            admin=instance.id % 10  # Может быть несуществующий ID!
        )

# Это откатит User если админ не найден
try:
    with transaction.atomic():
        user = User.objects.create(name='John')
except:
    # User не создан!
    pass

Безопасный код (после транзакции):

from django.db import transaction
from django.db.models.signals import post_save
from myapp.models import User

@receiver(post_save, sender=User)
def notify_admins(sender, instance, created, **kwargs):
    if created:
        # БЕЗОПАСНО: выполнится ПОСЛЕ завершения транзакции
        transaction.on_commit(
            lambda: send_notification.delay(instance.id)
        )

# User ВСЕГДА создан, даже если send_notification вызовет ошибку
with transaction.atomic():
    user = User.objects.create(name='John')

Множественные on_commit() обработчики

from django.db import transaction

transaction.on_commit(lambda: print("Callback 1"))
transaction.on_commit(lambda: print("Callback 2"))

# Выполняют в порядке регистрации после COMMIT
# Вывод:
# Callback 1
# Callback 2

Использование с savepoints

from django.db import transaction

with transaction.atomic():
    user = User.objects.create(name='John')
    
    # Savepoint — вложенная транзакция
    with transaction.atomic():
        user.name = 'Jane'
        user.save()
        # post_save вызывается, но savepoint ещё не завершён
    
    # Здесь savepoint завершился, но внешняя транзакция ещё открыта

# post_save вызывается на уровне САМОЙ ВНУТРЕННЕЙ транзакции
# Но in_atomic_block = True для обеих уровней

Проверка в реальном проекте

from django.db import transaction, connection
from django.db.models.signals import post_save
from myapp.models import User

@receiver(post_save, sender=User)
def debug_transaction(sender, instance, **kwargs):
    # Получить текущий уровень транзакции
    print(f"Transaction in_atomic_block: {transaction.get_connection().in_atomic_block}")
    
    # Проверить, есть ли активная транзакция
    if connection.in_atomic_block:
        print("ВЫ ВНУТРИ ТРАНЗАКЦИИ!")
    else:
        print("Вы НЕ внутри транзакции")

Решения для разных сценариев

Сценарий 1: Отправить email ПОСЛЕ создания User

# ❌ Неправильно
@receiver(post_save, sender=User)
def send_email(sender, instance, created, **kwargs):
    if created:
        send_welcome_email(instance.email)  # Может откатиться User!

# ✅ Правильно
@receiver(post_save, sender=User)
def send_email(sender, instance, created, **kwargs):
    if created:
        transaction.on_commit(
            lambda: send_welcome_email(instance.email)
        )

Сценарий 2: Логировать в другую БД

# ❌ Опасно
@receiver(post_save, sender=User)
def log_user(sender, instance, **kwargs):
    other_db_log = LogEntry(
        action='user_created',
        user_id=instance.id
    )
    other_db_log.save(using='analytics')  # Может откатиться User!

# ✅ Правильно
@receiver(post_save, sender=User)
def log_user(sender, instance, **kwargs):
    def log_async():
        other_db_log = LogEntry(
            action='user_created',
            user_id=instance.id
        )
        other_db_log.save(using='analytics')
    
    transaction.on_commit(log_async)

Сценарий 3: Вызвать external API

# ❌ Опасно
@receiver(post_save, sender=User)
def sync_to_api(sender, instance, **kwargs):
    requests.post('https://api.example.com/users', json={
        'id': instance.id,
        'name': instance.name
    })  # Может откатиться User если API недоступен!

# ✅ Правильно (используй Celery)
@receiver(post_save, sender=User)
def sync_to_api(sender, instance, **kwargs):
    transaction.on_commit(
        lambda: sync_to_api_task.delay(instance.id)
    )

Заключение

  • post_save вызывается ВНУТРИ транзакции — это основной факт
  • Если post_save вызывает ошибку, ВЕСЬ User также откатится
  • Используй transaction.on_commit() для действий ПОСЛЕ завершения транзакции
  • Для асинхронных задач (email, API) всегда используй on_commit() + Celery
  • Помни: post_save — не место для side effects, которые не должны откатываться вместе с моделью