「Using Django with Appengine」邦訳
ようやく少しずつ時間が取れるようになってきたので、 Google App Engine の勉強をはじめています。 Python の書き方や App Engine 自体の使い方を覚えるのは苦ではないのですが、大変なのはその先、ライブラリやフレームワークなどを把握するのが厄介なんですよね。
まあとりあえずは App Engine に付属している Web フレームワーク Django に挑戦しようと思い、良いドキュメントはないかと探してみたところ、「Using Django with Appengine」という、そのものズバリなページを発見しました。 Django のチュートリアルを App Engine 用に書き直したもののようです。素晴らしい。著者の Shabda Raaj 氏に感謝です。
そんなわけで、勉強ついでにそのページを翻訳してみました。いつものごとくフィーリングで訳しているので、あまり人前に出せるような出来ではないのですが、それでも無いよりはマシだろうということで公開します。これから App Engine を使ってみようという方は参考にしてください。誤訳などのご指摘も大歓迎です。
Using Django with Appengine
著者: | Shabda Raaj |
バージョン: | 1 |
Copyright: | このドキュメントは Creative Commons Attribution license で配布されます。 |
翻訳: | 伊藤千光 |
原文: | http://www.42topics.com/dumps/django/docs.html |
Contents
About
Django を App Engine に統合するためのチュートリアルにようこそ。この文書は純粋な Django の代わりに App Engine を使用する Django チュートリアルの改変版です。 Djaongo チュートリアルと同様に、複数の質問を作成してユーザーがそれらに投票できる投票エンジンを構築します。 Django チュートリアルの 4 つのパートの代わりに、ここではただひとつのチュートリアルを使います。また、多くの Django の機能と同様に、傑出した素晴らしい管理インターフェースは動作しないので、それらも回避することにします。このチュートリアルは皆さんが Python をよく知っていることを前提にしていますが、 Django の予備知識は不要です。 Django のコンセプトから解説しています。
What will we build
ここではこれより、人々が質問を作成し、その選択肢に投票することができる投票エンジンを構築します。基本的にこのアプリケーションは以下の 4 つのタイプのページを持ちます。
- 最近作成された質問のリスト。
- 質問の新規作成ページ。
- 選択肢に投票するためのページ。
- 集計結果のページ。
ライブデモが blogango.appspot.com にて公開されています。完全なソースはここからダウンロードできます。
Downloading Django, and Appengine
Dojango のダウンロード方法はとてもわかりやすいので、ここでは繰り返しません。ましてや皆さんは既にApp Engine をダウンロードしていますよね?それならば、 Django のダウンロード方法はこのチュートリアルには必要ありません。 App Engine は Django 0.96 ビルドを含んでいるからです。
Getting Help
もしこのチュートリアルでトラブルに遭遇したら、 Django フォーラム で質問してみてください。 Django コミュニティーの誰かが助けてくれるでしょう。もしくは、 42topics.com blog にコメントを残してくれれば、私も力になれるよう努めます。
Creating a project
App Engine と Django のすべてのファイルを格納するディレクトリをお好みの場所に作成し、そのディレクトリの中で以下のコマンドを実行します:
django-admin.py startproject appproject
このコマンドは Django で使われるすべてのファイルを格納するディレクトリを appproject
という名前で作成します。すぐに我々はその中身を見ていくことになりますが、その前にさらに 2 つのファイルを作成する必要があります。 appproject
フォルダーと同じ階層に main.py
というファイルを作成し、内容を以下のようにします:
import os,sys os.environ['DJANGO_SETTINGS_MODULE'] = 'appproject.settings' # Google App Engine imports. from google.appengine.ext.webapp import util # Force Django to reload its settings. from django.conf import settings settings._target = None import django.core.handlers.wsgi import django.core.signals import django.db import django.dispatch.dispatcher # Log errors. #django.dispatch.dispatcher.connect( # log_exception, django.core.signals.got_request_exception) # Unregister the rollback event handler. django.dispatch.dispatcher.disconnect( django.db._rollback_on_exception, django.core.signals.got_request_exception) def main(): # Create a Django application for WSGI. application = django.core.handlers.wsgi.WSGIHandler() # Run the WSGI CGI handler with that application. util.run_wsgi_app(application) if __name__ == '__main__': main()
さらに app.yaml
という名前で別のファイルを作成し、内容を以下のようにします:
application: appproject version: 1 runtime: python api_version: 1 handlers: - url: /.* script: main.py
これら 2 つのファイルは App Engine に我々のアプリケーションに関するいくつかの事柄と、リクエストの処理に Django を利用することを伝えています。 url: /.*
という行は、すべての URL のリクエストを main.py が処理することを示しています。
以下のコードは Django wsgi サーバーを作成し、それにリクエストを委譲しています。
def main(): # Create a Django application for WSGI. application = django.core.handlers.wsgi.WSGIHandler() # Run the WSGI CGI handler with that application. util.run_wsgi_app(application)
appproject フォルダに移動して、このコマンドを実行してください:
python manage.py startapp poll
manage.py
は django-admin.py
によって作られたファイルです。皆さんの Django アプリケーションを管理するためのコマンドを含んでいます。 python manage.py startapp poll
はこのアプリケーションに関連するすべてのファイルを保持するディレクトリを作成します。このディレクトリにある、もうひとつの興味深いファイルが settings.py
です。ここには皆さんのプロジェクトの設定が書かれています。
Editing the settings.py file
純粋な Django と Django + App Engine のもっとも大きな相違点は ORM です。 App Engine は従来のデータベースサーバーにはデータを保存しないので、 Django ORM は App Engine では動作しません。 Django にバンドルされている多くのアプリケーション(とくに admin
, auth
, sessions
など)も動作しません。 django-admin.py startproject appproject
で作成した settings.py
は皆さんがこれらのアプリを使うことを想定しているので、それらのアプリケーションを settings.py から取り除かなければなりません。
settings.py を編集し、 MIDDLEWARE_CLASSES
と INSTALLED_APPS
を以下のように変更します:
MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', ) INSTALLED_APPS = ( 'appproject.poll' )
さらに、ページを表示する土台として使うファイルがどこにあるのかを、 Django に教えてやらなければなりません。 TEMPLATE_DIRS
を以下のように変更します:
import os ROOT_PATH = os.path.dirname(__file__) TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates" or # "C:/www/django/templates". Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. ROOT_PATH + '/templates', )
これは HTML テンプレートをカレントディレクトリからの相対で templates
という名前のディレクトリから探すように Django に指示しています。
Write the URL configuration file
Django は与えられた URL をどの関数が処理するのかを示すために、正規表現を使います。 urls.py ファイルを以下のように編集します:
from django.conf.urls.defaults import * urlpatterns = patterns('', (r'^$', 'pollango.poll.views.index'), (r'^create/$', 'pollango.poll.views.create'), (r'^poll/(?P<poll_key>[^\.^/]+)/$', 'pollango.poll.views.poll_detail'), (r'^poll/(?P<poll_key>[^\.^/]+)/results/$', 'pollango.poll.views.poll_results'), )
我々は 4 つの異なったタイプのページを持っており、それぞれのページが個々にマップされています。 (r'^$', 'pollango.poll.views.index'),
という行を見てみましょう。正規表現 r'^$'
はサイトルートにマッチします(^
は URL のパス部分の先頭にマッチし、 $
は末尾にマッチするので、結果的に追加のパスを持たない URL がこの行でハンドルされます)。 'pollango.poll.views.index'
はその URL が呼び出された際にこの関数が使われることを示しています。
同様に、 (r'^create/$', 'pollango.poll.views.create'),
は \create\
を含む URL が pollango.poll.views.create
でハンドルされることを示しています。
Write the models.py file.
データモデルは models.py ファイルで定義する必要があります。今回の場合、質問を保存する Poll と関連する選択肢を保存する Choice の 2 つのエンティティーがあります。したがって、 models.py
ファイルには以下のように記述します:
from google.appengine.ext import db class Poll(db.Model): question = db.StringProperty() created_on = db.DateTimeProperty(auto_now_add = 1) created_by = db.UserProperty() def __str__(self): return '%s' %self.question def get_absolute_url(self): return '/poll/%s/' % self.key() class Choice(db.Model): poll = db.ReferenceProperty(Poll) choice = db.StringProperty() votes = db.IntegerProperty(default = 0)
Poll のデータモデルに注目しましょう:
class Poll(db.Model): question = db.StringProperty() created_on = db.DateTimeProperty(auto_now_add = 1) created_by = db.UserProperty()
データベースへの保存を行うエンティティーは google.appengine.ext.db.Model
を継承しなければいけないので、 Poll
もそうしています。エンティティーのそれぞれのアトリビュートは google.appengine.ext.db.*Property
型である必要があります。例えば、整数を保存するアトリビュートは google.appengine.ext.db.IntegerProperty
型にします。
Poll
の場合、
- 質問を保存する
question
アトリビュートはdb.StringProperty
型 - 質問者を保存する
created_by
はdb.UserProperty()
型 - 作成時刻を保存する
created_on
はdb.DateTimeProperty
型
となっています。 created_on
のキーワード引数 auto_now_add はオブジェクトが最初に作成された時刻を保存するように ORM に指示しています。
以下のメソッドは文字列表現と Poll の URL の取得に使われます。
def __str__(self): return '%s' %self.question def get_absolute_url(self): return '/poll/%s/' % self.key()
Choice
も同様に db.model を継承し、アトリビュートを定義します。
class Choice(db.Model): poll = db.ReferenceProperty(Poll) choice = db.StringProperty() votes = db.IntegerProperty(default = 0)
Writing the forms
Django は HTML フォームを django.newsforms.Form
型の Python オブジェクトで表現することで状態を追跡します。
今回の場合、 2 つのフォームが必要です。
- 質問の作成。
- 選択肢の作成。
それでは新規ファイル bforms.py を作成し、以下の内容にしてください:
from django import newforms as forms import models from google.appengine.ext.db import djangoforms class PollForm(djangoforms.ModelForm): class Meta: model = models.Poll exclude = ['created_by'] class ChoiceForm(forms.Form): choice = forms.CharField(max_length = 100) def __init__(self, poll=None, *args, **kwargs): self.poll = poll super(ChoiceForm, self).__init__(*args, **kwargs) def save(self): choice = models.Choice(poll = self.poll, choice = self.clean_data['choice']) choice.put()
それぞれのフォームを順番に見ていきましょう:
class PollForm(djangoforms.ModelForm): class Meta: model = models.Poll exclude = ['created_by']
PollForm
は新しい質問を作成するためのフォームです。 google.appengine.ext.db.djangoforms.ModelForm
によって App Engine エンティティーに関連する Django フォームを作成できます。 model = models.Poll
はどのエンティティーのためのフォームかを定義しています。 exclude = ['created_by']
は Django に created_by のためのフィールドを作らないように指示しています。
次は ChoiceForm です:
class ChoiceForm(forms.Form): choice = forms.CharField(max_length = 100) def __init__(self, poll=None, *args, **kwargs): self.poll = poll super(ChoiceForm, self).__init__(*args, **kwargs) def save(self): choice = models.Choice(poll = self.poll, choice = self.clean_data['choice']) choice.put()
特定の質問の選択肢のためのフォームなので、 forms.Form を継承して、必要なフィールドを定義しています。 choice = forms.CharField(max_length = 100)
は Django に HTML の Textfield
を作成するように指示しています。
def save(self):
はこの質問に関連するデータが保存されたときのアクションを定義しています。 choice = models.Choice(poll = self.poll, choice = self.clean_data['choice'])
で Choice オブジェクトを作成し、 choice.put()
でデータベースに保存しています。 .clean_data
はフィールドに関連するデータの検索方法です。
PollForm
には .save メソッドを定義しませんでしたが、これは ModelForm
が関連するオブジェクトにデータを保存するデフォルトの .save メソッドを持っているためです。
Write the view
作成中のアプリケーションには 4 つのタイプのページがあり、 urls.py には 4 つのエントリーがあります。従って、 4 つの URL パターンにマップされる 4 つの関数を作成する必要があります。 views.py を以下のように変更してください。もし理解できなくてもご心配なく。後で解説します。
from django.http import HttpResponse, HttpResponseRedirect from pollango.poll import models import bforms from django.shortcuts import render_to_response def render(template, payload): payload['recents'] = models.Poll.all().order('-created_on').fetch(5) return render_to_response(template, payload) def index(request): polls = models.Poll.all().order('-created_on').fetch(20) payload = dict(polls = polls) return render('index.html', payload) def create(request): if request.method == 'GET': pollform = bforms.PollForm() choiceforms = [] for i in range(4): choiceforms.append(bforms.ChoiceForm(prefix = 'f%s'%i)) if request.method == 'POST': pollform = bforms.PollForm(request.POST) choiceform = bforms.ChoiceForm() if pollform.is_valid(): poll = pollform.save() choiceforms = [] for i in range(4): choiceforms.append(bforms.ChoiceForm(poll=poll, prefix = 'f%s'%i, data=request.POST)) for form in choiceforms: if form.is_valid(): form.save() return HttpResponseRedirect(poll.get_absolute_url()) payload = dict(pollform=pollform, choiceforms=choiceforms) return render('create.html', payload) def poll_detail(request, poll_key): poll = models.Poll.get(poll_key) choices = models.Choice.all().filter('poll = ', poll) if request.method == 'POST': choice_key = request.POST['value'] choice = models.Choice.get(choice_key) choice.votes += 1 choice.put() return HttpResponseRedirect('./results/') payload = dict(poll = poll, choices = choices) return render('poll_details.html', payload) def poll_results(request, poll_key): poll = models.Poll.get(poll_key) choices = models.Choice.all().filter('poll = ', poll) payload = dict(poll = poll, choices = choices) return render('poll_results.html', payload)
def index(request)
は URL にレスポンスする際に呼ばれる関数その 1 です。これは (r'^$', 'pollango.poll.views.index')
という行で定義されています。
def index(request): polls = models.Poll.all().order('-created_on').fetch(20) payload = dict(polls = polls) return render('index.html', payload)
polls = models.Poll.all().order('-created_on').fetch(20)
という行の中で、 models.Poll.all() はすべての Poll
オブジェクトを取得し、 .order('-created_by')
でそのクエリーセットを created_by
の降順でソートし、そして .fetch(20)
で最大 20 オブジェクトに制限します。これは以下の SQL と同等です:
SELECT * FROM poll ORDER BY created_on DESC LIMIT 0, 30
このクエリーセットは遅延評価されるので、使用したオブジェクトのみがロードされます。
これを HTML に変換するために、表示に必要なオブジェクトの辞書を引数にして render
を呼び出します。 render
がさらに render_to_response(template, payload)
へオブジェクトの辞書を引き渡し、そしてテンプレートを使用します。テンプレートがどのように動作するかは後述します。
二番目のビュー関数は create
です:
def create(request): if request.method == 'GET': pollform = bforms.PollForm() choiceforms = [] for i in range(4): choiceforms.append(bforms.ChoiceForm(prefix = 'f%s'%i)) if request.method == 'POST': pollform = bforms.PollForm(request.POST) choiceform = bforms.ChoiceForm() if pollform.is_valid(): poll = pollform.save() choiceforms = [] for i in range(4): choiceforms.append(bforms.ChoiceForm(poll=poll, prefix = 'f%s'%i, data=request.POST)) for form in choiceforms: if form.is_valid(): form.save() return HttpResponseRedirect(poll.get_absolute_url()) payload = dict(pollform=pollform, choiceforms=choiceforms) return render('create.html', payload)
この関数は質問の新規作成フォームの表示とそれが送信された際の処理を担当します。 HTTP GET リクエストではフォームを表示し、 HTTP POST リクエストでは新しい質問を作成します:
if request.method == 'GET': pollform = bforms.PollForm() choiceforms = [] for i in range(4): choiceforms.append(bforms.ChoiceForm(prefix = 'f%s'%i))
このコードは Poll エンティティーオブジェクトを作成するためのフォームと Choice
に関連したフォームを作成します。それらのフォームは bforms.py
で定義されています。
if request.method == 'POST': pollform = bforms.PollForm(request.POST) choiceform = bforms.ChoiceForm() if pollform.is_valid(): poll = pollform.save() choiceforms = [] for i in range(4): choiceforms.append(bforms.ChoiceForm(poll=poll, prefix = 'f%s'%i, data=request.POST)) for form in choiceforms: if form.is_valid(): form.save() return HttpResponseRedirect(poll.get_absolute_url())
POST リクエストが行われると、 request.method
の値が POST になります。その場合はユーザーが値を入力したかどうかを pollform.is_valid()
と form.is_valid()
でチェックします.値が入力されていた場合は form.save()
が実行され、それが obj.put
を呼び出してオブジェクトをデータベースに保存します。
そして表示されるべきオブジェクトがあれば、 render
にそれらを引き渡します。
poll_detail
と poll_results
は Poll
とそれに関連する Choice
を表示します。もし poll のキーが xxxyyyyzzzz
なら、 poll_detail は /poll/xxxyyyyzzzz/
へのレスポンスとして呼び出されます。この場合 poll_details
には xxxyyyyzzzz
が poll_key
として、リクエストデータについての HttpRequest
オブジェクトが第一引数として渡されます。
def poll_detail(request, poll_key): poll = models.Poll.get(poll_key) choices = models.Choice.all().filter('poll = ', poll) if request.method == 'POST': choice_key = request.POST['value'] choice = models.Choice.get(choice_key) choice.votes += 1 choice.put() return HttpResponseRedirect('./results/') payload = dict(poll = poll, choices = choices) return render('poll_details.html', payload)
models.Poll.get(poll_key)
は poll_key
をキーとするオブジェクトをひとつ取得します。これは以下の SQL クエリーと同等です:
SELECT * FROM poll WHERE key = poll_key
choices = models.Choice.all().filter('poll = ', poll)
はこの Poll
オブジェクトに関するすべての Choice
オブジェクトを取得します。これは以下と同じです:
SELECT * FROM choice WHERE poll_id = poll.id
直接 filter('poll = ', poll)
と記述できることに注意してください。このキーの参照と逆参照は暗黙的に行われます。
Poll オブジェクトと Choice オブジェクトが取得できたら、それらを render に渡してページに表示します。
poll_results もほぼ同様に同じオブジェクトを取得しますが、それを別のページに表示します。
Writing the templates
もし PHP やそれに似た言語をご存知なら、 Django テンプレートは窮屈に感じるかもしれません。テンプレートはプレゼンテーションロジックを意味しているので、 python のすべての機能の代わりに単純なテンプレート言語を使います。
余談はこれくらいにして、以下がインデックスページのテンプレートです:
{% extends 'base.html' %} {% block title %} Pollengine - A polling app built with Django, and Appengine {% endblock %} {% block contents %} <h2>Pollengine - A polling app built with Django, and Appengine </h2> {% for poll in polls %} <div class="poll"> <a href="{{poll.get_absolute_url}}">{{poll.question}}</a> <br> By {{poll.user}} on {{poll.created_on|date}} </div> {% endfor %} {% endblock %}
2 つの構造が見て取れると思います。 {% … %}
はタグと呼ばれ、ループのようなプログラミング構造を実現します。 {{ .. }}
はテンプレートに渡されたオブジェクトへのアクセスを可能にします。例えば、関数 index
の中では polls
変数をテンプレートに渡しているので、必要に応じて {{polls}} が使えます。そしてタグにはさらに興味深い事柄があります。
ほとんどのサイトにはナビゲーション項目のような共通の要素があります。それらをそれぞれのページに記述するのはとても無駄なことです。 Django ではテンプレートを拡張することができます。ベーステンプレートでは子テンプレートでオーバーライドできる要素を定義できます。 index.html
の親テンプレートを見てみましょう。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title> {% block title %} Page title {% endblock %} </title> <style type="text/css"> <!-- .contents{ width: 70%; float: right; } .sidebar{ width: 25%; float: right; } --> </style> </head> <body> <div class="contents"> {% block contents %} asdf {% endblock %} </div> <div class="sidebar"> {% block sidebar %} <h3>Meta</h3> <ul> <li> <a href="/create/"> Create new Poll</a> </li> </ul> <h3>Recent Polls</h3> <ul> {% for poll in recents %} <li> <a href="{{poll.get_absolute_url}}">{{poll.question}}</a> </li> {% endfor %} </ul> {% endblock %} </div> </body> </html>
これは単純な HTML ページです。しかし、子テンプレートがオーバーライドして個々の情報を挿入できるフックが 3 つ提供されています。 {% block contents %}
がそのようなフックの開始を指示しています。ブロックは {% endblock %}
で終了します。 Index.html
は {% block contents %}
を再度定義することでオーバーライドします。 {% extends 'base.html' %}
というステートメントは Django に index.html
の親テンプレートを伝えます。
{% for poll in polls %}
はループ構造で、このページに渡されたそれぞれの質問に対して働きます。このループの終端は {% endfor %} です。このループの内部では {{poll}}
を使ってカレントオブジェクトにアクセスできます。
ほかにも index 以外のページに対応する 3 つのテンプレートがあります。それらはとても似通っているので、詳しく解説はしません。
create.html:
{% extends 'base.html' %} {% block contents %} <form action="." method="post"> {{pollform.as_p}} {% for form in choiceforms %} {{form.as_p}} {% endfor %} <input type="submit" name="createpoll" value="createpoll" /> </form> {% endblock %}
poll_details.html:
{% extends 'base.html' %} {% block title %} {{poll.question}} {% endblock %} {% block contents %} <div class="poll"> {{poll.question}} <br /> {{poll.created_on|date}} </div> <br /> <form action="." method="post"> {% for choice in choices %} <div class="choice"> {{ choice.choice }} <input type="radio" name="value" value="{{ choice.key }}"> </div> {% endfor %} <input type="submit" name="dovote" value="Choose" /> </form> {% endblock %}
poll_results.html:
{% extends 'base.html' %} {% block title %} {{poll.question}} - results {% endblock %} {% block contents %} <div class="poll"> {{poll.question}} <br /> {{poll.created_on|date}} </div> <br /> {% for choice in choices %} <div class="choice"> {{ choice.choice }} {{choice.votes}} </div> {% endfor %} {% endblock %}
What next
皆さんがこのチュートリアルを読んで Django の可能性に夢中になってくれることを望みます。 Django forum では Django についてさらに学び、そして行き詰まったときに助けを得ることができます。もしこのチュートリアルに関して疑問があれば 42topics.com blog にコメントしてください。力になれるよう努めます。
Links
皆さんはこれらのリンクにも興味を持つでしょう。
詳しくはこちらの記事をどうぞ!
この記事にコメントする