Django マイグレーション入門

前稿でDjangoのモデル入門ということでmodels.py の開発について取り扱いました。Pythonプログラムとしてはそれで十分なのですが、実際にはDBを使ってプログラムは動作します。つまり作ったモデルをDBに反映する必要が生じます。

これに関する作業をマイグレーションといっています。

一般的な言葉としては、マイグレーションとは移行、移転、移住、移動、乗換などの意味を持っていて、特に ITの分野では、ソフトウェアやシステム、データなどを別の環境に移転したり、新しい環境に切り替えたりすることを意味すると思います。

個人的な感覚としては、プログラムのモデルを変更したらそれをDBに反映するのをマイグレーションというのは、作業が小さくて言葉が大げさな気もしますが・・・このあたり誰が言い始めたのか知りませんが、他の言語やフレームワークでも使われている用語なので、なじみのある方はスルーで、なじみのない方はこの際慣れてしまいましょう。

makemigration

モデルをプログラミングしたあと、makemigrations コマンドを実行するとDBへ反映するための準備(ファイル生成)が行われます。次に実行するのはmigrate コマンドで、そのタイミングでDBのオブジェクトが作成されたり変更されたりします。

python manage.py makemigrations app

makemigrationsの後に指定するのはアプリケーション名です。アプリケーション名を指定するとそのアプリケーションのモデルと依存関係のある範囲に限定されます。もちろん指定無しでも動作します。その場合の動作はすべてのアプリケーションということになります。

ここで全てのアプリケーションという言葉はある意味カギで、はじめてmakemigrations を実行した場合にアプリケーションをsettings.pyのINSTALLED_APPS に追加し忘れていると以下のようにエラーとなります。

>>省略<<
RuntimeError: Model class app.models.Company doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.

ご丁寧にINSTALLED_APPSのことを教えてくれてます。(ってことは、設定値の意味を逆にして除外リストを設定値として、通常は自動的に追加したことにしてくれれば良いのに…って思いますが、Djangoに限らずPythonやっていると感じるモヤッと感ですね)

ともかく、初めてのmakemigrations で問題が発生したら疑ってみる、というか慣れてない頃は高確率で発生するミスだと思います。

さて実際に実行した時に生成されているファイルですが、以下のようにアプリケーションのディレクトリ配下にmigrationsディレクトリが出来て、そこにPythonファイルが追加されていきます。以下の場合、全部で4回makemigrations が実行されたことになります。

サンプルのとして1つ0002_company_change_count.pyファイルをのぞいてみましょう。

# Generated by Django 3.1.2 on 2020-11-01 06:03

from django.db import migrations, models


class Migration(migrations.Migration):

    dependencies = [
        ('app', '0001_initial'),
    ]

    operations = [
        migrations.AddField(
            model_name='company',
            name='change_count',
            field=models.IntegerField(null=True, verbose_name='変更回数'),
        ),
    ]

大体の場合は、コマンドにより自動生成されて、つぎのコマンドによって消化される中間ファイルとも表現できるファイルなので、ただちに詳しく内容を理解する必要もありませんし自分で書けるようになる必要もありません。ただ、希に対応を迫られる変更を自分が命じてしまうことがあるため、少しは学んでおいた方が良いでしょう。

ただ、ぼーっと眺めてみる程度でも、0001_initialが前提になっていて、company テーブルにchange_count カラムが追加されようとしている事くらいは見て取れると思います。ひとまずはそれで十分かと思います。

次のmigrate コマンドが、この指示に従ってDB上の構成を変更するということになります。

migrate

続いてマイグレーション処理の本命、migrateコマンドです。このコマンドは先ほどの説明の通り、makemigrationsコマンドで生成されたファイルを用いて実際にDBへ反映する作業を担います。

実際に実行する前に1つだけ知っておくべき事として、今回題材にしているMySQLに対するmigrate 処理ではトランザクションがサポートされていません。まぁDDLに対してトランザクションなんて発想が合って良いのかどうか分かりませんが、もしも処理中にエラーが発生したとしても、処理実行前の姿に自然に戻っているなんてありがたいことは起こりません。いくつかカラムを追加した後でエラーになったのであれば、それらのカラムは中途半端に追加されっぱなしと言うことです。そのため、あらかじめそのための考慮(バックアップなのかスナップショットなのか、一からやり直しなのか)が必要ということです。

python manage.py migrate app

ここでもアプリケーション名を指定しますが、同様に指定しなくても実行は可能です。また、アプリケーションを指定したとしても、依存関係によっては他のアプリのマイグレーションも合わせて実施されます。

その他settings.pyの説明のところでdefault以外のDB設定も可能である旨記載していると思いますが、 別のDB定義に対して働きかけたい場合には--databaseオプションを使用します。

さて、このmigrate コマンドですが、前に進むだけが能ではありません。しまったあのときに戻り合い、というときにも使用します。

そんなとき、次のshowmigrations コマンドを使って、マイグレーションのリストを表示することが出来ます。

showmigrations

すでにmakemigrations のところで生成されたファイルを見てしまっているので、実際にはそこ見れば良いんでしょ感があるかと思いますが、一応ご紹介しておきます。

python manage.py showmigrations app

これも同じくアプリケーション名を指定することが出来ますが、指定しないと全てのマイグレーションのリストが取得できます。指定無しで実行するとDjango自身もこの仕組みを使っているんだなと感じることが出来ると思います。

admin
[X] 0001_initial
[X] 0002_logentry_remove_auto_add
[X] 0003_logentry_add_action_flag_choices
app
[X] 0001_initial
[X] 0002_company_change_count
[X] 0003_auto_20201101_1939
[X] 0004_auto_20201101_2034
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
[X] 0010_alter_group_name_max_length
[X] 0011_update_proxy_permissions
[X] 0012_alter_user_first_name_max_length
contenttypes
[X] 0001_initial
[X] 0002_remove_content_type_name
sessions
[X] 0001_initial
(LearningDjango) MacBookPro:houjin $

あの頃は良かったというのがあるのであれば、例えばappを0002_company_change_countの時代に戻したいとして、以下のように実行します。

python manage.py migrate app 0002_company_change_count

これで古き良き時代に戻ることが出来ます。当たり前ですが一度失われたものは元には戻らないので、過去にカラムAがあり値が入っていた物を後にカラムAを削除し、カラムAがあった頃に戻ると、当時のデータも戻っているなんて事はありません。

なお、最後のマイグレーション名(0002_company_change_count)の接頭辞(0002)の部分でユニークに特定できる場合には、全て指定せず接頭辞(0002)を指定することでも動作します。でもそれって例えば2と3を間違うと嫌なことが起こるので、やはり全部指定した方が個人的には安全、安心かなと思います。

モデルを変更する場合の注意

モデルを変更する場合に、null=Falseなカラムをdefault指定無しで追加してしまうと、すでに存在するレコードに対する扱いにmakemigrationsコマンドが困って、以下のようにユーザーに対応を迫ってきます。

You are trying to change the nullable field 'name' on company to non-nullable 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) Ignore for now, and let me handle existing rows with NULL myself (e.g. because you added a RunPython or RunSQL operation to handle NULL values in a previous data migration)
3) Quit, and let me add a default in models.py
Select an option:

まず1つ目の選択肢は、今このタイミングでデフォルト値を指定することです。2つ目は今この時点においては無視して先に進んでくれというもので、3つ目の選択肢は models.pyにおいて考え直しますというものです。

ここまではそれほど問題にはならず、ああ言われてみればそうだね、今まで無かったから空で、今後出来るデータには何かデータを入れさせたいという希望を叶えてくれます。

問題は、uniq=True, null=False な場合です。デフォルト値の類いで埋めようとしたとき、その値はレコード毎にユニークであるべしという指定に背いてしまうからです。

そうなると、migrations ファイルを編集する必要が生じます。そのあたりは以下のURLに説明があります。これはマイグレーション入門の範囲なのか?という話もあるので、本稿では記載を割愛します。

https://docs.djangoproject.com/en/3.1/howto/writing-migrations/#migrations-that-add-unique-fields

シリーズの案内

 親記事 Hello Django
 前の記事 Django モデル入門(models.py)
 次の記事 未定