【Django】テーブル作成と初期データ挿入を一回のmigrateで実施する方法

# 概要
Djangoにて、マイグレーションファイルでテーブルを作成すると同時に、初期データも挿入したい場合がありますよね。外部キー制約を使っていて`null`が許容されていないケースなど。

今回、マイグレーションファイルと`fixtures`を利用して実装できたので、その手順を紹介します。


# 手順
以下のようなモデルがあるとします。

```book/models.py
from django.db import models

class Author(models.Model):
    author_id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=255, null=True)

    class Meta:
        db_table = 'author'

class Book(models.Model):
    book_id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=255, null=True)
    # author_id = models.ForeignKey('Author', on_delete=models.CASCADE, null=False, db_column='author_id')

    class Meta:
        db_table = 'book'
```

ここで、`Book`モデルの`author_id`は外部キー制約を利用しており、`null=False`です。このままコメントアウトを外して実行すると、`author`テーブルにまだレコードがないので外部キー制約違反でエラーになってしまいます。

ということで、以下の順序で実施します。

①`author`テーブルと`book`テーブル(`author_id`なし)の作成を行うマイグレーション生成
②初期データ用のJSONを作成
③`author`テーブルにレコードを追加するマイグレーション生成
④`book`テーブルに`author_id`(外部キー)を追加するマイグレーション生成
⑤マイグレート実施

### ①`author`テーブルと`book`テーブル(`author_id`なし)の作成を行うマイグレーション生成
上記の`models.py`で一度`python manage.py makemigrations`を実施してマイグレーションファイルを作成。

1つ目であれば`0001_initial`と言う名前になります。

### ②初期データ用のJSONを作成
次に挿入データ用のJSONを作成し、`fixtures`ディレクトリ以下におきます。

```book/fixtures/author_initial_data.json
[
    {
        "model": "app_name.author",
        "pk": 1,
        "fields": {
            "name": "Taro Yamada"
        }
    },
    {
        "model": "app_name.author",
        "pk": 2,
        "fields": {
            "name": "Jiro Tanaka"
        }
    }
]
```

### ③`author`テーブルにレコードを追加するマイグレーション生成

`0002_load_author_data.py`と言うファイルを`migrations`ディレクトリ以下に作成して、以下の内容を書きます。ここでは、先ほど作成した`fixtures`を実行するコマンドを書いています。

```book/migrations/0002_load_author_data.py
from django.core.management import call_command
from django.db import migrations

def load_fixture(apps, schema_editor):
    call_command('loaddata', 'fixtures/author_initial_data.json', app_label='book')

class Migration(migrations.Migration):

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

    operations = [
        migrations.RunPython(load_fixture),
    ]
```


上記では`call_command`関数を使用して、Djangoの`loaddata`コマンドを呼び出しています。
`loaddata`コマンドは、指定されたJSONファイルからデータを読み込み、DBに挿入してくれます。

### ④`book`テーブルに`author_id`(外部キー)を追加するマイグレーション生成

最後に、Bookモデルの外部キーのコメントアウト（`author_id...`）を外し、再び`python manage.py makemigrations`を実施してマイグレーションファイルを作成します。

```book/models.py
from django.db import models

class Author(models.Model):
    author_id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=255, null=True)

    class Meta:
        db_table = 'author'

class Book(models.Model):
    book_id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=255, null=True)
    author_id = models.ForeignKey('Author', on_delete=models.CASCADE, null=False, db_column='author_id') # コメントアウトを外しました

    class Meta:
        db_table = 'book'
```

これで、`0003`から始まるマイグレーションファイルが作成されるはずです。

### ⑤マイグレート実施
これで合計3つのマイグレーションファイルの準備ができましたので、
以下のコマンドですべてのマイグレーションを実行します。

```
python manage.py migrate
```

無事に、外部キー制約がある状態でテーブルが作成され、レコードも挿入されていればOKです。

## 参考・その他

以下の記事を参考させていただきました。

https://qiita.com/d9magai/items/71dfa9ff95f7b70486a7

また、マイグレーションファイル内で直接SQLスクリプトを実行してデータを挿入する方法もあると思います。個人的には「`fixtures`ディレクトリさえ見れば挿入した(初期)データがわかる」という点から`fixtures`の方がわかりやすいかなと感じています。