【Django】マイグレーション不整合エラーの解消【環境構築】

# はじめに

Djangoプロジェクトに途中参加した際や、チームでDBを共有した際に「マイグレーション不整合（依存関係）エラー」に悩まされることがあります。全リセットでやり直せれば楽ですが、チームで共有している場合は勝手に変更してコミットするわけにはいきません。

例えば、以下のようなときのための記事です。
- 共有DBのダンプをもらってリストアし、`python manage.py migrate` を実行したらエラーが出た
- チームメンバーが追加したマイグレーションファイルをpullして `migrate` したらエラーになった
- 途中参加でDBとコードの状態が合わず、マイグレーションが進まなくなった

---

### エラー内容

- `django.db.migrations.exceptions.InconsistentMigrationHistory` などのエラーが発生
- 例: `Migration accounts.0002_initial is applied before its dependency booking.0032_auto_20250516_1301 on database 'default'.`
    （accountsアプリの[0002_initial]マイグレーションが、依存先であるbookingアプリの[0032_auto_20250516_1301]よりも先に適用されている）

---

### 原因

- DB側のマイグレーション履歴（django_migrationsテーブル）と、手元のマイグレーションファイルや適用状況が食い違っている（＝履歴と実体がズレている）ため、Djangoが正しい状態を判断できない

---

### 目次

1. エラー内容の確認
2. 【ソースコード】マイグレーション適用状況の確認
3. 【DB】マイグレーション履歴の確認・修正・再適用
4. 最終確認
5. 補足：Djangoマイグレーションの基礎知識

<br>

# 解決までの手順
【ソースコード側のマイグレーションファイルの適用状況】と【DB側のマイグレーション履歴】を確認して、不整合部分の履歴を削除・再適用していきます。

## 1. エラー内容の確認

マイグレーション不整合エラーが発生した場合、まずはエラーメッセージからどのアプリ・どのマイグレーションで不整合が発生しているか特定します。

- 例:
```
Migration accounts.0002_initial is applied before its dependency booking.0032_auto_20250516_1301 on database 'default'.
```
  この例では「accountsアプリの[0002_initial]マイグレーションが、依存先であるbookingアプリの[0032_auto_20250516_1301]よりも先に適用されてしまっている」ことが原因でエラーになっています。

<br>

## 2. 【ソースコード】マイグレーション適用状況の確認

どのマイグレーションファイルが適用済み（[X]）か、未適用（[ ]）かを一覧で確認し、エラーで指摘されたマイグレーションや依存関係の状態を把握します。
```sh
python manage.py showmigrations
```
- 実行例：
```
    accounts
     [X] 0001_initial
     [ ] 0002_initial
    booking
     [X] 0030_auto_20250501_1000
     [X] 0031_auto_20250510_1200
     [ ] 0032_auto_20250516_1301
    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]が付いているものが適用済み、[ ]は適用できなかったマイグレーションファイルです。
[0002_initial]と[0032_auto_20250516_1301]がうまく適用できていないことが分かります。

## 3. 【DB】マイグレーション履歴の確認・修正・再適用

以下の手順で履歴を確認・修正し、正しい順序で `--fake` を使ってマイグレーションを適用し直します。

### 1. マイグレーションの履歴を確認する

まず、DBの`django_migrations` テーブルで該当アプリのマイグレーション履歴を確認します。
`django_migrations` テーブルには「どのアプリのどのマイグレーションが、いつ適用されたか」が記録されています。
- MySQLの場合：

```sql
SELECT * FROM django_migrations WHERE app='accounts';
```

- 実行例：
```
| app      | name           | applied             |
|----------|----------------|--------------------|
| accounts | 0001_initial   | 2024-05-20 10:00:00 |
| accounts | 0002_initial   | 2024-05-25 09:30:00 ←実は履歴だけ進んでいる！|
```
[0002_initial]について、showmigrationsで[X]が付いていないのに、`django_migrations` テーブルではすでに適用されたことになっています。これがまさに今回のエラーの原因ということです。
なので、この適用履歴を消してもう一度やり直すことで解消を目指します。

<details>
<summary>【DockerでMySQLを利用している場合】</summary>
  Dockerコンテナ内のMySQLに接続してから、SQLを実行してください。
  
  ```sh
  docker-compose exec <mysqlコンテナ名> mysql -u <ユーザー名> -p<パスワード> <データベース名>
  ```
  
※ <mysqlコンテナ名>  <ユーザー名> などは自分の環境に合わせて置き換えてください。

</details>

---

### 2. 該当履歴をDBから削除する

エラーで指摘されたマイグレーション（例：accounts.0002_initial）の履歴をDBから削除します。
   - MySQLの場合：
```sql
    DELETE FROM django_migrations WHERE app='accounts' AND name='0002_initial';
```
    
---

### 3. 依存先のマイグレーションを `--fake` で適用する

依存先のマイグレーション（例：booking.0032_auto_20250516_1301）を `--fake` で適用します。
```sh
    python manage.py migrate booking 0032_auto_20250516_1301 --fake
```

---

### 4. 依存元のマイグレーションを `--fake` で再適用する

依存元のマイグレーション（例：accounts.0002_initial）を `--fake` で再適用します。
```sh
    python manage.py migrate accounts 0002_initial --fake
```

<br>

## 4. 最終確認

全てのマイグレーションが正しく適用されているかを確認します。

```sh
python manage.py migrate
```

他にも同様のエラーが出た場合は全て解消するまで、同じように履歴を確認・削除し、依存関係の順に `--fake` で適用します。

何も適用されなくなれば整合性OKです。
念のため `python manage.py showmigrations` で全て[X]になっているか確認しましょう。
お疲れ様でした！

<br>

# 補足：Djangoマイグレーションの基礎知識

### makemigrations とは？

- `makemigrations` は、`models.py` の変更内容をもとに「マイグレーションファイル（DB変更手順）」を自動生成するコマンドです。

```sh
python manage.py makemigrations
```

これにより、DBスキーマ変更の履歴がコードとして管理できます。

---

### migrate とは？

- `migrate` は、マイグレーションファイルの内容を実際にDBに適用するコマンドです。

```sh
python manage.py migrate
```

テーブル作成・カラム追加など、DB構造が物理的に変更されます。

---

### migrate --fake とは？

- `migrate --fake` は、**DBのスキーマは変更せず**、マイグレーション履歴（`django_migrations`テーブル）だけ「適用済み」に進めるコマンドです。

```sh
python manage.py migrate --fake
```

すでにDBが最新状態だが履歴だけズレている場合や、不整合解消時に使います。

---

### migrateとmigrate --fakeの使い分け

- 通常は `python manage.py migrate` を使い、マイグレーションファイルの内容を**実際にDBへ反映**させます。
- `--fake` オプションは、**DBのスキーマは既に最新だが、マイグレーション履歴だけズレている場合や、不整合を解消したい場合**に使います。
    - 例: すでに手動でDB変更が反映されている、他環境からDBを引き継いだ、など。
- `--fake` を使うと、Djangoは「マイグレーションが適用された」と**履歴だけ進める**ため、
  実際のDB構造とマイグレーション内容が一致していることを**必ず確認してから**使いましょう。
- 誤って `--fake` を使うと、DBと履歴がさらにズレてしまう危険があるので注意してください。
  例えば、新しいモデルを追加したのに`--fake`を使うと、本来DBに反映されるべき変更（テーブル追加）が実際には行われていないのに、「適用済み」と履歴だけが進んでしまいます。

---

### django_migrationsテーブルとは？

- Djangoが「どのマイグレーションが適用済みか」を管理するためのDB内のテーブルです。
- 各マイグレーションの適用履歴（アプリ名・マイグレーション名・適用日時など）が記録されます。
- これにより、Djangoは「どこまで適用済みか」「何が未適用か」を正確に把握できます。

---

## 参考URL

- [Django公式ドキュメント: Migrations](https://docs.djangoproject.com/ja/stable/topics/migrations/)
- [Django公式ドキュメント: migrateコマンド](https://docs.djangoproject.com/ja/stable/ref/django-admin/#migrate)