Python入門 Djangoの使い方(その12)マイグレーションする。
はじめに
不定期連載のPython入門第26回目です。今回もPythonのWebフレームワーク「Django」を使っていきましょう。PythonのWebフレームワークといえばFlaskやPyramidなどもありますが、最も使われているのはDjangoです。今回はDjangoのマイグレーションについて扱っていきたいと思います。
※ところどころ脇道にそれますが、基本的に本家チュートリアルの流れに沿っていきたいと思います。
今回の内容:
- マイグレーションする。
- マイグレーションの結果を確認する。
- モデルを編集し、マイグレーションを追加する。
前提条件:
- Pythonバージョン:Python3
- Djangoバージョン:Django1.10.5
- 実行環境:Windows8
- ツール:VisualStudioCommunity2015
マイグレーションする。
前回作成したマイグレーションファイルを実行するところから始めましょう。実行するときには、以下のコマンドを流します。
python manage.py migrate
実行した結果、以下の様に表示されました。
Operations to perform: Apply all migrations: admin, auth, contenttypes, myApp1, sessions Running migrations: Applying myApp1.0001_initial... OK
特に引数を指定しないと、INSTALLED_APPSに指定された全てのアプリケーションのマイグレーションが実行されます。
「myApp1」だけマイグレートしたいときには
python manage.py migrate myApp1
とすればよさそうです。
Applying myApp1.0001_initial... OK
上記の記述のようにマイグレーションが成功しました。
マイグレーションの結果を確認する。
それでは、DBをのぞいてみましょう。
うまくいっていれば、モデルクラスに定義した形のテーブルが生成されているはずです。
今回DBはSQLiteを使っており、DBファイルはプロジェクト内の「db.sqlite3」ファイルになります。
sqlite3 db.sqlite3 sqlite> .tables auth_group django_content_type auth_group_permissions django_migrations auth_permission django_session auth_user django_site auth_user_groups myApp1_section auth_user_user_permissions myApp1_user django_admin_log
myApp1_sectionとmyApp1_userが生成されていますね。
myApp1_userテーブルの構造を確認してみましょう。
sqlite> .schema myApp1_user CREATE TABLE IF NOT EXISTS "myApp1_user" ("id" integer NOT NULL PRIMARY KEY AUTO INCREMENT, "name" varchar(100) NOT NULL, "section_id" integer NOT NULL REFERENCE S "myApp1_section" ("id"), "age" integer NOT NULL); CREATE INDEX "myApp1_user_section_id_07db7e4b" ON "myApp1_user" ("section_id");
モデルで指定した内容でテーブルが生成されていることが分かります。
(インデックスも張られていますね)
モデルを編集し、マイグレーションを追加する。
では次にモデルを編集したときにマイグレーションがどのようになるかを確認してみましょう。
myApp1のmodels.pyを以下の様に修正します。
・Userクラスにアドレス属性を追加(address = models.CharField(max_length=50))
・Sectionクラスからreg_date属性を削除
マイグレーションを作成してみましょう。
python manage.py makemigrations myApp1
以下の様に表示されました。
You are trying to add a non-nullable field 'address' to user without a default; we can't do that (the database needs something to populate existing rows). Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Quit, and let me add a default in models.py Select an option:
NULL許可していないフィールドを追加しようとすると既存行の値をどうすればよいかわからないので、
1か2のいずれかを選択して対処する必要があるようです。
1は今回のみデフォルト値を決めて実行する、2はモデルにデフォルト値を定義する、という意味です。
今回は既存行はないのであまり関係なさそうですが、1を選択してデフォルト値を決めていきます。
Select an option: 1 Please enter the default value now, as valid Python The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now Type 'exit' to exit this prompt >>>
デフォルト値を入力すればよいので、仮に「japan」とします。
>>> 'japan' Migrations for 'myApp1': myApp1\migrations\0002_auto_20170629_1141.py - Remove field reg_date from section - Add field address to user
「0002」番で、編集用マイグレーションファイルが生成できました。
どのようなSQLになるかを見ておきましょう。
python manage.py sqlmigrate myApp1 0002
以下の様なSQLになっていました。
BEGIN; -- -- Remove field reg_date from section -- ALTER TABLE "myApp1_section" RENAME TO "myApp1_section__old"; CREATE TABLE "myApp1_section" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(50) NOT NULL); INSERT INTO "myApp1_section" ("id", "name") SELECT "id", "name" FROM "myApp1_sec tion__old"; DROP TABLE "myApp1_section__old"; -- -- Add field address to user -- ALTER TABLE "myApp1_user" RENAME TO "myApp1_user__old"; CREATE TABLE "myApp1_user" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "ad dress" varchar(50) NOT NULL, "name" varchar(100) NOT NULL, "age" integer NOT NUL L, "section_id" integer NOT NULL REFERENCES "myApp1_section" ("id")); INSERT INTO "myApp1_user" ("id", "name", "age", "section_id", "address") SELECT "id", "name", "age", "section_id", 'japan' FROM "myApp1_user__old"; DROP TABLE "myApp1_user__old"; CREATE INDEX "myApp1_user_section_id_07db7e4b" ON "myApp1_user" ("section_id"); COMMIT;
テーブルを一旦別名にしてからカラムを新しくしたテーブルを新たに生成し、
その後レコードを全てinsertして移行するという段取りを踏んでいることが分かります。
デフォルト値も「japan」になっていますね。
それではmigrationを実行しましょう。
python manage.py migrate myApp1 Operations to perform: Apply all migrations: myApp1 Running migrations: Applying myApp1.0002_auto_20170629_1141... OK
テーブルを見てみるとaddressカラムが追加されていることが分かりました。
sqlite> .schema myApp1_user CREATE TABLE IF NOT EXISTS "myApp1_user" ("id" integer NOT NULL PRIMARY KEY AUTO INCREMENT, "name" varchar(100) NOT NULL, "age" integer NOT NULL, "section_id" in teger NOT NULL REFERENCES "myApp1_section" ("id"), "address" varchar(50) NOT NUL L); CREATE INDEX "myApp1_user_section_id_07db7e4b" ON "myApp1_user" ("section_id");
モデルを変えるとマイグレーションファイルは新たに生成でき、
それを実行することで変更内容をテーブル構造に反映できることがわかりました。
まとめ
マイグレーションを使うと、SQLを書かずにテーブルを操作することができました。
テーブルの変更も簡単ですし、変更内容はマイグレーションファイルで履歴管理されるので便利ですね。