Django ORMのdelete入門
データを削除する方法
Django ORMでデータベースのレコードを削除する方法を解説します。単一削除から一括削除、論理削除まで紹介します。
こんな人向けの記事です
- Django ORMでデータの削除方法を学びたい人
- 関連データの削除動作(CASCADE等)を理解したい人
- 論理削除の実装方法を知りたい人
Step 1delete()で1件削除
モデルインスタンスのdelete()メソッドを呼ぶと、そのレコードがデータベースから削除されます。
Python
# models.py
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.IntegerField()
is_active = models.BooleanField(default=True)
def __str__(self):
return self.name
class Order(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.IntegerField()
ordered_at = models.DateTimeField(auto_now_add=True)Python
# 1件取得して削除
product = Product.objects.get(id=1)
print(f"削除対象: {product.name}")
result = product.delete()
print(result)実行結果
削除対象: りんご
(3, {'app.Order': 2, 'app.Product': 1})delete()の戻り値はタプルで、削除された総件数と、モデルごとの削除件数の辞書が返されます。上の例では、Product1件とそれに紐づくOrder2件の合計3件が削除されています。
Step 2一括削除
QuerySetのdelete()を使うと、条件に一致する複数のレコードを一括削除できます。
Python
# 非アクティブな商品を一括削除
deleted_count, details = Product.objects.filter(is_active=False).delete()
print(f"{deleted_count}件削除しました")
print(details)
# 価格が0の商品を一括削除
Product.objects.filter(price=0).delete()
# 特定の日時より古い注文を削除
from datetime import datetime, timedelta
threshold = datetime.now() - timedelta(days=365)
Order.objects.filter(ordered_at__lt=threshold).delete()実行結果
5件削除しました
{'app.Order': 3, 'app.Product': 2}全件削除に注意
Product.objects.all().delete()で全レコードが削除されます。実行前にfilter()の条件が正しいか、count()で件数を確認してから削除しましょう。Step 3関連データの削除動作
ForeignKeyのon_delete引数で、親レコード削除時の関連データの動作を指定できます。
Python
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100)
class Product(models.Model):
name = models.CharField(max_length=200)
# CASCADE: 親削除時に子も削除(デフォルト)
category = models.ForeignKey(
Category, on_delete=models.CASCADE
)
class Review(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
comment = models.TextField()
# PROTECT: 子が存在する場合、親の削除を禁止
# on_delete=models.PROTECT
# SET_NULL: 親削除時にNULLをセット(null=True必須)
# on_delete=models.SET_NULL, null=True
# SET_DEFAULT: 親削除時にデフォルト値をセット
# on_delete=models.SET_DEFAULT, default=1
# DO_NOTHING: 何もしない(外部キー制約違反の可能性あり)
# on_delete=models.DO_NOTHINGPython
# CASCADEの動作確認
category = Category.objects.get(id=1) # "食品"カテゴリ
print(f"商品数: {category.product_set.count()}")
# カテゴリを削除すると、紐づく商品とレビューも全て削除される
result = category.delete()
print(result)
# PROTECTの場合はProtectedErrorが発生
from django.db.models import ProtectedError
try:
category.delete()
except ProtectedError:
print("関連データが存在するため削除できません")on_deleteの選択指針
CASCADE: 注文→注文明細のように、親がなければ子も意味がない場合PROTECT: ユーザー→投稿のように、誤削除を防ぎたい場合
SET_NULL: 担当者→タスクのように、親がなくても子は残したい場合
Step 4論理削除の実装
データを物理的に削除せず、フラグで論理的に削除する方法です。データの復元や履歴管理が必要な場合に使います。
Python
# models.py
from django.db import models
from django.utils import timezone
class SoftDeleteManager(models.Manager):
"""論理削除されていないデータのみ返すマネージャー"""
def get_queryset(self):
return super().get_queryset().filter(deleted_at__isnull=True)
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.IntegerField()
deleted_at = models.DateTimeField(null=True, blank=True)
# デフォルトのマネージャー(論理削除を除外)
objects = SoftDeleteManager()
# 全データ取得用のマネージャー
all_objects = models.Manager()
def soft_delete(self):
"""論理削除"""
self.deleted_at = timezone.now()
self.save(update_fields=["deleted_at"])
def restore(self):
"""論理削除の取り消し"""
self.deleted_at = None
self.save(update_fields=["deleted_at"])Python
# 論理削除の実行
product = Product.objects.get(id=1)
product.soft_delete()
# 通常のクエリでは論理削除されたデータは見えない
print(Product.objects.count()) # 論理削除を除外
print(Product.all_objects.count()) # 全データ(論理削除含む)
# 論理削除されたデータを復元
product = Product.all_objects.get(id=1)
product.restore()Step 5削除時の注意点
Python
# 1. 削除前に件数を確認する
queryset = Product.objects.filter(is_active=False)
count = queryset.count()
print(f"削除予定: {count}件")
if count > 0:
confirm = input("本当に削除しますか? (y/n): ")
if confirm == "y":
queryset.delete()
# 2. トランザクションで安全に削除する
from django.db import transaction
try:
with transaction.atomic():
# 関連データを先に削除
Order.objects.filter(product_id=1).delete()
# 親データを削除
Product.objects.filter(id=1).delete()
except Exception as e:
print(f"エラーが発生しました: {e}")
# トランザクション内なので自動ロールバック
# 3. pre_delete / post_deleteシグナルを活用
from django.db.models.signals import pre_delete
from django.dispatch import receiver
@receiver(pre_delete, sender=Product)
def product_pre_delete(sender, instance, **kwargs):
print(f"削除されます: {instance.name}")
# ログ記録やバックアップ処理などまとめ
delete()でインスタンスまたはQuerySetのレコードを削除できるon_deleteで関連データの削除動作を制御する(CASCADE, PROTECT, SET_NULL等)- 論理削除はカスタムマネージャーと
deleted_atフィールドで実装できる - 削除前に
count()で件数を確認し、トランザクションで安全に実行する - 重要なデータは物理削除ではなく論理削除を検討する