こんにちは。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フィールドだけ定義します。
createdとmodifiedフィールドは抽象基底クラスを承継しているので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);
マイグレーション
それでは実際にマイグレーションしてみましょう。
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のモデル実装を効率化する方法」でした。