djangoで1ファイルでORMの挙動を確認する方法

# djangoで1ファイルでORMの挙動を確認する方法

以前 [1ファイルでhello worldを作る方法](http://qiita.com/podhmo/items/b6385fde9e32bbb8d57b) を書きましたが。
djangoで最も試してみたい事柄はORM部分かもしれません。そのORM部分を1ファイルだけで試す方法についてです。

1ファイルで試すには、ちょっとした細工が必要ですが、最近のdjangoではそれもそれなりに手軽にできるようになりました。

具体的には以下のような作業が必要になります。

- settingsをpython側から設定する
- djangoのApplicationRegistryにmodelを登録する
- 各modelのテーブルを生成する

## settingsをpython側から設定する

settingsの設定がない場合には、以下のような例外が出てしまいます。

```
django.core.exceptions.ImproperlyConfigured: Requested setting DEFAULT_INDEX_TABLESPACE, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.
```

エラーメッセージの通り、`DJANGO_SETTINGS_MODULE`を環境変数として登録しておくか`settings.configure()`を呼びましょう。
最低限必要な設定は以下の通りです。

```py
from django.conf import settings

settings.configure(
    DEBUG=True,
    DATABASES={"default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": ":memory:"
    }},
    INSTALLED_APPS=[__name__]
)
```

`DEBUG=True`はsqlのquery結果を出力する際に必要なので付けておいた方が良いでしょう。また、DBの設定は必要です。ここではsqliteのオンメモリDBを指定しています。`INSTALLED_APPS`については後述するmodelの登録の際に指定を忘れていた場合`ManyToMany`の関連が上手く動作しない様になってしまいます。

## djangoのApplicationRegistryにmodelを登録する

`django.setup()`を呼んでください。これにより`django.apps.registry.Apps`の`populate()`が呼ばれます。この中でdjangoのregistryにmodelが登録されていきます。これがなかった場合には`Model.save()`を呼んだタイミングなどで以下のようなエラーが発生します。

```
django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.
```

また注意点として、定義するmodelには`app_label`を設定してください。これがないとmodelクラスを生成できません。以下のようなエラーが出てしまいます。

```
IndexError: list index out of range
```

定義する時には以下の様にするということです。

```py
class X(models.Model):
    name = models.CharField(max_length=255, default="foo", blank=True)

    class Meta:
        app_label = __name__
```

## 各modelのテーブルを生成する

通常は`python manage.py migrate`などでDBのテーブルを生成しますが。1ファイルの場合にはその手順がありません。他の方法を考える必要があります。
例えば、テーブルを生成する関数を作ってあげます。

```py
from django.db import connections
from django.core.management.color import no_style


def create_table(model):
    connection = connections['default']
    cursor = connection.cursor()
    sql, references = connection.creation.sql_create_model(model, no_style())
    for statement in sql:
        cursor.execute(statement)

    for f in model._meta.many_to_many:
        create_table(f.rel.through)


# 一つ一つ生成していく必要がある
create_table(X)
```

## まとめ

以上のことを一通りまとめると以下の様になります。これで手軽にmodelの挙動を確かめる事ができますね。

```py
# -*- coding:utf-8 -*-
import django
from django.db import models
from django.conf import settings
from django.db import connections
from django.core.management.color import no_style


settings.configure(
    DEBUG=True,
    DATABASES={"default": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": ":memory:"
    }},
    INSTALLED_APPS=[__name__]
)


def create_table(model):
    connection = connections['default']
    cursor = connection.cursor()
    sql, references = connection.creation.sql_create_model(model, no_style())
    for statement in sql:
        cursor.execute(statement)

    for f in model._meta.many_to_many:
        create_table(f.rel.through)


class X(models.Model):
    name = models.CharField(max_length=255, default="foo", blank=True)

    class Meta:
        app_label = __name__


if __name__ == "__main__":
    import logging
    logging.basicConfig(level=logging.DEBUG)
    django.setup()
    create_table(X)

    xs = X.objects.bulk_create([X(id=1), X(id=2), X(id=3)])

    print(X.objects.count())
```