Djangoでmigrate時に任意の初期データを自動投入する

もうすぐ平成も終わりますが、djangoでmigrate時に任意の初期データを自動投入したいと思うときがあると思うのでそのやり方です。

## migrate時に初期データを投入したいケースとは

まず、どういうときにmigrate時に初期データを投入したいと思うかと言うと、[以前の記事](https://qiita.com/d9magai/items/8c4ebb032a6c4829b0e0)で書いていましたが、都道府県テーブルなんかはその典型ですよね。都道府県テーブルを作りたいが都道府県のレコードは入れずに使う、という状況は考え難いです。
かと言って「migrateして必要なテーブルを作ったら`hidennotare.sql`を実行して必要なデータを投入する」みたいなことをしていたらそう遠くないうちに事故が起きそうです。

> なお、[このSQL](https://gist.github.com/d9magai/ca552cbc659ac74f907181e23fe11eb9)で都道府県とランキング1位と2位をDBに登録できます。

記事の中でこんな感じで親切ぶってSQLを示していますが、じゃあ開発中にDBを再作成したいと思ったときにこのSQLを毎回実行するのか？って話です。
それにスキーマ管理には冪等性の保証がほしいですし、そう考えるとmigrateの機能の一部として初期データを投入するのが合理的に思えてきます。

## 具体的なやり方

具体的なやり方としては、次の3つのステップです。

1. 投入したいデータのfixtureファイルを作成
2. 空のmigrationファイルを作る
3. そのmigrationファイルからfixtureファイルをロードするように書く


順番に解説します。

### 投入したいデータのfixtureファイルを作成

Webアプリケーションに初期データを付属させるときには、特定の環境でしか動かないようなファイルではなくデータベースに依らずにインポートできるようなファイルがほしいですよね。Djangoではそんなファイルのことをfixtureと呼んでいます。
一体どんなファイルなのかというと、お手元にDjangoのアプリがあれば話は早いので`python manage.py dumpdata`を実行してみて下さい。するとDBの中身がエクスポートされてjsonファイルがずらずらと表示されたと思います。fixtureもこの形式で書きます。

```bash
$ python manage.py dumpdata
[{"model": "contenttypes.contenttype", "pk": 1, "fields": {"app_label": "admin", "model": "logentry"}},...(以下略
```


さて、dumpdataコマンドで吐き出されるような形式で投入したいデータのfixtureファイルを作成すればいいわけですが、そらでfixtureファイルを書くのは中々の難易度です。ですので、

* 初期データとしてほしいデータをDBに入れる -> dumpdataコマンドでDBをエクスポート -> 欲しい部分をfixtureファイルとして利用

という方法が効率がいいと思います。このとき`jq`コマンドがあるとズバリほしい部分が抽出できるし整形もできて見やすくなるのでいいですね。例えば都道府県テーブルに都道府県のレコードが入っている状態で、都道府県のfixtureファイルを作りたいときはこんな感じです。

```bash
$ python manage.py dumpdata | jq 'map(select(.["model"] == "app.prefecture"))'
[
  {
    "model": "app.prefecture",
    "pk": 1,
    "fields": {
      "name": "北海道"
    }
  },
  {
    "model": "app.prefecture",
    "pk": 2,
    "fields": {
      "name": "青森県"
    }
  },
(長いので中略)
  {
    "model": "app.prefecture",
    "pk": 47,
    "fields": {
      "name": "沖縄県"
    }
  }
]
```

このjsonを`app/fixture/prefecture.json`にでも置いておきます。

### 空のmigrationファイルを作る

通常、djangoのmakemigrationsコマンドと言えば、モデルの定義を見てマイグレーションファイルを作ってくれるコマンドです。しかし、`--empty`オプションを付けることで、何もしないマイグレーションファイルを作ることが出来ます。
そんなもの作ってどうするのか、と言いますとやりたいことを開発者がそのマイグレーションファイルに書いて任意の処理を行えるようにしつつ、スキーマ管理自体はdjangoのマイグレーション機能に任せるということが出来るようになります。

```bash
$ python manage.py makemigrations app --empty
```
このコマンドで空のマイグレーションファイルを作りましょう。

### そのmigrationファイルからfixtureファイルをロードするように書く

作られた空のマイグレーションファイルを覗いてみます。日付とかdependenciesとかは人によると思いますが、だいたいこんな感じで出来ると思います。

```python
# Generated by Django 2.0 on 2019-04-30 14:47

from django.db import migrations


class Migration(migrations.Migration):

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

    operations = [
    ]
```


これを以下のように書き換えます。

```python
# Generated by Django 2.0 on 2019-04-30 14:47

from django.core.management import call_command
from django.db import migrations


def load_fixture(apps, schema_editor):
    call_command('loaddata', 'app/fixture/prefecture.json', app_label='app')


class Migration(migrations.Migration):

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

    operations = [
        migrations.RunPython(load_fixture),
    ]

```

これでmigrate時にfixtureファイルがロードされるようになります。

## 初期データを自動投入できると嬉しいこと

このマイグレーションファイルを作った直後は特に何も嬉しくないと思います。真価を発揮するのは、主に次の2つのケースですかね。

* このmigrateを開発環境以外で流すとき
* 開発中、今使っているDBを捨てて再作成するとき

Djangoを使っているなら当然、production環境等のスキーマ管理にもDjangoのマイグレーション機能を使っていると思います。既に述べたとおりにその機能の一部として初期データが自動的に投入されるので、各環境でmigrateを実行した後にあれを実行する、みたいな手間から解放されて嬉しいです。
そして開発中にDBを再作成したいな、と思ったときにも役立ちます。スキーマを管理することでいつでもDBを作り直せるようになるので、初めからDBを使い捨てることを前提にしたような開発も可能になるわけですが、migrateのたびに初期データを手動で投入していては片手落ちです。自動投入によるスピーディーな開発で令和の時代も乗り切りたいですね。