Djangoのモデル実装を効率化する方法

スポンサードリンク



こんにちは。sinyです。

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

抽象基底クラスを使って共通フィールドを定義

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

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

抽象基底クラスを定義する

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

django-admin startproject config .
python manage.py startapp app

 

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

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

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'

 

 

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

├─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を作成しておきます。

├─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に以下のコードを定義します。

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を承継して定義します。

class TimeStampedModel(models.Model):

 

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

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

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

class Meta:
    abstract = True

 

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

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

app/models.py

from django.db import models
from core.models import TimeStampedModel


class Flavor(TimeStampedModel):

    title = models.CharField(max_length=200)

 

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

from core.models import TimeStampedModel

 

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

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

以上の設定でマイグレーションを実行すると、以下のようなSQL(sqliteのケース)で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);

 

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

 

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

マイグレーション

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

python manage.py makemigrations
Migrations for 'app':
  app\migrations\0001_initial.py
    - Create model Flavor

 

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 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文も確認してみます。

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を以下の通り設定しておきます。

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 manage.py createsuperuser

 

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

python manage.py runserver

 

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

 

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

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

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

 

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

おすすめの記事