PythonでDjango MongoDBエンジンを使うためのガイド

この記事では、非リレーショナルデータベースである MongoDB を Python Web Framework である Django で使用する方法を説明します。

Django は、リレーショナルデータベースである PostgreSQL や MariaDB、MySQL と一緒に使うのが一般的ですが、その理由は ORM が搭載されているからです。

MongoDBは非常に柔軟で、プロトタイピングを容易にするために、Flaskのような軽量なフレームワークとよく組み合わされます。

しかし、スケーラビリティ、動的構造、クエリのサポートにより、大規模なプロジェクトで使用されることも多くなっています。

Django MongoDB Engine は、スキーマを宣言的に定義するために使用されます。

注意: 執筆の時点では、このエンジンは Python 3.x をサポートしていません。

最新のサポートバージョンは Python 2.7 です。

非リレーショナルデータベースとリレーショナルデータベースの比較

このエンジンが他の一般的なエンジンと大きく異なる点は、Django アプリケーションがより一般的にリレーショナルデータベースで開発されるのに対し、ノンリレーショナルデータベースで動作するという点です。

この二つのアプローチのどちらを選ぶかは、あなたが取り組んでいるプロジェクトに帰結します。

なぜなら、それぞれのタイプには状況に応じて一定の長所と短所があるからです。

非リレーショナルデータベースは通常、より柔軟であり(長所でもあり短所でもある)、リレーショナルデータベースはより適合的です(これも長所でもあり短所でもある)。

また、多くのデータを保持するスケーラブルなシステムには、通常、非リレーショナル・データベースが適している。

しかし、小規模から中規模のシステムでは、リレーショナル・データベースの保守のしやすさが優先されることがよくある。

リレーショナルデータベース

リレーショナルデータベースは、列と行で構成されるテーブルにデータを格納する。

  • 行はエンティティ (例: Movie) を表す。
  • 列は実体の属性 (例: 映画名長さ公開年 など) を表す。
  • 行は、データベース内の1つのエントリを表します (例: {"The Matrix", 2h 16min, 1999.})。

すべてのテーブルの行は、その行だけを表すユニークなキー(ID)を持っていなければなりません。

リレーショナルデータベースの代表的なものは以下の通りである。

Oracle、PostgreSQL、MySQL、MariaDBなど。

インストールとセットアップ

Django MongoDB Engine をプロジェクトに実装するために、3つのものをインストールしたいと思います。

  1. Django-nonrel – 非リレーショナルデータベースのサポート (これは Django 1.5 もインストールしてくれ、以前にインストールしたバージョンはアンインストールします)。
  2. djangotoolbox – 非リレーショナルな Django アプリケーションのためのツール。
  3. Django MongoDB Engine – エンジンそのものです。

これらを pip 経由で、Django 自体と一緒にインストールしましょう。

$ pip install django
$ pip install git+https://github.com/django-nonrel/django@nonrel-1.5
$ pip install git+https://github.com/django-nonrel/djangotoolbox
$ pip install git+https://github.com/django-nonrel/mongodb-engine


まずは Django プロジェクトをコマンドラインから初期化してみましょう。

$ django-admin.py startproject djangomongodbengine


さて、いくつかの基本的なファイルを含むスケルトンプロジェクトができたので、 Django にどのエンジンを使いたいかを知らせたいと思います。

そのために、 settings.py ファイルを更新し、より具体的には DATABASES プロパティを更新します。

DATABASES = {
   'default' : {
      'ENGINE' : 'django_mongodb_engine',
      'NAME' : 'example_database'
   }
}


インストールとセットアップが終わったので、 Django MongoDB Engine でできることをいくつか見てみましょう。

モデル・フィールド

モデルを扱う場合、標準的な MVC (Model-View-Controller) アーキテクチャでは、古典的なアプローチは django.db.models モジュールを使用することです。

ModelクラスにはCharFieldTextField` などがあり、基本的にモデルのスキーマを定義し、Django の ORM によってデータベースにマッピングされる方法を定義することができます。

では、 models.pyMovie というモデルを追加してみましょう。

from django.db import models


class Movie(models.Model)
    name = models.CharField()
    length = models.IntegerField()


ここでは、 Movie モデルがあり、 namelength という2つのフィールドを持っています。

それぞれ、データベースのカラムを表す Field の実装で、データ型は指定したものになります。

かなりの数のフィールドタイプがありますが、 models モジュールは複数の値を持つフィールドをあまりサポートしていません。

これは、 models モジュールが主にリレーショナルデータベースで使用されることを想定しているためです。

オブジェクトが複数の値を持つフィールドを持つ場合、例えば Movie が多くの Actor を持つ場合、他のテーブルと一対多のリレーションを持つことになります。

MongoDBでは、他のテーブルやドキュメントを参照することなく、そのドキュメント内のリストとして保存することができます。

ここで、 ListFieldDictField などのフィールドがないことを実感します。

ListField

ListFieldはリスト型属性で、複数の値を保持することができる属性です。

これはdjangotoolbox.fields` モジュールに属しており、リストのような値を含むフィールドを指定し、BSONドキュメントに保存するために使用されます。

では、先ほどの Movie のモデルをいじってみましょう。

from django.db import models
from djangotoolbox.fields import ListField


class Movie(models.Model):
    name = models.CharField()
    length = models.IntegerField()
    year = models.IntegerField()
    actors = ListField()


idフィールドを指定していないことに注意してください。

MongoDB は暗黙のうちにModelのインスタンスにそれを代入してくれるからです。

さらに、actorsフィールドを追加しました。

これはListField` です。

これで、Movie のインスタンスを作成するときに actors フィールドにリストを代入して、そのまま MongoDB のデータベースに保存できます。

Actor インスタンスを格納するテーブルを別に作成して Movie ドキュメントでそれを参照しなくてもいいのです。

movie = Movie.objects.create(
    name = "The Matrix",
    length = 136,
    year = 1999,
    actors = ["Keanu Reeves", "Laurence Fishburne"]
)


このコードを実行すると、MongoDB ドキュメントが作成されます。

{
  "_id" : ObjectId("..."),
  "name" : "The Matrix",
  "length" : 136,
  "year" : 1999,
  "actors" : [
    "Keanu Reeves", 
    "Laurence Fishburne"
  ]
}


ListFieldextend()` して、さらに値を追加することもできます。

movie.actors.extend(['Carrie-Ann Moss'])


この結果、BSON ドキュメントが更新されます。

{
  "_id" : ObjectId("..."),
  "name" : "The Matrix",
  "length" : 136,
  "year" : 1999,
  "actors" : [
    "Keanu Reeves", 
    "Laurence Fishburne",
    "Carrie-Ann Moss",
    "Carrie-Ann Moss"
  ]
}


SetField

SetFieldListField` と同じですが、Pythonのセットとして解釈されるので、重複は許されません。

もし、同じアクターを2回追加すると

movie.actors.extend(['Carrie-Ann Moss'])


出力が少し変なことにすぐに気がつきます。

{
  "_id" : ObjectId("..."),
  "name" : "The Matrix",
  "length" : 136,
  "year" : 1999,
  "actors" : [
    "Keanu Reeves", 
    "Laurence Fishburne",
    "Carrie-Ann Moss"
  ]
}


重複したエントリを避けたいので、各個人が実際の個人であることが望ましいのですが、 actorsListField ではなく SetField にするのがより理にかなっています。

from django.db import models
from djangotoolbox.fields import ListField


class Movie(models.Model):
    name = models.CharField()
    length = models.IntegerField()
    year = models.IntegerField()
    actors = SetField()


これで、複数のアクターを追加することができ、その中には重複しているものもありますが、ユニークなものだけが追加されることになります。

movie = Movie.objects.create(
    name = "John Wick",
    length = 102,
    year = 2014,
    actors = ["Keanu Reeves", "Keanu Reeves", "Bridget Moynahan"]
)


しかし、出来上がったドキュメントには "Keanu Reeves" という、たったひとつの項目だけが残ります。

{
  "_id" : ObjectId("..."),
  "name" : "John Wick",
  "length" : 102,
  "year" : 2014,
  "actors" : [
    "Keanu Reeves", 
    "Bridget Moynahan"
  ]
}


DictField

DictField` は、Python辞書をBSONドキュメントとして、ドキュメント内に保存します。

これは、辞書がどのようなものであるかわからない場合、また、辞書の構造があらかじめ定義されていない場合に、望ましい方法です。

一方、構造がよく分かっている場合は、モデルの中のモデルとして、埋め込みモデルを使用することが推奨されます。

例えば、Actorはそれ自身のモデルであり、Movieモデルには複数のActorモデルが埋め込まれるようにすることができます。

一方、可変長の値を追加する場合は、Key-Value 要素としてマッピングし、 DictField として保存することができます。

例えば、 0..n 件のレビューを持つことができる reviews フィールドを追加してみましょう。

レビューは予測可能な構造 (name,grade,comment) を持っていますが、actorsreviewsのために別々のModelを作る前に、それらをDictField` として実装することにしましょう。

from django.db import models
from djangotoolbox.fields import SetField
from djangotoolbox.fields import DictField


class Movie(models.Model):
    name = models.CharField()
    length = models.IntegerField()
    year = models.IntegerField()
    actors = SetField()
    reviews = DictField()


これで、ムービーを作成するときに、レビュアーの辞書と、そのムービーのレビューを追加することができるようになりました。

movie = Movie.objects.create(
    name = "Good Will Hunting",
    length = 126,
    year = 1997,
    actors = ["Matt Damon", "Stellan Skarsgard"],
    reviews = [
        {"Portland Oregonian" : "With its sweet soul and sharp mind..."},
        {"Newsweek" : "Gus Van Sant, working from the tangy, well-written script..."}
    ]
)


このコードを実行すると、次のような結果が得られます。

{
  "_id" : ObjectId("..."),
  "name" : "Good Will Hunting",
  "length" : 126,
  "year" : 1997,
  "actors" : [
    "Matt Damon", 
    "Stellan Skarsgard"
  ],
  "reviews" : [
    {"Portland Oregonian" : "With its sweet soul and sharp mind..."},
    {"Newsweek": "Gus Van Sant, working from the tangy, well-written script..."}
  ]
}


エンベデッドモデル

さて、レビューフィールドは、間違いなく、名前の後にコメントが続くという、同じような構造になるでしょう。

俳優は名前だけでなく、生年月日` などの特徴も持っています。

この2つについては、リレーショナルデータベースと同じように、独立したモデルを作成することができます。

しかしリレーショナルデータベースの場合、それぞれのテーブルに保存して、 Movie テーブルからリンクすることになります。

しかし MongoDB では、それらを埋め込みモデル (ドキュメント全体を別のドキュメントに埋め込んだもの) にすることができます。

もう一度 Movie を変更してみましょう。

from django.db import models
from djangotoolbox.fields import ListField, EmbeddedModelField


class Movie(models.Model):
    name = models.CharField(max_length=100)
    length = models.IntegerField()
    year = models.IntegerField()
    actors = SetField(EmbeddedModelField("Actor"))
    reviews = SetField(EmbeddedModelField("Review"))


ここでは、actorsreviews の両方に SetField (これは ListField のようなものでもよい) を作っています。

しかし、今回は SetField のコンストラクタに EmbeddedModelField を渡すことで、他のモデルの SetField にしています。

また、EmbeddedModelField クラスのコンストラクタで、どのモデルであるかを指定しています。

では、この2つも models.py ファイルで定義してみましょう。

class Actor(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    date_of_birth = models.CharField(max_length=11)

class Review(models.Model):
    name = models.CharField(max_length=30)
    comment = models.CharField(max_length=300)


これで、Movie オブジェクトを作成してデータベースに保存するときに、新しい ActorReview インスタンスを追加できるようになりました。

movie = Movie.objects.create(
    name = "Focus",
    length = 105,
    year = 2015,
    actors = [
        Actor(
            first_name="Will",
            last_name="Smith", 
            date_of_birth="25.09.1968."
        )
    ],
    reviews = [
        Review(
            name = "Portland Oregonian",
            comment = "With its sweet soul and sharp mind..."
        ),
        Review(
            name = "Newsweek",
            comment = "Gus Van Sant, working from the tangy, well-written script..."
        )
    ]
)


これは、セット内の ActorReview それぞれに対して新しい BSON ドキュメントを作成し、それらを movie ドキュメントに埋め込みオブジェクトとして保存します。

{
  "_id" : ObjectId("..."),
  "name" : "Focus",
  "length" : 105,
  "year" : 2015,
  "actors" : [
      {
          "name" : "Will",
          "last_name" : "Smith",
          "date_of_birth" : "25.09.1968"
        }   
    ],
    "reviews" : [
        {
          "name" : "Portland Oregonian",
          "comment" : "With its sweet soul and sharp mind..."
        },
        {
          "name" : "Newsweek",
          "comment" : "Gus Van Sant, working from the tangy, well-written script..."
        }
    ]
}


BSON配列の reviews の各エントリは、個々の Review のインスタンスです。

Actors` も同様です。

ファイル操作

MongoDB には GridFS と呼ばれるファイルシステムにファイルを保存/検索する仕様が組み込まれており、Django MongoDB Engine でもそれを使っています。

注意: MongoDB はファイルを 255kB の大きさに分割して保存します。

ファイルがアクセスされると、GridFS はその断片を集めてマージします。

GridFSのシステムをインポートするには、django_mongodb_engine_storageモジュールにアクセスします。

from django_mongodb_engine.storage import GridFSStorage


gridfs = GridFSStorage()
uploads_location = GridFSStorage(location = '/uploaded_files')


また、GridFSField()というフィールドを利用することで、GridFSシステムを利用してデータを格納するフィールドを指定することができます。

class Movie(models.Model):
    name = models.CharField()
    length = models.IntegerField()
    year = models.IntegerField()
    actors = SetField(EmbeddedModelField("Actor"))
    reviews = SetField(EmbeddedModelField("Review"))
    poster = GridFSField()


これで、この画像はチャンクに保存され、必要に応じてレイジーローディングされるようになります。

結論

まとめると、Django MongoDB Engine はかなり強力なエンジンで、これを使う主な欠点は、古いバージョンの Django (1.5) と Python (2.7) で動作することですが、Django は現在 3.2 LTS で 1.5 のサポートはかなり前に終了しています。

Pythonは3.9で、2.7のサポートは昨年終了しています。

それに加えて、Django MongoDB Engineは2015年にさらなる開発を停止したようです。

タイトルとURLをコピーしました