Django で MongoDB を使用する

ドキュメント指向のデータベースを活用する

Python による Web フレームワークである Django は、ORM (Object-Relational Mapper)、バックエンド・コントローラー、テンプレート・システムで構成されています。MongoDB はスケーリング能力とパフォーマンスに優れたドキュメント指向のデータベースです (NoSQL データベースとしても知られています)。この記事では、Python から (MongoEngine を使用して) MongoDB を呼び出す方法と、組み込みの ORM の代わりに MongoDB を Django プロジェクトに統合する方法について説明します。また、MongoDB バックエンドに対してデータの作成、読み取り、書き込み、更新を行うためのサンプルの Web インターフェースについても説明します。

Cesar Otero, Consultant, Freelance Consultant

Cesar OteroCesar Otero は Java と Python に関するフリーのコンサルタントです。彼は電気工学の学位を持ち、副専攻は数学です。



2012年 3月 22日

Django は非常に優れたモジュール方式で使用されるため、Django ベースの Web アプリケーションでは、さまざまなコンポーネントを簡単に置き換えることができます。最近では NoSQL データベースがよく使われるようになっているため、MySQL などの標準的なリレーショナル・データベースとは異なるバックエンドを使用してアプリケーションを実行したい場合があるかもしれません。この記事では、MongoDB について簡単に紹介し、続いて PyMongo または MongoEngine を使用して Python プロジェクトの中で MongoDB を呼び出す方法を説明します。その後で、Django と MongoEngine を使用して CRUD (Create、Read、Update、Delete) 操作を実行できる単純なブログを作成します。

NoSQL データベースについて

nosql-database.org によれば、NoSQL データベースは「そのほとんどが、非リレーショナル、分散型、オープンソース、水平スケーラビリティーという性質を備えた次世代のデータベース」です。この記事で取り上げるドキュメント指向のデータベース MongoDB は、この NoSQL データベースの 1 つです。

Django 1.3 は、最初から SQLite、MySQL、PostgreSQL、Oracle をサポートしていますが、MongoDB はサポートしていません。MongoDB のサポートを追加するのは簡単ですが、残念なことに MongoDB では自動管理パネルを使用できないという欠点があります。そのため、この欠点と皆さんの要求とを照らして MongoDB のサポートを追加するかどうかを判断する必要があります。


MongoDB の簡単な紹介

MongoDB は JavaScript インタープリターとして動作するため、データベース操作は JavaScript コマンドを使って行われます。皆さんのマシンに MongoDB をローカルでインストール (「参考文献」を参照) した後、リスト 1 に示すコマンドを試してみてください。

リスト 1. MongoDB で試すことができる JavaScript コマンドの例
var x = "0";
x === 0;
typeof({});

MongoDB を使い始める上で JavaScript のエキスパートである必要はありませんが、知っておくと役に立つ JavaScript の概念をいくつか以下に示します。

  • オブジェクト・リテラル構文を使用して (つまり 2 つの波括弧を使用して)、オブジェクトを作成することができます (例えば var myCollection = {}; など)。
  • 角括弧 ([]) を使用して配列を作成することができます。
  • JavaScript では、数値、ブール値、ヌル、未定義を除くすべてがオブジェクトです。

JavaScript の他の機能 (例えば、プロトタイプ・ベースのオブジェクト指向プログラミング (OOP: Object-Oriented Programming)、スコープの規則、JavaScript が持つ関数型プログラミングの性質など) について学びたい場合には、「参考文献」を参照してください。

リレーショナル・データベースとはまったく対照的に、MongoDB にはスキーマがありません。テーブルの代わりに、複数のドキュメントで構成されるコレクションを使用します。ドキュメントはリスト 2 のようにオブジェクト・リテラル構文を使用して作成されます。

リスト 2. ドキュメントを作成する例
var person1 = {name:"John Doe", age:25};
var person2 = {name:"Jane Doe", age:26, dept: 115};

では、リスト 3 のコマンドを実行して新しいコレクションを作成してみましょう。

Creating collections
db.employees.save(person1);
db.employees.save(person2);

MongoDB にはスキーマがないため、person1person2 の列の型が同じである必要はなく、さらには person1 と person2 の列の数が同じである必要もありません。また、MongoDB は動的な性質を持つため、上記コードによってエラーはスローされずに employees が作成されます。ドキュメントを取得するためには find() メソッドを使用します。employees 内のすべてのドキュメントを取得するためには、リスト 4 のように引数なしで find() を呼び出します。

リスト 4. MongoDB の単純なクエリー
> db.employees.find();
// returns
[ 
  {   "_id" : {   "$oid" : "4e363c4dcc93747e68055fa1"   },   
        "name" : "John Doe",   "age" : 25   },
  {   "_id" : {   "$oid" : "4e363c53cc93747e68055fa2"   },   
        "name" : "Jane Doe",   "dept" : 115,   "age" : 26   }
]

_id が主キーと同じ役割をしていることに注意してください。具体的な内容を指定したクエリーを実行する場合、クエリーによって求める対象を示すキーと値のペアを持つ別のオブジェクトを渡す必要があります (リスト 5)。

リスト 5. 1 つの検索パラメーターによってクエリーを実行する
> db.employees.find({name: "John Doe"});
// returns
[ 
  {   "_id" : {   "$oid" : "4e363c4dcc93747e68055fa1"   },   
  "name" : "John Doe",   "age" : 25   }
]

年齢が 25 歳よりも上の従業員に対するクエリーを実行する場合、リスト 6 のコマンドを実行します。

リスト 6. 年齢が 25 歳よりも上の従業員に対するクエリーを実行する
> db.employees.find({age:{'$gt':25}});
// returns
[ 
  {   "_id" : {   "$oid" : "4e363c53cc93747e68055fa2"   },   
  "name" : "Jane Doe",   "dept" : 115,   "age" : 26   }
]

$gt は「・・・よりも大きい」を意味する特殊演算子です。他にもいくつかの修飾子を表 1 に記載します。

表 1. MongoDB で使用できる修飾子
修飾子説明
$gt・・・よりも大きい
$lt・・・よりも小さい
$gte以上
$lte以下
$in配列内に存在するかどうかのチェック。SQL の in 演算子に似ています。

当然ですが、update() を使用してレコードを更新することができます。リスト 7 のようにレコード全体を更新することもできます。

リスト 7. レコード全体を更新する
> db.employees.update({
    name:"John Doe",  // Document to update
    {name:"John Doe", age:27} // updated document
  });

あるいは、リスト 8 のように $set 演算子を使用して 1 つの値のみを更新することもできます。

リスト 8. レコード内の 1 つの値を更新する
> db.employees.update({name:"John Doe", 
     { '$set': {age:27} }
  });

コレクションを空にするためには、引数なしで remove() を呼び出します。例えば employees コレクションから John Doe を削除したい場合にはリスト 9 のようにします。

リスト 9. employees コレクションから John Doe を削除する
> db.employees.remove({"name":"John Doe"});
> db.employees.find();
// returns
[ 
  {   "_id" : {   "$oid" : "4e363c53cc93747e68055fa2"   },   "name" : "Jane Doe",   
      "dept" : 115,   "age" : 26   }
]

MongoDB の入門は、これぐらいで十分です。もちろん、引き続き MongoDB の公式サイトを調べても構いません。この公式サイトには、チュートリアルを備えた便利な Web ベースの対話型 mongodb コマンド・プロンプトや、公式のドキュメントが用意されています。「参考文献」を参照してください。


Django と MongoDB を統合する

Python または Django から MongoDB にアクセスする方法はいくつかあります。第 1 の方法は PyMongo という Python モジュールを使用する方法です。リスト 10 に PyMongo セッションの例を示します。この例では、MongoDB がインストールされており、あるポートで既に 1 つのインスタンスが実行されていることを前提としています。

リスト 10. PyMongo セッションの例
from pymongo import Connection

databaseName = "sample_database"
connection = Connection()

db = connection[databaseName]
employees = db['employees']

person1 = { "name" : "John Doe",
            "age" : 25, "dept": 101, "languages":["English","German","Japanese"] }

person2 = { "name" : "Jane Doe",
            "age" : 27, "languages":["English","Spanish","French"] }

print "clearing"
employees.remove()

print "saving"
employees.save(person1)
employees.save(person2)

print "searching"
for e in employees.find():
    print e["name"] + " " + unicode(e["languages"])

PyMongo を使用すると複数のデータベースを並行して実行することができます。接続を定義するためには、単純にデータベース名を接続インスタンスに渡します。この場合には、新しいドキュメントの定義を作成するために、Python の辞書が JavaScript のオブジェクト・リテラルの代わりとなり、Python のリストが JavaScript の配列の代わりとなります。find メソッドはデータベースのカーソル・オブジェクトを返し、このオブジェクトに対して繰り返し処理を行うことができます。

MongoDB と PyMongo は構文が似ているため、MongoDB のコマンドラインと PyMongo でのコマンドの実行とを切り換えるのは簡単です。例えば、 リスト 11 に示すのは PyMongo でクエリーを実行する方法です。

リスト 11. PyMongo でクエリーを実行する
for e in employees.find({"name":"John Doe"}):
    print e

Python から MongoDB を呼び出す方法には、MongoEngine を使用する方法もあります。Django に組み込みの ORM を使用したことがある人であれば、その ORM と MongoEngine は似ているように思えるはずです。MongoEngine はドキュメントとオブジェクトの間のマッパーであり、概念としては ORM と似ています。リスト 12 に MongoEngine のセッションの例を示します。

リスト 12. MongoEngine のセッションの例
from mongoengine import *

connect('employeeDB')

class Employee(Document):
    name = StringField(max_length=50)
    age = IntField(required=False)

john = Employee(name="John Doe", age=25)
john.save()

jane = Employee(name="Jane Doe", age=27)
jane.save()

for e in Employee.objects.all():
    print e["id"], e["name"], e["age"]

Employee オブジェクトは mongoengine.Document を継承しています。この例では、StringFieldIntField という 2 つのフィールド・タイプを使用しています。Django の ORM の場合と同じように、コレクション内のすべてのドキュメントに対してクエリーを実行するためには、Employee.objects.all() を呼び出します。一意のオブジェクト ID にアクセスするためには “_id” ではなく “id“ を使用することに注意してください。


サンプル・ブログ

では、Blongo という名前の単純なブログを作成しましょう。ここでは、Python 1.7、Django 1.3、MongoDB 1.8.2、MongoEngine 0.4、そして HTML (HyperText Markup Language) 5 を使用します。私とまったく同じ設定を再現したい人のために紹介すると、私は Ubuntu Linux と FireFox を使用しました。Blongo は、ページがロードされると、これまでのブログ記事をすべて表示し、任意の記事を更新、削除できるようにします。つまり標準的なすべての CRUD 操作を行うことができます。Django のビューには、indexupdatedelete という 3 つのメソッドがあります。

CSS (Cascading Style Sheets) は別の静的ファイルで定義されます。ここでは詳細については触れませんが、「ダウンロード」に含まれているソース・コードを調べてみてください。

すべてが問題なくインストールされ、実行されているとして、リスト 13 のように新しい Django プロジェクトと必要なコンポーネントを作成します。

リスト 13. Django ブログ・プロジェクトを設定するためのコマンド
$ django-admin.py startproject blongo
$ cd blongo
$ django-admin.py startapp blogapp
$ mkdir templates
$ cd blogapp
$ mkdir static

Django 1.3 の新機能として、静的ファイルの処理を改善するためのアプリケーションが contrib の中に含まれています。任意のアプリケーション・ディレクトリー (この場合の blogapp など) に static ディレクトリーを追加し、インストールされたアプリケーションに必ず django.contrib.staticfiles を含めるようにすることで、Django は他に何も調整しなくても .css ファイルや .js ファイルなどの静的ファイルを見つけることができるようになります。リスト 14 に、このブログ・アプリケーションを実行させるために (デフォルトの settings.py ファイルから) 変更された設定ファイルの行を示します。

リスト 14. (デフォルトの settings.py ファイルから) 変更された設定ファイルの行
# Django settings for blog project.
import os
APP_DIR = os.path.dirname( globals()['__file__'] )

DBNAME = 'blog'

TEMPLATE_DIRS = (
    os.path.join( APP_DIR, 'templates' )
)

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog.blogapp',
)

このプロジェクトには index.html、update.html、delete.html というテンプレートもあります。リスト 15 に、この 3 つのテンプレート・ファイルすべてのコードを示します。

リスト 15. 3 つのテンプレート・ファイル、index.html、update.html、delete.html のコード
<!-- index.html -->
<!DOCTYPE html>
<html>
  <head>
    <link href="{{STATIC_URL}}blog.css" rel="stylesheet" type="text/css">
  </head> 
  <body>
    <h1>Blongo</h1>
    <form method="post" action="http://127.0.0.1:8000/">
      {% csrf_token %}
      <ul>
        <li>
          <input type="text" name="title" placeholder="Post Title" required>
        </li>
        <li>
          <textarea name="content" placeholder="Enter Content" rows=5 cols=50 required>
          </textarea>
        </li>
        <li>
          <input type="submit" value="Add Post">
        </li>
      </ul>
    </form>
<!-- Cycle through entries -->
    {% for post in Posts %}
      <h2> {{ post.title }} </h2>
      <p>{{ post.last_update }}</p>
      <p>{{ post.content }}</p>
      <form method="get" action="http://127.0.0.1:8000/update">
        <input type="hidden" name="id" value="{{ post.id }}">
        <input type="hidden" name="title" value="{{ post.title }}">
        <input type="hidden" name="last_update" value="{{ post.last_update }}">
        <input type="hidden" name="content" value="{{ post.content }}">
        <input type="submit" name="" value="update">
      </form>
      <form method="get" action="http://127.0.0.1:8000/delete">
        <input type="hidden" name="id" value="{{post.id}}">
        <input type="submit" value="delete">
      </form>
    {% endfor %}
  </body>
</html>

<!-- update.html -->
<!DOCTYPE html>
<html>
  <head>
    <link href="{{STATIC_URL}}blog.css" rel="stylesheet" type="text/css">
  </head> 
  <body>
    <h1>Blongo - Update Entry</h1>
    <form method="post" action="http://127.0.0.1:8000/update/">
      {% csrf_token %}
      <ul>
        <li><input type="hidden" name="id" value="{{post.id}}"></li>
        <li>
          <input type="text" name="title" placeholder="Post Title" 
             value="{{post.title}}" required>
          <input type="text" name="last_update" 
             value="{{post.last_update}}" required>
        </li>
        <li>
          <textarea name="content" placeholder="Enter Content" 
            rows=5 cols=50 required>
            {{post.content}}
          </textarea>
        </li>
        <li>
          <input type="submit" value="Save Changes">
        </li>
      </ul>
    </form>
  </body>
</html>
<!-- delete.html -->
<!DOCTYPE html>
<html>
  <head>
    <link href="{{STATIC_URL}}blog.css" rel="stylesheet" type="text/css">  
  </head> 
  <body>
    <h1>Blongo - Delete Entry</h1>
    <form method="post" action="http://127.0.0.1:8000/delete/">
      {% csrf_token %}
      <input type="hidden" name="id" value="{{id}}">
      <p>Are you sure you want to delete this post?</p>
      <input type="submit" value="Delete">
    </form>
  </body>
</html>

次に、URL のマッピングをリスト 16 のコードに変更します。このコードは、index、update、delete のビューを指しています。サンプル・ブログでは、必要に応じて新しいブログ記事を (index で) 作成したり、既存のブログ記事を (update で) 更新したり、(delete で) 削除したりすることができるようにします。各アクションを実行するためには、特定の URL に POST します。

リスト 16. index、update、delete のための URL のマッピングe
from django.conf.urls.defaults import patterns, include, url

urlpatterns = patterns('',
    url(r'^$', 'blog.blogapp.views.index'),
    url(r'^update/', 'blog.blogapp.views.update'),
    url(r'^delete/', 'blog.blogapp.views.delete'),
)

syncdb という Django コマンドを実行する必要がないことに注意してください。MongoDB をアプリケーションに統合するためには MongoEngine が必要です。blogapp ディレクトリーの models.py ファイルにリスト 17 のコードを追加します。

リスト 17. データ層に MongoEngine を含める
from mongoengine import *
from blog.settings import DBNAME

connect(DBNAME)

class Post(Document):
    title = StringField(max_length=120, required=True)
    content = StringField(max_length=500, required=True)
    last_update = DateTimeField(required=True)

関心を分離するために、データベースの名前は settings ファイルから取得します。各ブログ記事には、titlecontentlast_update という 3 つの必須フィールドがあります。このリスト 17 の内容と Django で通常実行することを比較対照してみると、それほど大きな違いはありません。django.db.models.Model を継承するクラスの代わりに、このリストでは mongoengine.Document クラスを使用しています。ここではデータ型の違いについては説明しませんが、MongoEngine のドキュメントを調べてみてください (「参考文献」を参照)。

表 2 は、MongoEngine のフィールド・タイプの一覧と、(それらのタイプと等価な Django の ORM フィールド・タイプがある場合には) その等価な ORM フィールド・タイプを示しています。

表 2. MongoEngine のフィールド・タイプと、それらと等価な Django の ORM フィールド・タイプ
MongoEngine のフィールド・タイプ等価な Django の ORM フィールド・タイプ
StringFieldCharField
URLFieldURLField
EmailFieldEmailField
IntFieldIntegerField
FloatFieldFloatField
DecimalFieldDecimalField
BooleanFieldBooleanField
DateTimeFieldDateTimeField
EmbeddedDocumentField該当なし
DictField該当なし
ListField該当なし
SortedListField該当なし
BinaryField該当なし
ObjectIdField該当なし
FileFieldFileField

最後に、ビューを設定します。ここでは、indexupdatedelete という 3 つのビュー・メソッドがあります。目的のアクションを実行するためには、そのための特定の URL に対して POST リクエストを実行する必要があります。例えば、ドキュメントを更新するためには localhost:8000/update に対して POST を実行します。HTTP の GET リクエストを実行しても、保存 (save) や更新 (update) 等を実行することはできません。新しいブログ記事は index ビューから挿入します。リスト 18 に、index ビュー、update ビュー、delete ビューの実装を示します。

リスト 18. Django のビュー
from django.shortcuts import render_to_response
from django.template import RequestContext
from models import Post
import datetime

def index(request):
    if request.method == 'POST':
       # save new post
       title = request.POST['title']
       content = request.POST['content']

       post = Post(title=title)
       post.last_update = datetime.datetime.now() 
       post.content = content
       post.save()

    # Get all posts from DB
    posts = Post.objects 
    return render_to_response('index.html', {'Posts': posts},
                              context_instance=RequestContext(request))


def update(request):
    id = eval("request." + request.method + "['id']")
    post = Post.objects(id=id)[0]
    
    if request.method == 'POST':
        # update field values and save to mongo
        post.title = request.POST['title']
        post.last_update = datetime.datetime.now() 
        post.content = request.POST['content']
        post.save()
        template = 'index.html'
        params = {'Posts': Post.objects} 

    elif request.method == 'GET':
        template = 'update.html'
        params = {'post':post}
   
    return render_to_response(template, params, context_instance=RequestContext(request))
                              

def delete(request):
    id = eval("request." + request.method + "['id']")

    if request.method == 'POST':
        post = Post.objects(id=id)[0]
        post.delete() 
        template = 'index.html'
        params = {'Posts': Post.objects} 
    elif request.method == 'GET':
        template = 'delete.html'
        params = { 'id': id } 

    return render_to_response(template, params, context_instance=RequestContext(request))

ドキュメントの ID を取得するために eval 文を使用していることにお気付きの読者もいるのではないでしょうか。eval 文を使用する理由は、リスト 19 に示す if 文を書かずに済ませるためです。

リスト 19. ドキュメントの ID を取得するための別の方法
    if request.method == 'POST':
        id = request.POST['id']
    elif request.method == 'GET':
        id = request.GET['id']

リスト 19 のようなコードにすることもできます。これで、単純なブログを立ち上げて実行状態にするための作業はすべて終わりです。このブログ・アプリケーションを最終的なものとするには、実装する必要があるコンポーネントがまだ数多くあります (ユーザー・コンポーネント、ログイン・コンポーネント、タグ・コンポーネント等々)。

まとめ

ここまでの説明でおわかりのように、Django から MongoDB を呼び出すのは、まったくたいしたことではありません。この記事では MongoDB について簡単に紹介し、PyMongo ラッパーと、オブジェクトとドキュメントの間のマッパーである MongoEngine とを使用して、Python から MongoDB にアクセスする方法と MongoDB のコレクションとドキュメントを操作する方法について説明しました。最後に、Django を使用して基本的な CRUD フォームを作成する方法を簡単に説明しました。この記事は最初のステップにすぎませんが、ここまでの説明から、この記事の設定を皆さんのプロジェクトに応用する方法を理解できたようであれば幸いです。


ダウンロード

内容ファイル名サイズ
Sample Django application with MongoEngineblongo.zip12KB

参考文献

学ぶために

製品や技術を入手するために

  • MongoDB について学び、また MongoDB をダウンロードしてください。
  • Django をダウンロードして試してみてください。
  • Python の Web サイトを訪れてください。ダウンロードやドキュメントが豊富に用意されています。
  • MongoEngine について調べてください。
  • PyMongo について調べてください。
  • 皆さんの目的に最適な方法で IBM 製品を評価してください: 製品の試用版をダウンロードする方法、オンラインで製品を試す方法、クラウド環境で製品を使う方法、あるいは SOA Sandbox で数時間を費やし、サービス指向アーキテクチャーの効率的な実装方法を学ぶ方法などがあります。

議論するために

  • developerWorks コミュニティーに参加してください。ここでは他の developerWorks ユーザーとのつながりを持てる他、開発者によるブログ、フォーラム、グループ、ウィキを調べることができます。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=802009
ArticleTitle=Django で MongoDB を使用する
publish-date=03222012