Google App Engine用フレームワークtipfyの紹介とチュートリアル
このエントリについて
Google App Engine用に作られたフレームワーク「tipfy」の使い勝手がよかったので
チュートリアル形式で簡単に紹介をしたいと思います。
先日エントリにした「するめダイアリー」もGAE/Python + tipfyで作ってます。
おことわり
ちなみに、当方の開発環境は、
- Max OS 10.6
- GoogleAppEngineLauncher
- TextMate
なので、説明もこれに準じたものになってます。
また、
- Pythonの文法
- html、cssの書き方
- Google App Engineのデータストア
に関しては説明を省略させていただきます。
追記しました(12/10)
- テンプレートエンジン jinja2 について
- エントリのデータストアモデルの変更
導入
tipfyのダウンロード
公式サイト
http://www.tipfy.org/
の右側、All-in-one packをダウンロードします。現時点での最新バージョンは0.6.4です。
ダウンロード・解凍が終わったら「project」フォルダを適当な場所に配置・リネームします。
これがプロジェクト全体のフォルダになります。(このパスを「/path/to/project」とします。)
GoogleAppEngineLauncherでプロジェクトをインポート
GoogleAppEngineLauncherを起動し、「Add Existing Application」を選択。
Pathに「/path/to/project/app」を指定します。以降、説明でパスを指定する時はこのフォルダが基準になります。
これで開発を進める準備は完了です。さっそく、左上の「Run」ボタンを押し、
ブラウザで「http://localhost:8080/pretty」にアクセスしてみましょう。
中央に大きく水色で「Hello, World」と表示されたらセットアップは完了です。簡単でしょ?
今回作るもの
早速、開発を進めたいと思います!
今回は例として、簡単なブログシステムを作りたいと思います。
作る画面(機能)一覧。
- ブログのエントリ一覧
- 投稿、更新、削除
- エントリ詳細
データストアモデルの作成(12/10編集)
lib の下に datastore という名前のフォルダ*1を作成します。
そのフォルダの下に
- __init__.py
- model.py
の二つのファイルを作成します。
__init__.pyはPythonにここがパッケージのあるディレクトリであることを教えるためのものです。
model.pyには以下のようにモデルを定義します。
from google.appengine.ext import db class Entry(db.Model): title = db.StringProperty(required=True) content = db.TextProperty() created_at = db.DateTimeProperty(auto_now_add=True) updated_at = db.DateTimeProperty(auto_now=True)
ブログのエントリを表すEntryモデルを定義しました。
- エントリのタイトル
- 内容
- 作成日時
- 更新日時
これがあればとりあえず十分でしょうか。
作成日時には、作成したら自動で現在時刻をつけてくれるオプション「auto_now_add」、
更新日時には、データを更新したら自動で現在時刻をつけてくれるオプション「auto_now」を指定しています。
モデル定義の方法、使えるタイプなどについては
http://code.google.com/intl/ja/appengine/docs/python/datastore/entitiesandmodels.html
http://code.google.com/intl/ja/appengine/docs/python/datastore/typesandpropertyclasses.html
などが詳しいです。
管理者用アプリの作成
一覧機能…を作る前に
さっそく、エントリ一覧機能を作りたい所なのですが、
まだ機能を作ってもデータストアの中身が空っぽなので一覧機能の動作確認ができません。
なので、ここは管理者用のモジュールを別に作って、
そこにテストデータをデータストアに入れる処理を書いていこうと思います。
アプリの追加
apps以下に internal という名前のフォルダを追加します。
既にサンプルとして hello_world というアプリが入っている*2ので、それをコピペするのが楽かと思います。
中身は以下の3つのファイルになっています。
- __init__.py
- handlers.py
- urls.py
開発に入る前に、それぞれのファイルの役割を詳しく説明します。
ルーティング -> リクエストハンドラ -> テンプレート
リクエストハンドラ
handlers.py にはリクエストを受けた時の処理内容を書きます。中身はこんな感じになってるはずです。
from tipfy import RequestHandler, Response from tipfy.ext.jinja2 import render_response class HelloWorldHandler(RequestHandler): def get(self): """Simply returns a Response object with an enigmatic salutation.""" return Response('Hello, World!') class PrettyHelloWorldHandler(RequestHandler): def get(self): """Simply returns a rendered template with an enigmatic salutation.""" return render_response('hello_world.html', message='Hello, World!')
RequestHandlerを拡張したHelloWorldHandler、PrettyHelloWorldHandlerというクラスが書かれています。
リクエストを受け取ったら、〜〜する、という処理内容が
def get(self):
の中に記述されています。たとえば、HelloWorldHandler であったら、
- 中身が 'Hello, World!' となるレスポンスを返す
ようになっていて、PrettyHelloWorldHandlerであったら、
- テンプレート 'hello_world.html' を message='Hello, World!' であるコンテキストを元に解釈して返す
という感じになっています。
テンプレート
テンプレート 'hello_world.html' は templates直下にあって、
<html> <head> <title>{{ message|e }}</title> <style type="text/css"> /* 省略 */ </style> </head> <body> <h1>{{ message|e }}</h1> </body> </html>
こんなファイルになっています。ここで 「{{ }}」で囲まれた部分が、
コンテキストに設定した変数の内容を出力するようになっていて、
例えば、message='Hello, World!' のとき、{{ message }} ならば、
Hello, World!
と出力されます。
(12/10追記)
これはtipfyの機能ではなくて、jinja2 というテンプレートエンジンの機能です。
イメージ的には、tipfyが内部でjinja2を呼び出して使っている、といった感じです。
このようにテンプレートエンジンを使えば、htmlの動的生成がグッと楽になります。
jinja2の公式サイトはこちらにあります。↓
http://jinja.pocoo.org/
テンプレートのエスケープ処理
先ほどのファイル 'Hello, World!' には、
<h1>{{ message|e }}</h1>
という部分がありました。{{ message }} の横についている 「|e」とは何者なのか?
これはXSS*3対策のためのエスケープ処理を行っている部分です。
試しにテンプレートの
<h1>{{ message|e }}</h1>
この部分を
<h1>{{ message }}</h1>
として、hello_world/handlers.pyで
return render_response('hello_world.html', message='Hello, World!')
の部分を
return render_response('hello_world.html', message='Hello,<br/> World!')
で置き換えてみて、
http://localhost:8080/pretty
にアクセスしてみてください。エスケープ処理が行われず、改行が入っていることが確認できるかと思います。
ルーティング処理
説明が戻りますが、urls.pyを開いてみてください。
次のような内容になっているはずです。
from tipfy import Rule def get_rules(app): rules = [ Rule('/', endpoint='hello-world', handler='apps.hello_world.handlers.HelloWorldHandler'), Rule('/pretty', endpoint='hello-world-pretty', handler='apps.hello_world.handlers.PrettyHelloWorldHandler'), ] return rules
ここには、どのリクエストのURLが来た場合、どのリクエストハンドラを実行するか、
ということが書かれています。例えば、URLが
http://(ドメイン)/pretty
だった場合は、hello_worldモジュールのPrettyHelloWorldHandlerを実行するようになっています。
このrulesに、上記の例に倣ってRuleを追加していくことでルーティングのルールを増やせます。
管理者用アプリの作成(続き)
リクエストハンドラの作成
apps/internal/handlers.pyに以下の内容を追加します。
from tipfy import RequestHandler, Response from tipfy.ext.jinja2 import render_response from datastore.model import * class AddEntryHandler(RequestHandler): def get(self): for i in range(10): entry_model = Entry( title = "entry " + str(i), content = "content\ncontent\ncontent", ) entry_model.put() return Response('Done!')
ルーティング処理
apps/internal/urls.pyに以下の内容を追加します。
def get_rules(app): rules = [ Rule('/internal/addentry', endpoint='internal/addentry', handler='apps.internal.handlers.AddEntryHandler'), ] return rules
アプリの追加
config.pyにアプリ「internal」を追加します。
これをしないとアプリが認識されません。
'apps_installed': [ 'apps.hello_world', 'apps.internal', ],
実行
urls.pyに書いた通り、
http://localhost:8080/internal/addentry
にアクセスして実行させます。Done! が表示されたら成功!
SDK Consoleを表示させ、Datastore Viewerをのぞいてデータが追加されているか確かめてみましょう!
データストアの一括削除
SDK Consoleには残念ながらデータストアの中身をまっさらに消すツールはついてきません。
なので、これも internalアプリに追加しておくことをお勧めします。
apps/internal/handlers.pyに以下の内容を追加します。
class InitHandler(RequestHandler): def get(self): for entry_model in Entry.all(): entry_model.delete() return Response('Done!')
もちろん、ルーティング設定も忘れずに行います。
rules = [ Rule('/internal/addentry', endpoint='internal/addentry', handler='apps.internal.handlers.AddEntryHandler'), Rule('/internal/init', endpoint='internal/init', handler='apps.internal.handlers.InitHandler'), ]
実行
http://localhost:8080/internal/init
にアクセスして実行させます。Datastore Viewerを見てデータが消えていたら成功!
今回はここまで。
次回以降はエントリ一覧機能から作ります。