Сигнал pre_delete

Сигнал pre_delete относится к механизму сигналов Django и используется для выполнения пользовательского кода до того, как объект модели будет удален из базы данных. Сигнал отправляется при вызове метода delete экземпляра модели или при удалении через QuerySet. Основная особенность этого сигнала - возможность выполнить подготовительные действия или вовсе предотвратить удаление объекта (в сочетании с исключениями), а также обработать связанные объекты до того, как каскадное удаление вступит в силу.

Параметрами сигнала являются: sender - класс модели, отправляющий сигнал; instance - удаляемый экземпляр модели; using - псевдоним базы данных, используемый для операции; origin - источник удаления (например, сам экземпляр или QuerySet); transaction - состояние транзакции, в которой выполняется удаление. Эти параметры позволяют гибко управлять процессом удаления и контекстом его выполнения.

Синтаксис

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

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

Пример

Рассмотрим простой пример логирования удаления статьи. Создадим модель Article с полями title и created_at, и модель Log для хранения истории удалений. Затем подключим обработчик сигнала:

pythonfrom django.db import models
from django.utils import timezone

class Article(models.Model):
  title = models.CharField(max_length=200)
  created_at = models.DateTimeField(auto_now_add=True)
  
  def __str__(self):
    return self.title

class Log(models.Model):
  message = models.CharField(max_length=255)
  created_at = models.DateTimeField(auto_now_add=True)
  
  def __str__(self):
    return self.message

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

pythonfrom django.db.models.signals import pre_delete
from django.dispatch import receiver
from myapp.models import Article, Log

@receiver(pre_delete, sender=Article)
def log_article_deletion(sender, instance, **kwargs):
  message = f"Article '{instance.title}' deleted at {timezone.now()}"
  Log.objects.create(message=message)

При удалении объекта Article создается запись в логе:

pythonfrom myapp.models import Article

article = Article.objects.get(id=1)
article.delete()
# В Log появится запись: "Article 'Some title' deleted at ..."

Пример

Часто возникает необходимость проверить зависимости перед удалением объекта. Например, удалять категорию можно только в том случае, если она не содержит статей. Реализуем это с помощью сигнала:

pythonfrom django.db import models
from django.core.exceptions import ValidationError

class Category(models.Model):
  name = models.CharField(max_length=100)
  
  def __str__(self):
    return self.name

class Article(models.Model):
  title = models.CharField(max_length=200)
  category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='articles')

Обработчик сигнала для модели Category, который проверяет наличие связанных статей:

pythonfrom django.db.models.signals import pre_delete
from django.dispatch import receiver
from django.core.exceptions import ValidationError
from myapp.models import Category

@receiver(pre_delete, sender=Category)
def prevent_category_deletion(sender, instance, **kwargs):
  if instance.articles.exists():
    raise ValidationError(
      f"Category '{instance.name}' has related articles and cannot be deleted."
    )

Попытка удалить категорию со статьями приведет к ошибке:

pythonfrom myapp.models import Category

category = Category.objects.get(id=1)
try:
  category.delete()
except ValidationError as e:
  print(e.message)

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

python"Category 'Technology' has related articles and cannot be deleted."

Пример

Иногда необходимо выполнить очистку внешних ресурсов, например, удалить файлы, связанные с удаляемым объектом. Предположим, у нас есть модель с полем image:

pythonfrom django.db import models

class Profile(models.Model):
  name = models.CharField(max_length=100)
  avatar = models.ImageField(upload_to='avatars/')

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

pythonimport os
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from myapp.models import Profile

@receiver(pre_delete, sender=Profile)
def delete_avatar_file(sender, instance, **kwargs):
  if instance.avatar:
    if os.path.isfile(instance.avatar.path):
      os.remove(instance.avatar.path)

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

pythonfrom myapp.models import Profile

profile = Profile.objects.get(id=1)
profile.delete()  # файл avatar удаляется с диска

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

  • сигнал post_delete,
    который срабатывает после удаления объекта
  • сигнал pre_save,
    который срабатывает перед сохранением объекта
  • сигнал post_save,
    который срабатывает после сохранения объекта
  • сигнал m2m_changed,
    который срабатывает при изменении связи ManyToMany