【Django】抽象基底クラス(abstract = True)で共通フィールドを DRY 化|モデル継承の実装パターン

スポンサードリンク



Django · Model Inheritance · 抽象基底クラスで DRY

通フィールド(created_atupdated_atis_active 等)を持つモデルが複数あると、毎回同じ定義をコピペすることになりがちです。Django の 抽象基底クラス(abstract = True) を使えば、共通フィールドを 1 つの基底モデルに切り出して継承するだけで済み、コードの重複が一気に消えます。本記事では抽象基底クラスの定義方法、継承時の注意点、Meta クラスの扱いまでを実例付きで解説します。

この記事ではDjangoのモデル実装を効率化する方法を簡単にまとめました。

user@sinyblog:~/article 01_section_1.md抽象基底クラスを使って共通フィールドを定義

例えば、複数のアプリケーション、複数のモデル定義内において作成日時(created)更新日時(modified)のフィールドを共通で持たせたいといった場合に、各テーブルクラス内にcreatedとmodifiedを定義してもよいですが、テーブル数が多いとかなり冗長で長いコードになってしまいます。

そこで、共通するフィールドの定義を抽象基底クラスとして定義し、抽象基底クラスを承継することでモデル定義を簡素化することができます。

user@sinyblog:~/article 02_section_2.md抽象基底クラスを定義する

まず、事前準備としてDjangoプロジェクトとアプリケーションを作成しておきます。

python


django-admin startproject config .

python manage.py startapp app

 

また、settings.pyに作成したアプリケーションを追加しておきます。

言語とタイムゾーンも変更してきます。

default


INSTALLED_APPS = [

    'django.contrib.admin',

    'django.contrib.auth',

    'django.contrib.contenttypes',

    'django.contrib.sessions',

    'django.contrib.messages',

    'django.contrib.staticfiles',

    'app', #add

]



LANGUAGE_CODE = 'ja'



TIME_ZONE = 'Asia/Tokyo'

 

 

フォルダ構成は以下の通り

default


├─app

│  │  admin.py

│  │  apps.py

│  │  models.py

│  │  tests.py

│  │  views.py

│  │  __init__.py

│  │

├─config

│  │  asgi.py

│  │  settings.py

│  │  urls.py

│  │  wsgi.py

│  │  __init__.py

 

抽象基底クラス定義するmodels.pyを格納するためのフォルダ(core)を作成し、その配下にmodels.pyを作成しておきます。

default


├─app

│  │  admin.py

│  │  apps.py

│  │  models.py

│  │  tests.py

│  │  views.py

│  │  __init__.py

│  │

├─config

│  │  asgi.py

│  │  settings.py

│  │  urls.py

│  │  wsgi.py

│  │  __init__.py

├─core   #新規作成

│  │  models.py    #新規作成

 

core/models.pyに以下のコードを定義します。

python


from django.db import models





"""

created,modifiedフィールドを更新する抽象基底クラス

"""



class TimeStampedModel(models.Model):



    created = models.DateTimeField(auto_now_add=True)

    modified = models.DateTimeField(auto_now=True)

    

    class Meta:

        abstract = True

 

抽象基底クラスとなる TimeStampedModelクラス(名称は任意)は通常通りmodels.Modelを承継して定義します。

python


class TimeStampedModel(models.Model):

 

TimeStampedModelクラスで他のクラスで共通的に利用したいフィールドを定義します。

今回はcreated(作成時刻)modified(更新時刻)のフィールドを定義しています。

このままだと普通のモデルクラスですが、最後の2行でこのクラスを抽象基底クラス化しています。

python


class Meta:

    abstract = True

 

abstract = Trueとすることで抽象基底クラスになります。

続いて、アプリケーション(app)直下のmodels.pyに抽象基底クラスを承継したモデルクラステーブルを定義します。

app/models.py

python


from django.db import models

from core.models import TimeStampedModel





class Flavor(TimeStampedModel):



    title = models.CharField(max_length=200)

 

以下のコードで先ほど作成したcore/models.pyのTimeStampedModelクラスをインポートしています。

python


from core.models import TimeStampedModel

 

TimeStampedModelクラスを承継してFlavorクラスを定義し、titleフィールドだけ定義します。

createdmodifiedフィールドは抽象基底クラスを承継しているのでFlavorクラス内で定義する必要がありません。

以上の設定でマイグレーションを実行すると、以下のようなSQL(sqliteのケース)でapp_flavorテーブルが実装されます。

python


CREATE TABLE IF NOT EXISTS "app_flavor" ("id" integer NOT NULL 

PRIMARY KEY AUTOINCREMENT, "created" datetime NOT NULL, 

"modified" datetime NOT NULL, "title" varchar(200) NOT NULL);

 

 なお、抽象基底クラス(TimeStampedModel)を承継したクラスをマイグレーションしてもTimeStampedModelクラスのモデルテーブルは作成されません。

 

 Django 抽象基底クラスと Python スタンダード ライブラリの abc モジュールの抽象基底クラスは別物なので混同しないように注意

user@sinyblog:~/article 03_section_3.mdマイグレーション

それでは実際にマイグレーションしてみましょう。

python


python manage.py makemigrations

Migrations for 'app':

  app\migrations\0001_initial.py

    - Create model Flavor

 

python


python manage.py migrate

Operations to perform:

  Apply all migrations: admin, app, auth, contenttypes, sessions

Running migrations:

  Applying contenttypes.0001_initial... OK

  Applying auth.0001_initial... OK

  Applying admin.0001_initial... OK

  Applying admin.0002_logentry_remove_auto_add... OK

  Applying admin.0003_logentry_add_action_flag_choices... OK

  Applying app.0001_initial... OK

  Applying contenttypes.0002_remove_content_type_name... OK

  Applying auth.0002_alter_permission_name_max_length... OK

  Applying auth.0003_alter_user_email_max_length... OK

  Applying auth.0004_alter_user_username_opts... OK

  Applying auth.0005_alter_user_last_login_null... OK

  Applying auth.0006_require_contenttypes_0002... OK

  Applying auth.0007_alter_validators_add_error_messages... OK

  Applying auth.0008_alter_user_username_max_length... OK

  Applying auth.0009_alter_user_last_name_max_length... OK

  Applying auth.0010_alter_group_name_max_length... OK

  Applying auth.0011_update_proxy_permissions... OK

  Applying sessions.0001_initial... OK

 

SQLITEデータベースにアクセスして登録されたテーブルを確認してみます。

python


python manage.py dbshell

SQLite version 3.28.0 2019-04-16 19:49:53

Enter ".help" for usage hints.

sqlite> .tables





app_flavor                  auth_user_user_permissions

auth_group                  django_admin_log

auth_group_permissions      django_content_type

auth_permission             django_migrations

auth_user                   django_session

auth_user_groups

 

上記の通りapp_flavorテーブルが登録されていることが確認でいます。

実装時のSQL文も確認してみます。

python


sqlite> .schema app_flavor

CREATE TABLE IF NOT EXISTS "app_flavor" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "created" datetime NOT NULL, "modified" datetime NOT NULL, "title" varchar(200) NOT NULL);

sqlite>

 

上記の通り、titileだけでなくcreatedとmodifiedフィールドも実装されていることが確認できます。

ついでにadminサイト上の動作を確認してましょう。

app/admin.pyを以下の通り設定しておきます。

python


from django.contrib import admin



from .models import Flavor



class FlavorAdmin(admin.ModelAdmin):

    list_display=('pk','created','modified','title')





admin.site.register(Flavor, FlavorAdmin)

 

管理者ユーザも作成しておきましょう。

python


python manage.py createsuperuser

 

runserverを実行してadminサイト(http://127.0.0.1:8000/admin)にアクセスします。

python


python manage.py runserver

 

Flavorテーブルにレコードを新規作成しようとすると、以下の通りFlavorクラスで定義したtitileフィールドだけ表示されています。

 

保存ボタンを押すとレコードが登録されます。

すると以下の通り承継先の抽象基底クラス(TimeStampedModel)で定義されているフィールドも自動的に生成されます。

ちなみに抽象基本クラスでないクラスを承継した場合は、承継先のモデルクラスのテーブルもデータベース上に登録されます。

 

以上、「Djangoのモデル実装を効率化する方法」でした。

おすすめの記事