2012年10月7日

Python の ORM 調査:Django編

概要

Python の ORM を調査中。今回はWebアプリケーション作成で比較的利用されている「Django」内蔵のORMを調査してみる。

Djangoに関して

Django」はPythonで代表的なフルスタックフレームワーク。
結構なんでも揃い、かつプラグイン機構が充実しているので、拡張もしやすい。
ライセンスは BSD。Python 3 には今の所対応していない。
対応RDBは MySQL、Oracle、PostgreSQL、SQLite。

本来は ORM だけ独立で使うための物ではないかもしれないが、今回は ORM だけを調査したい。

ドキュメント

Django ドキュメント」から日本語のドキュメントが読める。
今回のようにORMだけ独立で利用したい場合等は公式ドキュメント(英語)の「Writing custom django-admin commands」に書いてある。使い方は昨日書いたので参照。

インストール

pip でインストールする。

pip install django

サンプルソース

自分は ORM を利用する場合あまり DDL を発行しないので、DML を中心としたサンプルとする。

今回は MySQL でデータベースとテーブル用意する。

> mysql -u root
mysql> CREATE DATABASE example DEFAULT CHARACTER SET utf8;

mysql> GRANT ALL PRIVILEGES ON example.*
    -> TO username@localhost
    -> IDENTIFIED BY 'password';

mysql> exit;

> mysql -u username -p example

mysql> CREATE TABLE tweets (
    ->  id serial PRIMARY KEY,
    ->  status_id VARCHAR(255) UNIQUE NOT NULL,
    ->  from_user_id VARCHAR(255) NOT NULL,
    ->  text VARCHAR(140) NOT NULL,
    ->  created_at VARCHAR(50) NOT NULL,
    ->  datetime DATETIME NOT NULL
    ->) engine=innodb default charset=utf8;

次に付属のツール「django-admin.py」を利用してプロジェクトの雛形を作成する。
「django-admin.py」は pip 等でインストールすると、適切な場所にインストールされるのでパスを通しておくと良い。 以下のように利用する。

django-admin.py startproject mysite

データベースの設定は「settings.py」で行なう。HOST、PORT は初期設定のままなので、今回は空にしておく。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql.',
        'NAME': 'example',
        'USER': 'username',
        'PASSWORD': 'password',
        'HOST': '',
        'PORT': '',
    }
}

「settings.py」には他にも以下のような設定をした。

TIME_ZONE = 'Asia/Tokyo'
LANGUAGE_CODE = 'ja-JP'
USE_TZ = False

次にアプリケーションを作成する。

python manage.py startapp example

「management/commands」ディレクトリを作成する。

mkdir -p example/management/commands
touch example/management/__init__.py
touch example/management/commands/__init__.py

「settings.py」の「INSTALLED_APPS」にアプリケーションを有効化するために追加する。

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 以下を追加
    'example',
)

「models.py」を以下のように記述。(実際には __unicode__ 等を書くが省略)

from django.db import models


class Tweets(models.Model):
    status_id = models.CharField(max_length=255)
    from_user_id = models.CharField(max_length=255)
    text = models.CharField(max_length=140)
    created_at = models.CharField(max_length=50)
    datetime = models.DateTimeField()

    class Meta:
        db_table = 'tweets'

「example/management/commands」以下にスクリプトを作成。sample.py」の名前で以下のように作成。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from django.core.management.base import BaseCommand

from datetime import datetime
from example.models import Tweets


class Command(BaseCommand):

    def handle(self, *args, **options):
        # 基本的に autocommit=True

        tweet = Tweets(
            status_id='251298602096xxxxx1',
            from_user_id='43172xxx1',
            text=u'さんぷるでーた',
            created_at='Mon, 1 Oct 2012 12:33:50 +0000',
            datetime=datetime.utcnow(),
        )

        #  インスタンスの生成では INSERT しない
        self.assertEqual(0, Tweets.objects.count())

        # save メソッドで INSERT を発行
        tweet.save()

        self.assertEqual(1, Tweets.objects.count())

        tweet = Tweets(
            status_id='251298602096xxxxx2',
            from_user_id='43172xxx2',
            text=u'さんぷるでーた その 2',
            created_at='Mon, 1 Oct 2012 12:33:50 +0000',
            datetime=datetime.utcnow(),
        )
        tweet.save()

        self.assertEqual(2, Tweets.objects.count())


        # UPDATE 前の確認
        self.assertEqual(1, Tweets.objects.filter(
            from_user_id='43172xxx1').count())
        self.assertEqual(1, Tweets.objects.filter(
            from_user_id='43172xxx2').count())
        self.assertEqual(0, Tweets.objects.filter(
            from_user_id='43172xxx3').count())

        # UPDATE
        tweet = Tweets.objects.get(from_user_id='43172xxx2')
        tweet.from_user_id = '43172xxx3'

        # COMMIT
        tweet.save()

        # UPDSTE 後の確認
        self.assertEqual(0, Tweets.objects.filter(
            from_user_id='43172xxx2').count())
        self.assertEqual(1, Tweets.objects.filter(
            from_user_id='43172xxx3').count())

        # bulk INSERT
        insert_lst = [
            Tweets(
                status_id='251298602096xxxxx3',
                from_user_id='43172xxx3',
                text=u'さんぷるでーた その 3',
                created_at='Mon, 1 Oct 2012 12:33:50 +0000',
                datetime=datetime.utcnow(),
            ),
            Tweets(
                status_id='251298602096xxxxx4',
                from_user_id='43172xxx4',
                text=u'さんぷるでーた その 4',
                created_at='Mon, 1 Oct 2012 12:33:50 +0000',
                datetime=datetime.utcnow(),
            ),
        ]
        Tweets.objects.bulk_create(insert_lst)

        self.assertEqual(4, Tweets.objects.count())

        # DELETE
        for tweet in Tweets.objects.all():
            tweet.delete()

        # DELETE 確認
        self.assertEqual(0, Tweets.objects.count())

    def assertEqual(self, first, second):
        """
        テスト用の簡易関数
        """
        if first == second:
            print '・',
        else:
            print 'NG',

実行は以下のようにする。

python manage.py sample

まとめ

DjangoのORMは確かに良くできている。Djangoを普段利用している人には便利だろう。
ただ、速度はあまり早くないので、速度をそれほど必要としないような管理ツール等に向いていると思われる。

2012/10/16追記:

この記事は Python のORM 調査の記事の一部となる。以下が関連記事。

blog comments powered by Disqus