Сигнал pre_save

Сигнал pre_save отправляется непосредственно перед тем, как объект модели будет сохранен в базу данных. Он позволяет выполнить произвольный код на этапе подготовки объекта к сохранению, например, изменить значения полей, провести дополнительную валидацию или создать связанные объекты. Сигнал отправляется как при создании нового объекта, так и при обновлении существующего, и передает в обработчик сам экземпляр модели, флаг создания и дополнительные аргументы.

Синтаксис

pythonfrom django.db.models.signals import pre_save
from django.dispatch import receiver

@receiver(pre_save, sender=MyModel)
def my_handler(sender, instance, raw, using, update_fields, **kwargs):
  # выполнить действия перед сохранением
  pass

Параметры обработчика

Обработчик сигнала pre_save получает следующие параметры:

Пример

Рассмотрим модель Article, в которой перед сохранением нужно автоматически обновлять поле updated_at текущей датой:

pythonfrom django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.utils import timezone

class Article(models.Model):
  title = models.CharField(max_length=200)
  content = models.TextField()
  created_at = models.DateTimeField(auto_now_add=True)
  updated_at = models.DateTimeField(auto_now=True)

@receiver(pre_save, sender=Article)
def update_article_timestamp(sender, instance, **kwargs):
  instance.updated_at = timezone.now()
  
# Создаем статью
article = Article(title="First Article", content="Some content")
article.save()

В этом примере перед сохранением каждой статьи в поле updated_at будет записано текущее время.

Пример

Перед сохранением пользователя автоматически преобразуем его email в нижний регистр:

pythonfrom django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver

class User(models.Model):
  username = models.CharField(max_length=100)
  email = models.EmailField()
  is_active = models.BooleanField(default=True)

@receiver(pre_save, sender=User)
def normalize_user_email(sender, instance, **kwargs):
  if instance.email:
    instance.email = instance.email.lower()

# Создаем пользователя
user = User(username="john_doe", email="John@Example.com")
user.save()

# Проверяем результат
print(f"Email: {user.email}")

Результат выполнения кода:

python"Email: john@example.com"

Пример

Используем сигнал pre_save для автоматического создания связанного объекта при сохранении нового продукта:

pythonfrom django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver

class Product(models.Model):
  name = models.CharField(max_length=100)
  price = models.IntegerField()

class ProductLog(models.Model):
  product = models.OneToOneField(Product, on_delete=models.CASCADE)
  status = models.CharField(max_length=50)

@receiver(pre_save, sender=Product)
def create_product_log(sender, instance, **kwargs):
  if not instance.pk:
    # Новый продукт - создаем лог
    log = ProductLog(product=instance, status="created")
    log.save()
    
# Создаем продукт
product = Product(name="Laptop", price=1200)
product.save()

# Проверяем наличие лога
has_log = ProductLog.objects.filter(product=product).exists()
print(f"Log exists: {has_log}")

Результат выполнения кода:

python"Log exists: True"

Пример

Отфильтруем сигнал по типу операции, используя проверку первичного ключа, и выполним разные действия при создании и обновлении записи:

pythonfrom django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver

class Category(models.Model):
  name = models.CharField(max_length=50)
  slug = models.SlugField(unique=True)
  is_active = models.BooleanField(default=True)

@receiver(pre_save, sender=Category)
def handle_category_save(sender, instance, **kwargs):
  if instance.pk is None:
    # Создание новой категории
    print(f"Creating new category: {instance.name}")
  else:
    # Обновление существующей категории
    print(f"Updating category: {instance.name}")

# Создание новой категории
category = Category(name="Technology", slug="tech")
category.save()

# Обновление категории
category.is_active = False
category.save()

Результат выполнения кода:

python"Creating new category: Technology"
"Updating category: Technology"

Смотрите также

  • сигнал post_save,
    который отправляется после сохранения объекта
  • сигнал pre_delete,
    который отправляется перед удалением объекта
  • метод save,
    который сохраняет объект в базу данных
  • метод clean,
    который выполняет валидацию полей модели