Python入門 Djangoの使い方(その12)マイグレーションする。


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を書かずにテーブルを操作することができました。
テーブルの変更も簡単ですし、変更内容はマイグレーションファイルで履歴管理されるので便利ですね。

  • このエントリーをはてなブックマークに追加

PAGE TOP