🔥

Django で各テーブルに簡単に論理削除や作成日付を入れたい時

に公開

はじめに

Backendでよくある問題として以下に出くわすことよくあると思います。

  • レコードを削除したけど、後から「やっぱり必要だった...」という状況
  • いつ作られたデータなのか、いつ更新されたのかを確認したい時

そのような時に各々のテーブルにいちいち実装するのは面倒かな、、と思います。
そんな時に便利なのが基底クラスによる、論理削除(ソフトデリート)とタイムスタンプの自動記録です。今回は、これらの機能を簡単に実装する方法を紹介します!

開発環境

  • Python 3.9
  • Django 4.2
  • PostgreSQL 14

ディレクトリ構成

myproject/
├── manage.py
└── myapp/
    ├── __init__.py
    ├── models.py  # ここに基底クラスを定義
    ├── views.py
    └── ...

抽象基底クラスの作成

Djangoには抽象基底クラス(Abstract Base Class)という便利な機能があります。これを使うと、共通のフィールドやメソッドを一度定義するだけで、他のモデルでも簡単に使えるようになります。

まずはこんな感じで実装してみましょう

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

class BaseModel(models.Model):
    created_at = models.DateTimeField('作成日時', auto_now_add=True)
    updated_at = models.DateTimeField('更新日時', auto_now=True)
    is_deleted = models.BooleanField('削除フラグ', default=False)
    deleted_at = models.DateTimeField('削除日時', null=True, blank=True)

    class Meta:
        abstract = True

    def delete(self, using=None, keep_parents=False):
        self.is_deleted = True
        self.deleted_at = timezone.now()
        self.save(update_fields=['is_deleted', 'deleted_at'])

    def hard_delete(self, using=None, keep_parents=False):
        return super().delete(using=using, keep_parents=keep_parents)

使い方は超簡単!

この基底クラスを使うのは本当に簡単です。例えば、ユーザーモデルを作る時はこんな感じにかけます。

class User(BaseModel):
    name = models.CharField('名前', max_length=100)
    email = models.EmailField('メールアドレス')

    class Meta:
        db_table = 'users'

これだけで、以下の機能が全部使えるようになります。

主な機能

  1. 自動でタイムスタンプを記録

    • created_at: レコードを作った時に自動で現在時刻を記録
    • updated_at: レコードを更新した時に自動で現在時刻を更新
  2. 論理削除が簡単に

    • is_deleted: 削除したかどうかのフラグ(デフォルトはFalse)
    • deleted_at: いつ削除したかを記録
    • 普通のdelete()メソッドを使うと論理削除
    • 本当に消したい時はhard_delete()を使う

データの取得方法

論理削除されたデータを除外して取得するのも簡単です。

# 論理削除されていないデータだけ取得
active_users = User.objects.filter(is_deleted=False)

# 全部のデータを取得(削除済みも含む)
all_users = User.objects.all()

もっと便利に使いたい

さらに便利に使うために、querysetのカスタムマネージャーを追加してみましょう。

class BaseModelManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(is_deleted=False)

class BaseModel(models.Model):
    # ... 既存のフィールド定義 ...

    objects = BaseModelManager()
    all_objects = models.Manager()  # 削除済みも含めて取得したい時用

    # ... 既存のメソッド定義 ...

これで、デフォルトで論理削除されていないデータだけを取得できるようになります

# 論理削除されていないデータだけ取得(簡単!)
active_users = User.objects.all()

# 全部のデータを取得(削除済みも含む)
all_users = User.all_objects.all()

つくってみて

抽象基底クラスを使うことで、こんなメリットがあります

  • コードの重複を避けられる
  • 一貫した実装が可能
  • メンテナンスが楽
  • 機能の追加・変更が簡単

これで、Djangoプロジェクト全体で論理削除とタイムスタンプの管理が簡単に楽に実装ができます。
履歴テーブルやBitemporalなテーブルなども応用でできますが、これはまたの機会に書こうかなと思います。

参考リンク

GitHubで編集を提案

Discussion