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
でマイグレーションの状態を確認