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

Зачем нужен атрибут on_delete?

1.0 Junior🔥 221 комментариев
#Django

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

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

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

Зачем нужен атрибут on_delete?

on_delete — это критический параметр в Django ORM, который определяет, что должно произойти с объектом, если удаляется связанный с ним объект (через ForeignKey или OneToOneField). Это не просто синтаксис — это важный архитектурный выбор, влияющий на целостность данных.

Основная проблема

Представьте ситуацию:

Автор                           Статья
┌──────────────────┐            ┌──────────────────┐
│ id: 1            │ ──────────>│ id: 1            │
│ name: "John"     │ ForeignKey │ title: "Python"  │
│                  │            │ author_id: 1     │
└──────────────────┘            └──────────────────┘

Что произойдёт, если удалить автора 1?
- Статья остаётся с author_id = 1, но такого автора больше нет
- Ссылка нарушена (orphaned record)
- Целостность данных нарушена

Атрибут on_delete решает эту проблему, определяя поведение.

Основные значения on_delete

1. CASCADE — каскадное удаление

Если удаляется родитель, удаляются все дети:

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField()

class Article(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(
        Author,
        on_delete=models.CASCADE  # При удалении автора удаляются все его статьи
    )
    created_at = models.DateTimeField(auto_now_add=True)

# Использование
author = Author.objects.create(name="John", email="john@example.com")
Article.objects.create(title="Python Tips", author=author)
Article.objects.create(title="Django ORM", author=author)

# При удалении автора
author.delete()
# DELETE FROM articles WHERE author_id = 1
# DELETE FROM authors WHERE id = 1
# Все статьи этого автора также удалены

Когда использовать CASCADE:

  • Связь хозяин-подчинённый (Author → Articles)
  • Логическое удаление
  • Дочерние данные не имеют смысла без родителя

2. SET_NULL — установить NULL

При удалении родителя внешний ключ становится NULL:

class Article(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(
        Author,
        on_delete=models.SET_NULL,  # Автор удалится, статья остаётся
        null=True,
        blank=True
    )

# Использование
author = Author.objects.create(name="John", email="john@example.com")
article = Article.objects.create(title="Python Tips", author=author)

print(article.author)  # <Author: John>

author.delete()

# Перезагружаем статью
article.refresh_from_db()
print(article.author)  # None
print(article.title)  # "Python Tips" (статья осталась!)

Когда использовать SET_NULL:

  • Статья может существовать без автора
  • Хотим сохранить исторические данные
  • Мягкое удаление связей

3. SET_DEFAULT — установить значение по умолчанию

При удалении родителя используется значение по умолчанию:

class Article(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(
        Author,
        on_delete=models.SET_DEFAULT,
        default=1  # ID архивного/системного автора
    )

# Использование
archive_author = Author.objects.create(name="Archive", email="archive@example.com")

author = Author.objects.create(name="John", email="john@example.com")
article = Article.objects.create(title="Python Tips", author=author)

print(article.author_id)  # 2 (John)

author.delete()
article.refresh_from_db()

print(article.author_id)  # 1 (Archive)

Когда использовать SET_DEFAULT:

  • Заново назначить родителя системному/архивному объекту
  • Сохранить историю, но нейтрализовать ссылку
  • Fallback для удалённых данных

4. PROTECT — запретить удаление

Если у родителя есть дети, удаление запрещено:

from django.db import models
from django.core.exceptions import ProtectedError

class Category(models.Model):
    name = models.CharField(max_length=100)

class Product(models.Model):
    name = models.CharField(max_length=200)
    category = models.ForeignKey(
        Category,
        on_delete=models.PROTECT  # Защита от удаления категории с товарами
    )

# Использование
category = Category.objects.create(name="Electronics")
Product.objects.create(name="Laptop", category=category)

try:
    category.delete()  # Попытка удалить категорию
except ProtectedError:
    print("Cannot delete category with products")  # Ошибка!

# Сначала удаляем товары
Product.objects.filter(category=category).delete()
category.delete()  # Теперь успешно

Когда использовать PROTECT:

  • Важные справочники (категории, типы)
  • Предотвращение ошибочных удалений
  • Явное требование удалить зависимые данные

5. SET() — установить функцию/значение

При удалении использовать функцию для определения значения:

from django.db import models

def get_default_manager():
    return User.objects.get(username='default_manager')

class Task(models.Model):
    title = models.CharField(max_length=200)
    assigned_to = models.ForeignKey(
        User,
        on_delete=models.SET(get_default_manager)  # Переназначить на менеджера
    )

# Использование
default = User.objects.create(username='default_manager')
assignee = User.objects.create(username='john')

task = Task.objects.create(title="Fix bug", assigned_to=assignee)
print(task.assigned_to.username)  # "john"

assignee.delete()
task.refresh_from_db()

print(task.assigned_to.username)  # "default_manager"

Когда использовать SET():

  • Динамические значения по умолчанию
  • Функции для определения fallback'а

6. DO_NOTHING — ничего не делать

Делегировать обработку БД (опасно!):

class Article(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(
        Author,
        on_delete=models.DO_NOTHING  # БД сама решает
    )

# Опасно! Может привести к orphaned records и нарушению целостности

НЕ рекомендуется использовать DO_NOTHING (кроме очень специальных случаев).

Практический пример: E-commerce система

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

class Supplier(models.Model):
    """Поставщик"""
    name = models.CharField(max_length=100)
    email = models.EmailField()

class Category(models.Model):
    """Категория товаров"""
    name = models.CharField(max_length=100)

class Product(models.Model):
    """Товар"""
    name = models.CharField(max_length=200)
    category = models.ForeignKey(
        Category,
        on_delete=models.PROTECT,  # Защита: нельзя удалить категорию с товарами
        related_name='products'
    )
    supplier = models.ForeignKey(
        Supplier,
        on_delete=models.SET_NULL,  # Товар может быть без поставщика
        null=True,
        blank=True,
        related_name='products'
    )
    price = models.DecimalField(max_digits=10, decimal_places=2)

class Order(models.Model):
    """Заказ"""
    created_at = models.DateTimeField(auto_now_add=True)
    customer_email = models.EmailField()

class OrderItem(models.Model):
    """Позиция в заказе"""
    order = models.ForeignKey(
        Order,
        on_delete=models.CASCADE,  # При удалении заказа удаляются все позиции
        related_name='items'
    )
    product = models.ForeignKey(
        Product,
        on_delete=models.PROTECT,  # Защита: нельзя удалить товар из каталога если он в заказах
    )
    quantity = models.IntegerField()
    price = models.DecimalField(max_digits=10, decimal_places=2)

class Review(models.Model):
    """Отзыв"""
    product = models.ForeignKey(
        Product,
        on_delete=models.CASCADE,  # При удалении товара удаляются отзывы
        related_name='reviews'
    )
    author_email = models.EmailField()
    text = models.TextField()
    rating = models.IntegerField()

# Использование
print("=== Примеры поведения on_delete ===")

# 1. CASCADE
order = Order.objects.create(customer_email="user@example.com")
product = Product.objects.create(name="Laptop", price=999.99, category=category)
OrderItem.objects.create(order=order, product=product, quantity=1, price=999.99)

print(f"Order items before delete: {order.items.count()}")  # 1
order.delete()
print(f"Order items after delete: {OrderItem.objects.filter(order=order).count()}")  # 0 (deleted!)

# 2. SET_NULL
review = Review.objects.create(product=product, author_email="user@example.com", text="Great!", rating=5)
print(f"Product before delete: {review.product}")  # <Product: Laptop>
product.delete()  # PROTECT error if there are OrderItems
# Но если нет OrderItems...
product = Product.objects.create(name="Mouse", price=29.99, category=category)
review.product = product
review.save()
product.delete()
review.refresh_from_db()
print(f"Product after delete: {review.product}")  # None

# 3. PROTECT
try:
    category.delete()  # Попытка удалить категорию
except models.ProtectedError:
    print("Cannot delete category with products")

Сравнение вариантов

on_deleteПоведениеЦелостностьДанныеИспользование
CASCADEУдалить детейСохраненаПотеря данныхТесная связь (Author→Articles)
SET_NULLNULLСохраненаСохранениеМягкие связи (Author→Articles)
SET_DEFAULTЗначениеСохраненаСохранениеFallback объекты
PROTECTОшибкаЗащищенаСохранениеВажные справочники
SET()ФункцияСохраненаСохранениеДинамические fallback'и
DO_NOTHINGOrphanedРискРискСпециальные случаи

Лучшие практики

Всегда явно указывайте on_delete (это обязательно в Django 2.0+) ✓ CASCADE для логических иерархий (Автор → Статьи) ✓ SET_NULL для мягких связей (Комментарий → Удаляемый пользователь) ✓ PROTECT для справочников (Категория, Тип) ✓ Документируйте причину выбора в коментариях ✗ DO_NOTHING только в исключительных случаяхНе игнорируйте выбор (это не просто техническое требование)

class Article(models.Model):
    title = models.CharField(max_length=200)
    # Причина CASCADE: Статья без автора не имеет смысла,
    # это внутренний контент, тесно связанный с автором
    author = models.ForeignKey(
        Author,
        on_delete=models.CASCADE,
        help_text="Автор статьи. При удалении автора статья удаляется."
    )

on_delete — это не просто параметр, это архитектурное решение, влияющее на целостность данных вашего приложения. Правильный выбор критичен для надежной системы.

Зачем нужен атрибут on_delete? | PrepBro