Djangoフレームワークにおけるmigrations機能の学習ノート（１）

Djangoフレームワークを利用してウェブアプリケーションを作るのは大変便利です。MVCモデルを例えるとすれば、ModelとViewの部分で煩雑なディテールを実装してくれて、プログラマーがやりたいこと（Control）に集中できるように手助けしています。
今回では、Modelの部分に関連するmigrationsに関して記載したいと思います。DjangoのProjectの作成設定周りは[Djangoオフィシャルチュートリアル](https://docs.djangoproject.com/en/1.11/intro/tutorial01/)を参考し、migrationsあたりは[こちら](https://docs.djangoproject.com/en/1.11/topics/migrations/#historical-models)を参考しています。
# migrationsとは
日本語に翻訳すると、migrationsは「移行」を意味しています。Djangoでは、主にユーザーが設計したデータ構造を自動的にデータベースのスキーマに変更する機能を指しています（マニュアルでmigrationsファイルを書くことも可能ですが、機会があればまた紹介します）。
# まず使ってみましょう
[チュートリアル](https://docs.djangoproject.com/en/1.11/topics/migrations/#historical-models)を参考しながら、アプリを作りましょう（ただし、データモデル部分だけ）。

|開発環境      |
|:-------------|
|Mac OS:Sierra |
|python2.7.10  |
|django1.11.2  |
|mysql5.7.18 |

workingディレクトリに入って、ターミナルで下記コマンドを入力：

```
django-admin startproject m_Migration
python manage.py startapp polls
```

これでプロジェクトm_Migration及び中にpollsアプリを作成しました。
次にデータベースを設定します。デフォルトのsqliteではなく、mysqlを使用しますので、
プロジェクトをsettingsファイルを変更する必要があります。
## Djangoのデータベースの設定
1. mysqlを立ち上げ、ユーザーとユーザーのパスワードと今回使用するデータベースを作成します。
2. １で作成した属性に対応して、プロジェクトパッケージにあるsettings.pyを変更する（詳細は[こちら](https://docs.djangoproject.com/en/1.11/ref/settings/#core-settings)参照）。

```
python:settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME':'blog',
        'USER':'user',
        'PASSWORD':'',
        'HOST':'',
        'PORT':'3306',
    }
}
```
あとはターミナルで初期設定を反映します。

```
python manage.py migrate
```

ログ:

```
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying sessions.0001_initial... OK
```
mysqlクライアントに入り、データベースblog現在持ってるテーブルを見てみると：

```
+----------------------------+
| Tables_in_blog             |
+----------------------------+
| auth_group                 |
| auth_group_permissions     |
| auth_permission            |
| auth_user                  |
| auth_user_groups           |
| auth_user_user_permissions |
| django_admin_log           |
| django_content_type        |
| django_migrations          |
| django_session             |
+----------------------------+
```

フレームワーク関連のデータベースが作成されました。
現時点model.pyモジュールを持ってるのはpollsアプリだけなので、pollsパッケージでmigrationsフォルダーが作成されされました。

## モデルを作成しましょう
pollsアプリで使うデータモデルCategoryとArticleを作成します。

```python:models.py

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models

# Create your models here.
class Category(models.Model):
    name = models.CharField(max_length=100)


class Article(models.Model):
    title = models.CharField(max_length=100)
    text = models.CharField(max_length=1000)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
```
pollsアプリの機能は保存してあるブログの記事を表示することです。したがって、記事モデルArticleと記事のジャンルを示すCategoryモデルを作成しました。作成したモデルを反映する為に、まずオペレーションを記述したmigrationsファイルを作成します。
```
python manage.py makemigrations polls
```
ログ：

```
Migrations for 'polls':
  polls/migrations/0001_initial.py
    - Create model Article
    - Create model Category
    - Add field category to article
```

ここで、migrations/フォルダーでは今回model.pyへの変更を記録したmigrationsファイル0001_initial.pyを作成しました。
次に、作成したmigrationsファイルを反映させます

```
python manage.py migrate
```

ログ：

```
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Applying polls.0001_initial... OK
```

今データベースでは

```
+----------------------------+
| Tables_in_blog             |
+----------------------------+
| auth_group                 |
| auth_group_permissions     |
| auth_permission            |
| auth_user                  |
| auth_user_groups           |
| auth_user_user_permissions |
| django_admin_log           |
| django_content_type        |
| django_migrations          |
| django_session             |
| polls_article              |
| polls_category             |
+----------------------------+
```
テーブルのスキーマを見てみると：

```
mysql>DESC polls_article;
+-------------+---------------+------+-----+---------+----------------+
| Field       | Type          | Null | Key | Default | Extra          |
+-------------+---------------+------+-----+---------+----------------+
| id          | int(11)       | NO   | PRI | NULL    | auto_increment |
| title       | varchar(100)  | NO   |     | NULL    |                |
| text        | varchar(1000) | NO   |     | NULL    |                |
| category_id | int(11)       | NO   | MUL | NULL    |                |
+-------------+---------------+------+-----+---------+----------------+
mysql>DESC polls_category;
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| name  | varchar(100) | NO   |     | NULL    |                |
+-------+--------------+------+-----+---------+----------------+
```

モデル通りに、新たにpollsアプリのテーブル（polls_article、polls_category）が作成されました（idフィールドはデフォルトで自動作成されます）。ここまでが、アプリでモデルを設計し、djangoのmigrations機能を利用してデータベースを作成する流れです。

# よくある利用ケース
一度作成してから、新機能の追加など、データモデルを修正する場合もあります。
## データモデルの更新
pollsアプリを例とします。例えば、もともとのブログサービスでは文字だけの記事を提供しましたが、いいデジカメを購入したので、up主が記事に写真をつけたいと思うようになりました。ここで、Articleモデルに写真のurlを保存するフィールドimage_urlを追加しましょう(すでにあるレコードのためにデフォルト値も設定します)。

```python:models.py
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models

# Create your models here.
class Category(models.Model):
    name = models.CharField(max_length=100)


class Article(models.Model):
    title = models.CharField(max_length=100)
    text = models.CharField(max_length=1000)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    image_url = models.URLField(max_length=200, default='toBeImplement')
```

変更を記録しましょう

```
python manage.py makemigrations polls
```

ログ：

```
Migrations for 'polls':
  polls/migrations/0002_article_image_url.py
    - Add field image_url to article
```

新しいmigrationsファイル0002_article_image_url.pyが作成されました。

変更を反映させます。

```
 python manage.py migrate
```

データベース現在のスキーマを見て見ましょう：

```
DESC polls_article;
+-------------+---------------+------+-----+---------+----------------+
| Field       | Type          | Null | Key | Default | Extra          |
+-------------+---------------+------+-----+---------+----------------+
| id          | int(11)       | NO   | PRI | NULL    | auto_increment |
| title       | varchar(100)  | NO   |     | NULL    |                |
| text        | varchar(1000) | NO   |     | NULL    |                |
| category_id | int(11)       | NO   | MUL | NULL    |                |
| image_url   | varchar(200)  | NO   |     | NULL    |                |
+-------------+---------------+------+-----+---------+----------------+
```

計画通りに、データベースを更新できました。

## データモデルの復旧

gitなどVCSのように、データモデルのバージョンをコントロールすることもできます。この機能の活躍場面として、緊急事態でのデータモデルの復旧が挙げられます。
仮に上記の例で写真urlに関連する変更を行い、migrationsファイル0002_article_image_url.pyが作成された状態でmigrateコマンドを実行しました。

```
 python manage.py migrate
```
ここでmigrateが失敗しました。最新のmigrationsの状態が0002_article_image_url.pyであり、しかし実際データベースのスキーマは0001_initial.pyに対応しています。この状態ではテーブルに写真urlに対応するコラムがありません。pollsアプリから写真urlデータをアクセスしようとしたらエラーが出ます。この問題の１つの対応策として、migrationsの状態を0001_initial.pyに復旧するが挙げられます（それからバッグを探します）。

復旧するために、同じくmigrateコマンドを利用して、migrationsファイル0001_initial.pyが記録した状態まで復旧します。

```
python manage.py migrate polls 0001_initial
```

ログ：

```
Operations to perform:
  Target specific migration: 0001_initial, from polls
Running migrations:
  Rendering model states... DONE
  Unapplying polls.0002_article_image_url... OK
```

ログが示したように、0002_article_image_url.pyの変更が撤廃（unapply）されました。今のArticleのテーブルのスキーマを見て見ましょう：

```
DESC polls_article;
+-------------+---------------+------+-----+---------+----------------+
| Field       | Type          | Null | Key | Default | Extra          |
+-------------+---------------+------+-----+---------+----------------+
| id          | int(11)       | NO   | PRI | NULL    | auto_increment |
| title       | varchar(100)  | NO   |     | NULL    |                |
| text        | varchar(1000) | NO   |     | NULL    |                |
| category_id | int(11)       | NO   | MUL | NULL    |                |
+-------------+---------------+------+-----+---------+----------------+
```

テーブルが元の状態に戻りました。