Djangoメモ(4) : 掲示板アプリのモデルを作成
A Complete Beginner's Guide to Djangoのチュートリアルを参考に掲示板アプリのモデルを作成する。
掲示板アプリのモデル
チュートリアルでは下の図のようなモデルを作成する。

それぞれのモデルの説明をまとめると以下の通り。
Board
- Boardはカテゴリのようなもの(例:Pythonに関するTopicを投稿するBoardなど)
- 複数のBoardがある
- 管理者のみがBoardを作成できる
Topic
- TopicはBoard内のスレッドようなもの
- TopicはBoardに属する
- Topicは一般ユーザが作成できる
Post
- PostはTopicに対する返信
- PostはTopicに属する
- PostはTopicを作成したユーザ以外でも返信できる
User
- Django built-inのユーザモデル
- Topic, Postがどのユーザによるものか対応付ける
モデルの作成
モデルを作成するためboards/models.pyをチュートリアル通りに編集する。
from django.db import models from django.contrib.auth.models import User class Board(models.Model): name = models.CharField(max_length=30, unique=True) description = models.CharField(max_length=100) class Topic(models.Model): subject = models.CharField(max_length=255) last_updated = models.DateTimeField(auto_now_add=True) board = models.ForeignKey(Board, related_name='topics') starter = models.ForeignKey(User, related_name='topics') class Post(models.Model): message = models.TextField(max_length=4000) topic = models.ForeignKey(Topic, related_name='posts') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(null=True) created_by = models.ForeignKey(User, related_name='posts') updated_by = models.ForeignKey(User, null=True, related_name='+')
コードについてのおおまかな説明は以下の通り。
- 各モデルは一つのクラスで表現される。
- 各フィールドは Field クラスのインスタンスとして表現される。
- CharField は文字のフィールド、 DateTimeField は日時フィー ルド(フィールド一覧)。
- インスタンス名はデータベースの列の名前として使用する。
auto_now_add=Trueはインスタンス作成時に現在時刻を登録する。related_nameはbackwards relationに名前をつける(詳細はこのブログを参照)。related_name='+'はbackwards relationを作成しない。- Django built-inのユーザモデルを使用するためUserは記述しない。
migrateでモデルを有効にする
まずはモデルに変更があったことを伝えるためにmakemigrationsを実行したがアプリケーションが見つからないというメッセージが表示されてしまった。
$ python manage.py makemigrations boards App 'boards' could not be found. Is it in INSTALLED_APPS?
アプリケーションをプロジェクトに含めるには構成クラスへの参照を設定に追加する必要があるためmyproject/settings.pyのINSTALLED_APPSにboards.apps.BoardsConfigを追加する(BoardsConfigクラスはboards/apps.pyにある)。
# Application definition INSTALLED_APPS = [ 'boards.apps.BoardsConfig', # 追加分 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ]
編集後、再実行したら下記エラーが表示された。
$ python manage.py makemigrations boards
Traceback (most recent call last):
File "manage.py", line 15, in <module>
execute_from_command_line(sys.argv)
File "/home/vagrant/.pyenv/versions/venv/lib/python3.6/site-packages/django/core/management/__init__.py", line 371, in execute_from_command_line
utility.execute()
File "/home/vagrant/.pyenv/versions/venv/lib/python3.6/site-packages/django/core/management/__init__.py", line 347, in execute
django.setup()
File "/home/vagrant/.pyenv/versions/venv/lib/python3.6/site-packages/django/__init__.py", line 24, in setup
apps.populate(settings.INSTALLED_APPS)
File "/home/vagrant/.pyenv/versions/venv/lib/python3.6/site-packages/django/apps/registry.py", line 112, in populate
app_config.import_models()
File "/home/vagrant/.pyenv/versions/venv/lib/python3.6/site-packages/django/apps/config.py", line 198, in import_models
self.models_module = import_module(models_module_name)
File "/home/vagrant/.pyenv/versions/venv/lib/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 978, in _gcd_import
File "<frozen importlib._bootstrap>", line 961, in _find_and_load
File "<frozen importlib._bootstrap>", line 950, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 655, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 678, in exec_module
File "<frozen importlib._bootstrap>", line 205, in _call_with_frames_removed
File "/home/vagrant/django/myproject/myproject/boards/models.py", line 10, in <module>
class Topic(models.Model):
File "/home/vagrant/django/myproject/myproject/boards/models.py", line 13, in Topic
board = models.ForeignKey(Board, related_name='topics')
TypeError: __init__() missing 1 required positional argument: 'on_delete'
調べたところDjango 2.0からはForeignKeyのon_deleteが必須になったのでmodels.pyを以下のように編集し直す。
on_delete=models.CASCADEは削除時に関連する要素も削除する設定。ドキュメントによるとその他の値としてはPROTECT, SET_NULLなどがあるようだ。
from django.db import models from django.contrib.auth.models import User class Board(models.Model): name = models.CharField(max_length=30, unique=True) description = models.CharField(max_length=100) class Topic(models.Model): subject = models.CharField(max_length=255) last_updated = models.DateTimeField(auto_now_add=True) board = models.ForeignKey(Board, on_delete=models.CASCADE, related_name='topics') starter = models.ForeignKey(User, on_delete=models.CASCADE, related_name='topics') class Post(models.Model): message = models.TextField(max_length=4000) topic = models.ForeignKey(Topic, on_delete=models.CASCADE, related_name='posts') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(null=True) created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts') updated_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='+')
makemigrationsを実行したところ今度は無事に成功。
boards/migrations/0001_initial.pyというファイルが作成される。このファイルは手動で微調整したいというとき以外は中身は気にしなくてよいとのこと。
$ python manage.py makemigrations boards
Migrations for 'boards':
boards/migrations/0001_initial.py
- Create model Board
- Create model Post
- Create model Topic
- Add field topic to post
- Add field updated_by to post
実際にデータベース(デフォルトではSQLite)に反映する前にsqlmigrateでどのようなSQLが実行されるかを確認してみる。
$ python manage.py sqlmigrate boards 0001 BEGIN; -- -- Create model Board -- CREATE TABLE "boards_board" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(30) NOT NULL UNIQUE, "description" varchar(100) NO T NULL); 省略 CREATE INDEX "boards_post_created_by_id_0b841038" ON "boards_post" ("created_by_id"); CREATE INDEX "boards_post_topic_id_f477c024" ON "boards_post" ("topic_id"); CREATE INDEX "boards_post_updated_by_id_76d3c48f" ON "boards_post" ("updated_by_id"); COMMIT;
migrateコマンドでデータベースに反映する。
$ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, boards, 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 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 boards.0001_initial... OK Applying sessions.0001_initial... OK
これでモデルの作成は完了。 boards以外のauth, adminなどはよく使われるためデフォルトで付属しているアプリケーション。
sqlite3でテーブルが作成されていることも確認できる。
$ sqlite3 db.sqlite3 SQLite version 3.7.17 2013-05-20 00:56:22 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> .tables auth_group boards_post auth_group_permissions boards_topic auth_permission django_admin_log auth_user django_content_type auth_user_groups django_migrations auth_user_user_permissions django_session boards_board sqlite> .schema boards_board CREATE TABLE "boards_board" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(30) NOT NULL UNIQUE, "description" varchar(100) NOT NULL); sqlite> .schema boards_post CREATE TABLE "boards_post" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "message" text NOT NULL, "created_at" datetime NOT NULL, "updated_at" datetime NULL, "created_by_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED, "topic_id" integer NOT NULL REFERENCES "boards_topic" ("id") DEFERRABLE INITIALLY DEFERRED, "updated_by_id" integer NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED); CREATE INDEX "boards_post_created_by_id_0b841038" ON "boards_post" ("created_by_id"); CREATE INDEX "boards_post_topic_id_f477c024" ON "boards_post" ("topic_id"); CREATE INDEX "boards_post_updated_by_id_76d3c48f" ON "boards_post" ("updated_by_id"); sqlite> .schema boards_topic CREATE TABLE "boards_topic" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "subject" varchar(255) NOT NULL, "last_updated" datetime NOT NULL, "board_id" integer NOT NULL REFERENCES "boards_board" ("id") DEFERRABLE INITIALLY DEFERRED, "starter_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED); CREATE INDEX "boards_topic_board_id_4462fe82" ON "boards_topic" ("board_id"); CREATE INDEX "boards_topic_starter_id_60e98681" ON "boards_topic" ("starter_id");
また、showmigrationsを実行すると現在のマイグレーションの状態が確認できる。
$ python manage.py showmigrations admin [X] 0001_initial [X] 0002_logentry_remove_auto_add auth [X] 0001_initial [X] 0002_alter_permission_name_max_length [X] 0003_alter_user_email_max_length [X] 0004_alter_user_username_opts [X] 0005_alter_user_last_login_null [X] 0006_require_contenttypes_0002 [X] 0007_alter_validators_add_error_messages [X] 0008_alter_user_username_max_length [X] 0009_alter_user_last_name_max_length boards [X] 0001_initial contenttypes [X] 0001_initial [X] 0002_remove_content_type_name sessions [X] 0001_initial
まとめ
- 掲示板アプリのモデルを作成
INSTALLED_APPSでプロジェクトにアプリケーションを追加- Django 2.0からは
ForeignKeyのon_deleteが必須 makemigrationsで変更をファイル形式で保存sqlmigrateで実行されるSQLを確認migrateでデータベースにテーブルを作成showmigrationsでマイグレーションの状態を確認