Django マイグレーション まとめ

#この記事について
Djangoのマイグレーション関連の情報を自分なりに整理しました。
バージョンは1.11です。

#データベースマイグレーションとは

アプリケーションで使うデータベースの定義を自動的に作成・管理する機能です。
旧来データベースに接続して直接変更を行っていた作業を、モデルから自動生成されるコードの実行で置き換えます。

・定義変更用のＳＱＬを作成する手間がなくなる。
・データベースがバージョン管理されるので複数人での開発作業がやりやすくなる

といったメリットがあり、過去の手法に比べ作業効率が大幅に上がります。
Djangoでは1.7以降のバージョンで標準的に利用できます。

同様の機能は他のフレームワークにも存在します。
・Ruby on Rails(ActiveRecord)
・.Net Framework (EntityFramework)
・cakephp など。

日本語の文献は他のフレームワークの方が多いです。
基本的な考え方や用語は一緒なので、探して読むと参考になります。

#基本用語

##モデル

model.py内のクラス定義。
具体的にはDjangoで管理したいデータ（ブログ、注文など）のデータ設計。
model.pyの射影をデータベースに作成するのがデータベースマイグレーションの目的です。

##マイグレーションファイル

model.pyの内容をデータベースに反映させるための中間ファイル。
makemigrationsコマンドで作成する。
初回のデータと、それにつらなる一連の差分データで構成されます。

```py:migration_sample.py
from django.db import migrations, models

class Migration(migrations.Migration):

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

    operations = [
        migrations.DeleteModel('Tribble'),
        migrations.AddField('Author', 'rating', models.IntegerField(default=0)),
    ]
```

ファイルの内容はこのようになっており、

 - dependencies に親世代のマイグレーションID
 - operations に処理の内容
 - 
 が記述されます。

##マイグレーションディレクトリ

アプリケーションのディレクトリにあるmigrationsディレクトリ。
makemigrationsを実行すると、マイグレーションファイルが連番付でここに出力される。
最初の実行で実行時点のmodel.py全体の内容が出力され、次の実行からはmodel.pyの変更分が別ファイルとして出力される。

##マイグレーション実行

または「マイグレートする」といわれる。
マイグレーションファイルの内容をSQLに翻訳しデータベースに適用する。
基本的には最新の状態まで適用するが、データベース定義を特定の状態まで進めたり戻したりもできる。

##ロールバック
過去のマイグレーションIDを指定してマイグレーションを実行すると、そのIDの適用前の状態まで適用が取り消される。これをロールバックするという。

##マイグレーション管理テーブル
初回マイグレーション実行時、データベースには「django_migrations」というテーブルが作成され、どこまでマイグレーションファイルを適用したかを管理する。
showmigrationsで表示される[X]のマークはこのテーブルの内容と照合させて表示している。

#コマンド一覧

##基本操作

  `manage.py makemigrations`
  マイグレーションファイルを作成
  
  `manage.py migrate (マイグレーションID)`
  マイグレーションファイルをデータベースに適用

  ・マイグレーションIDが未指定だった場合全てのマイグレーションを適用
  ・指定したマイグレーションIDが未適用のものだった場合、そのIDまでデータベースに適用
  ・指定したマイグレーションIDが適用済のものだった場合、そのIDまでの適用をデータベースから取り消す
  
  `manage.py showmigrations`
  マイグレーションの一覧を表示（アプリ・名前順）

  `manage.py showmigrations --plan`
  マイグレーションの適用計画順（後述）に表示
  
  `manage.py sqlmigrate マイグレーションID`
  対象のマイグレーションID適用時のSQLを表示
  

##特殊操作

  `manage.py squashmigrations From To `
  複数世代のマイグレーションをマージする
  単純に一つのファイルにまとめる。適用後は元ファイルの削除を勧告される。

  `manage.py makemigrations --merge`
  一つのマイグレーションファイルから複数のマイグレーションファイルが分岐してしまった状態を修復するこのコマンド。基本的にマイグレーションファイルは直列関係になっていなくてはならないが、チームでの開発だと容易に並列箇所が生まれる。分岐した複数のマイグレーションファイル全てをdependenciesに持つ、空のマイグレーションファイルを作成し、分岐した流れを統合する。

  `manage.py makemigrations --empty`
  空のマイグレーションファイルを作成する。operationsに処理を追加して使う。

  `manage.py migrate --fake`
  マイグレーションファイルの内容は実施せず、django_migrationsテーブルへの追記のみ実行する
  使い道は後述

#運用

##基本作業

基本は以下の１から５を繰り返してすすめる

１．model.py上のクラス定義を変更する

２．マイグレーションファイルの作成
manage.py makemigrations
３．マイグレーションで実行されるSQLの確認
manage.py sqlmigrations マイグレーションID
４．マイグレーションの実行
manage.py migrate
５．データベースクライアント等で適用結果を確認


##特殊作業の例

###マイグレーションファイルを作り直す

  適用済みのものがあるならロールバックを行い、やり直したい期間のマイグレーションファイルを削除してmakemigrationsを再実行する。

###マイグレーションファイルの作成前までソースコードをロールバックする

必ず先にマイグレーションのロールバックを行い、そののちソースコードをロールバックする。
※Migrationファイルがなくなるとソースコードのロールバックが実行できなくなるため。

###マイグレーションファイル名（ID）を変更する

・最新かつ未適用時は自由に変更可能
・未適用だが最新ではない場合は、後続世代のdependenciesの値も変更が必要
・適用済みのものは、データベース上のdjango_migrationの値も変更が必要

###マイグレーションファイルに任意の処理を追加する

マイグレーションに合わせてデータの更新等を行いたいときは、operationsに以下の形式で処理を追記する
`migrations.RunPython(実行する処理, option ロールバックで実行する処理)`

[参考：Django 1.7のモデルマイグレーション内で、モデルの値を更新する](https://qiita.com/key/items/94f5974f977fb7c19baa)


###マイグレーションファイルをまとめる

  manage.py squashmigrationsでマイグレーションファイルをまとめられる。
  特定時点から最新状態までをまとめるなら、特定時点までのファイルを削除して
  makemigrationsを再実行してもいい。

###データベースの現状を起点として再出発する

以下の方法で可能

[参考：Code & Business マイグレーションが失敗する場合は｡｡｡]
(https://remotestance.com/blog/2612/)

  １．model.pyとデータベースの定義を合わせる
  ２．マイグレーションファイルを全部削除して、マイグレーションファイルを再作成
  ３．テーブル：django_migrationsのデータを削除
  ４．manage.py migrate --fake を実行
  

##manage.py migrate --fake の使いどころ

マイグレーション機能は万能というわけではないので、エラー回避のためどうしても直接データベースを変更するケースが出てくる。以下はユニークキーの追加でマイグレーションが使えなかったケースである。

[Code & Business ユニーク制約付きのカラムを追加するときの注意点](https://remotestance.com/blog/2612/)

そのような場合は
１。まず直接データベースにマイグレーションと同じ定義変更を行い
２．次に「manage.py migrate --fake」を実行してマイグレーションが無事終了したように記録する。ソース管理の観点より、作業内容をマイグレーションファイルのコメントとして追記しておく。


##TIPS
- マイグレーションディレクトリに余計なファイルがあるとエラーになる。
- 中間のファイルを一つでも損失するとディレクトリにあるデータ全体が不正なものになるので、うっかり削除しないように注意する。
- マイグレーションファイルの名前（モジュール名）はそのままIDとなり、django_migrationsテーブルやdependenciesで使われる。
- マイグレーションファイルには自動的に連番が付くが番号は適応順と関係ない。適応順はdependenciesを評価して決まる。

- 組み込みアプリにもマイグレーションファイルは存在する。確認したいときは以下のディレクトリを参照。
env/Lib/site-packages/django/contrib/<アプリ名>/migrations

##開発用TIPS

Djangoのモデルフィールドはnot null制約がデフォルトで指定される。
not null制約があるフィールドを追加すると、migrate時に既存レコードに対しての処理を
いちいち確認されてなかなか面倒くさい。
ちょっと何か作る時は、とりあえずフィールドにnull=Trueを設定し、あとで必要に応じて
not null制約を付けた方がストレスがたまらない。

マイグレーションはロールバックができるが、マイグレーションファイル破棄前に実行するのを忘れて結局ロールバックできなかったりする。
開発環境ではスキーマの全オブジェクト削除をすぐ実行できるようにしておいて、最初から構築しなおした方が楽かも。
  
  dumpdataとloaddataやデータベースの直接更新を取り合わせて使い、臨機応変に対応しよう。


参考
[Xoxzoの公式ブログ djangoのmigrationについて](https://blog.xoxzo.com/ja/2017/04/26/about-django-migration-1/)