April 30, 2008

Google App Engine で Web アプリケーション ~ その2

Google App Engine は、Django というフレームワークがデフォルトで利用できます。この Django には強力なテンプレートエンジンが含まれています。

前回までのような方法で画面を出力するのは少々骨が折れますが、Django のテンプレートエンジンを使うと HTML ベースの画面を作りやすくなります。

Javaで言うところの、Velocity や FreeMarker のようなモノですね。


というわけで、今回はこれを利用してみました。


テンプレートとなるファイルを作成する

拡張子は .html で OK です。


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>ようこそ</title>
</head>
<body>
  <center>
    <br />
    <h3>ようこそ!{{username}}さん!</h3>
    <br /><br />
    <a href="/index.html">戻る</a>
  </center>
</body>
</html>


Django テンプレートでは、動的に値を埋め込みたい箇所は、{{...}} で囲み、括弧の中に変数名を記述しておきます。オブジェクトの属性にアクセスするには、user.username のように、. (ドット) で区切ってネストを指定します。


テンプレート出力処理を行うには

こんな風に書きます。

# テンプレートを取得
templatefile = os.path.join(os.path.dirname(__file__), 'テンプレートファイル名')
# パース・コンパイル・出力処理
self.response.out.write(
  template.render(templatefile, {'変数名':オブジェクト}))

template.render() メソッドの引数には、出力したいオブジェクトと、そのオブジェクトの変数名を連想配列の形式で記述します。複数指定したい場合は

template.render(templatefile, {'変数名1':オブジェクト1, '変数名2':オブジェクト2, '変数名3':オブジェクト3}))


のように書きます。



というわけで、前回のアプリケーションを、Django テンプレートを使ったものに書き換えてみると、こうなります。


[index.html]

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>ようこそ!</title>
</head>
<body>
  <form action="welcome" method="post">
    <center>
      <br />
      <h3>お名前は?</h3>
      <input type="text" name="username" size="30" />
      <br /><br />
      <input type="submit" value="送信" />
    </center>
  </form>
</body>
</html>


前回と同一です。


[WelcomeRequestHandler.py]

# coding: UTF-8

import os
import wsgiref.handlers

from google.appengine.ext import webapp
from google.appengine.ext.webapp import template

# webappのインポート
from google.appengine.ext import webapp

class WelcomeRequestHandler(webapp.RequestHandler):

  # HTTP post リクエスト処理
  def post(self):
      # リクエストパラメータ "username" の取得
      userName = self.request.get("username")

      # テンプレートファイルを取得
      templatefile = os.path.join(os.path.dirname(__file__), 'welcome.html')
      # パース・コンパイル・出力処理
      self.response.out.write(
        template.render(templatefile, {'username':userName.encode('UTF-8')}))

def main():
  # リクエスト URL パターンと、実行するクラスの関連づけ
  application = webapp.WSGIApplication([('/welcome', WelcomeRequestHandler)],
           debug=True)
  wsgiref.handlers.CGIHandler().run(application)

# アプリケーション起動時に main() 関数が実行されるようにする
if __name__ == "__main__":
  main()


前回は、HTML タグなどを直接出力していましたが、今回はテンプレートを利用してレスポンス出力を行っています。


[welcome.html]

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>ようこそ</title>
</head>
<body>
  <center>
    <br />
    <h3>ようこそ!{{username}}さん!</h3>
    <br /><br />
    <a href="/index.html">戻る</a>
  </center>
</body>
</html>


「ようこそ!{{username}}さん!」の箇所に注目です。WelcomeRequestHandler.py で、リクエストパラメータから取得した値を "username" という名前でテンプレートオブジェクトとして指定しました。この welcome.html では、その "username" という名前の変数の値を出力しています。


[app.yaml]

application: liebejudith-helloworld
version: 1
runtime: python
api_version: 1

handlers:
- url: /welcome
  script: WelcomeRequestHandler.py
- url: /index\.html
  static_files: index.html
  upload: index.html
- url: /
  static_files: index.html
  upload: index.html


前回と同一です。


実行結果




実行結果も前回と同一です。


HTML センシティブな文字・記号のエスケープについて

Webアプリケーションでは、正しい表示やクロスサイトスクリプティング対策のために、< や、> など、HTML や JavaScript コードとして認識されてしまう文字をエスケープする必要があります。
現在のバージョンの Django では、デフォルトでエスケープが有効になっています。もし何らかの理由でエスケープ処理を無効にしたい場合は、


こんにちは {{ username|safe }} さん


のように、safeフィルタを利用します。
あるいは、


{% autoescape off %}
    こんにちは {{ username }} さん
{% endautoescape %}


のように、autoescape タグを使って、off オプションを指定します。Django テンプレートでは、タグの表記に {% ... %} を使います。


ループ処理を行うには

for タグを使います。Velocity や FreeMarker を使ったことのある方なら直感的におわかりでしょう。


{% for 要素 in リスト %}
    ・・・・
{% endfor %}


ループの中では、次の変数を利用できます。

forloop.counterループカウンタ(1から開始)
forloop.counter0ループカウンタ(0から開始)
forloop.revcounterループの終わりからのインデックス(1から開始)
forloop.revcounter0ループの終わりからのインデックス(0から開始)
forloop.firstループの最初の繰り返しの場合、True になる
forloop.lastループの最後の繰り返しの場合、True になる



条件分岐を行うには

if タグあるいは ifequal/ifnotequal タグを使います。

if タグを使った評価

指定した変数が存在するか・空ではないか(リストの場合)・Trueかを評価します。


{% if name_list %}
    ・・・・
{% else %}
    ・・・・
{% endif %}


or not and を使って複合的な評価も可能です。


{% if name_list or order_list %}
    ・・・・
{% endif %}



{% if not name_list %}
    ・・・・
{% else %}
    ・・・・
{% endif %}



{% if name_list and order_list %}
    ・・・・
{% endif %}



{% if name_list and not order_list %}
    ・・・・
{% endif %}



ifequal/ifnotequal タグを使った評価

2つの値が同一かどうかを比較します。
ifnotequal は同一でないかどうかを比較します。
2つの値はスペース区切りで指定します。


{% ifequal user.id comment.user_id %}
    ...
{% endifequal %}



{% ifnotequal user.username "liebejudith" %}
    ...
{% endifequal %}




画面部品をインクルードするには

include タグを使います。
ファイル名は、文字列リテラルで直接指定することもできますし、変数を使っての動的指定も可能です。


{% include "foo/bar.html" %}



{% include template_name %}




コメントを記述するには

{# ... #} で囲みます。


{# comment #}




テンプレートの継承

Django では、テンプレートを継承することができます。この機能は、同一レイアウト、同一部品を使った似たデザインの画面が多数存在する場合に非常に便利です。
子テンプレートでオーバーライドする部分は、{% block ブロック名 %} ~ {% endblock %} で囲んで記述しておきます。ブロック名はユニークな任意の名前です。


<html>
<head>
    <title>{% block title %}タイトル{% endblock %}</title>
</head>

<body>
    <div id="content">
        {% block content %}{% endblock %}
    </div>
</body>
</html>


あるテンプレートを継承したテンプレートを作成するには、{% extends "親テンプレート名" %} を記述します。そして、親で定義された {% block ブロック名 %} ~ {% endblock %} を任意にオーバーライドできます。


{% extends "base.html" %}

{% block title %}子ページ{% endblock %}

{% block content %}
content 部分のオーバーライド
{% endblock %}




変数値の出力、条件分岐、ループ処理、インクルード、テンプレートの継承と見てきました。これでたいていの画面は作成できるのではないでしょうか?


Django の翻訳ドキュメントも見つけました。Yasushi Masuda さん、Takanao Endoh さんという方が翻訳されています。

Django
http://ymasuda.jp/python/django/index.html

Django オンラインドキュメント和訳
http://michilu.com/django/doc-ja/index/

Django は、テンプレートエンジンだけでなくアプリケーション全体をカバーするフルスタックのフレームワークです。今回取り上げたテンプレートのリファレンスはこちらです。

テンプレート作者のための Django テンプレート言語ガイド
http://michilu.com/django/doc-ja/templates/

Django のユーザコミュニティも存在するようですが、残念ながら現在はサーバが止まっているようです・・・
http://djangoproject.jp/


私は Python 初心者のため、定石もお作法も知りません。バリバリの Python 使いの方から見たらとんでもないコードを書いているかもしれませんがご容赦下さい。間違いや「こうするもんだ」を指摘していただけるととても嬉しいです。

環境: Python 2.5