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

April 26, 2008

Ubuntu祭り

Ubuntu 8.04 LTS がリリースされましたね。
2年に1度のLTSとあって、この日を待っていた人は世界中にいるのではないでしょうか。

そんなわけで、今日の午前中に同僚がダウンロードを始めたのですが・・・

やはりというか、激重でした。

どこからダウンロードしているのか聴いたら、台湾のミラーだそうで。

通常、ミラーサイトはできるだけ近い場所を選ぶのが基本なのですが・・・

私はイタリアからダウンロードしてみました。

時差の関係でヨーロッパはまだ深夜です。
几帳面なドイツ人などは、深夜でも待ちかまえてダウンロードしそうですが、
イタリア人ならそこまでする人も少なそうだ・・・(イタリアの方、ゴメンナサイ)
と推測してみました。

思った通り!

台湾よりイタリアのサーバの方が軽いという結果でした(笑)


G.W.にいろいろいじりながらゆっくりインストールしてみるとします。

April 22, 2008

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

Google App Engine には webapp という、Webアプリケーションのためのシンプルなフレームワークが用意されています。
前回はとりあえず Hello World まで行ったので、次はこの webapp フレームワークを使ってリクエストパラメータを受け取り、それを表示するアプリケーションを作ってみました。





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

環境: Python 2.5


ソースコードを UTF-8 で書くには

ソースコードの1行目または2行目に


# coding: UTF-8


と書きます。

こうしないと、文字列リテラルの前には u'こんにちは' のように、毎回 u をつけないといけなくなってしまいます。それはあまりに面倒です。


HTTP post リクエストを受けるには

webapp.RequestHandler を継承したクラスを定義し、post() メソッドを定義します。

(例)


class WelcomeRequestHandler(webapp.RequestHandler):
  def post(self):


HTTP get リクエストを受けるには、post() ではなく get() メソッドを使えば OK です。使い方は post() と全く同一です。他にも、Restful なアプリケーション構築のために put() delete() head() trace() options() が用意されています。

 ※ Python って、メソッド?関数?どう呼ぶんでしょう?


リクエストパラメータ値を取得するには

RequestHandler オブジェクトに用意されている属性 request の get() メソッドを利用します。引数にはリクエストパラメータ名を指定します。

(例)


userName = self.request.get('username')


これは、"username" という名前のリクエストパラメータ値を取り出し、変数 "userName" に入れておく、という処理になります。


リクエストパラメータが来なかった場合のデフォルト値を同時に指定することができます。

(例)


userName = self.request.get('username', default_value="anonymous")


上記の場合、リクエストパラメータ"username"が来なかった場合は変数"userName"の値が"anonymous"になります。

同名のリクエストパラメータが複数来る場合は

(例)


userNames = self.request.get('username', default_allow_multiple=True)


この場合、変数 userNames は配列になります。


レスポンスを出力するには

こう書きます(^^;



self.response.out.write('出力したい文字列')




変数の値を出力するには

こう書きます。



self.response.out.write(変数名.encode('文字エンコーディング名'))


これ、ハマりました。self.response.out.write(変数名) とするだけでは、実行時に UnicodeDecodeError とエラーになります。Python を使い慣れている方なら何でもないことなのでしょうけど・・・


リクエスト URL パターンと、実行するクラスを関連づけるには

こう書きます。



def main():
  application = webapp.WSGIApplication([('URLパターン', クラス名)],
           debug=True)
  wsgiref.handlers.CGIHandler().run(application)




アプリケーションが起動したら main() 関数が呼ばれるようにするには

こう書きます。



if __name__ == "__main__":
  main()




ある URL にリダイレクトするには

redirect() メソッドを使います。


self.redirect('http://www.google.co.jp/', False)

2番目の引数に True を指定すると、HTTP Status 301 での Redirect になります。False を指定した場合は HTTP Status 302 での Redirect を行います。

 ※Python の True/False って、大文字からはじめるんですね・・・


例外発生時の処理を記述するには

RequestHandler クラスの handle_exception() メソッドを使うらしいのですが、使い方がよくわかりません・・・



以下、全ソースコード。

[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>


テキストボックスとsubmitボタンが一つずつあるだけのシンプルなフォームを実装した単純な HTML です。


[WelcomeRequestHandler.py]


# coding: UTF-8

import wsgiref.handlers

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

class WelcomeRequestHandler(webapp.RequestHandler):

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

      # レスポンス出力
      self.response.out.write('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">')
      self.response.out.write('<html>')
      self.response.out.write('<head>')
      self.response.out.write('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">')
      self.response.out.write('<title>ようこそ</title>')
      self.response.out.write('</head>')
      self.response.out.write('<body>')
      self.response.out.write('  <center>')
      self.response.out.write('    <br />')
      self.response.out.write('    <h3>ようこそ!')
      # 変数値を出力する際には encode() 関数で処理する必要がある
      self.response.out.write(userName.encode('UTF-8'))
      self.response.out.write('さん!</h3>')
      self.response.out.write('    <br /><br />')
      self.response.out.write('    <a href="index.html">戻る</a>')
      self.response.out.write('  </center>')
      self.response.out.write('</body>')
      self.response.out.write('</html>')

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

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


リクエストパラメータを受け取り、その値を使って「ようこそ!○○さん!」と出力するスクリプト。


[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


/ あるいは /index.html が要求された場合は index.html を返し、/welcome が要求された場合は WelcomeRequestHandler.py が実行されるように設定しています。
app.yaml では、リクエスト URL パターンの関連づけを行いますが、.py スクリプトとの関連づけは、前回の Hello World で出てきました。
URL パターンと静的ファイルを関連づける場合は、static_files: エントリでファイル名を指定し、同時にサーバへアップロードするファイルを相対パスまたは絶対パスで指定します。


前回、「次回は掲示板でも・・・」なんて書きましたが、いきなり掲示板なんてとんでもなかったです。文字エンコーディングごときで大ハマリするぐらいですから。もっと少しずつ進めていきたいと思います。

April 18, 2008

MySQL、新機能追加は有償版の「MySQL Enterprise」だけを対象に

MySQL、新機能追加は有償版の「MySQL Enterprise」だけを対象に
http://www.technobahn.com/news/2008/200804172000.html
「MySQLは16日、米カリフォルニア州サンタクララで開催中のMySQLコンファレンスの席上で今後の新機能追加は有償版の「MySQL Enterprise」だけを対象としていく方針を明らかにした。」
「無償版の「MySQL Community Server」の提供は今後も継続されるが、無償版と有償版の開発は完全に切り離されることとなり、無償版と有償版の2つのMySQLはまったく別々の進化を遂げることとなる見通しだ。」

 ※technobahn のサイトが落ちているようなので、Google のキャッシュをご利用下さい。


Sun Microsystems の恐るべき愚行と言えます。
このニュースは、いったいどれだけの OSS 支持者を失望させ、どれだけの OSS 支持者の反感を買うのでしょうか?

今日六本木ミッドタウンで行われていた Sun Business .Next 2008 や G.W.明けの S.F.で行われる JavaOne 2008 で、オープンソース団体による抗議デモが行われても不思議ではないぐらいです。

今回発表されたこの方針に変更が無い限り、MySQL を離れるユーザは増加の一途をたどることでしょう。


こうだったはずなのに・・・

http://japan.cnet.com/news/ent/story/0,2000056022,20371630,00.htm
「Sunは、次バージョンMySQLの「ファイナルに近い」ビルドを公開するとともに、1月に10億ドルで買収した同オープンソースデータベース企業の文化を変更しないことを約束した。」

http://japan.cnet.com/news/ent/story/0,2000056022,20371536,00.htm
「Sun MicrosystemsがMySQLを10億ドルで買収した際に浮上した最大の問題は、両社の明白な文化的衝突だろう。長年、クローズドソース企業としてやってきたSunがオープンソース企業のMySQLを買収したことで、オープンソースコミュニティーの一部から、今後の成り行きを懸念する声が上がった。しかし、Sunは米国時間4月15日、買収をめぐるあらゆる疑問を払拭すべく、1月に買収が完了して以来、初となる大規模なMySQL開発者向けの集会MySQL Conference & Expoで、両社の相思相愛ぶりを演出した。」

GoogleAppsとSalesForcecomの提携

日本国内でも発表されましたね。

Salesforce for Google Apps
http://www.salesforce.com/jp/googleapps/?d=70130000000Dsim

プレスリリース
http://www.salesforce.com/jp/company/news-press/press-releases/2008/04/080417.jsp


SalesForce.com は来週に国内イベントを控えていることもあり、米国から幹部が来日して日本でも記者会見が行われました。

「Salesforce+Google Apps」の統合、日本で初のデモ (@IT)
http://www.atmarkit.co.jp/news/200804/17/sf.html

グーグルとセールスフォースが提携--その狙いと勝算 (CNET Japan)
http://japan.cnet.com/column/pers/story/0,2000055923,20371533,00.htm

「グーグルと“ソフトの終焉”に寄与」(ITpro)
http://itpro.nikkeibp.co.jp/article/NEWS/20080417/299412/

「グーグルと我々は同じビジョンを共有している。それはソフトウエアの終焉をもたらしたいということだ」は、
正直に「グーグルと我々は同じビジョンを共有している。それはMSの終焉をもたらしたいということだ」と言えばいいのに(笑)


大本営発表では「『ソフトウェアの終焉』が始まった」「ユーザーのSAP、オラクル、マイクロソフトへの依存が終わる」等、なかなか刺激的な言葉が並んでいますが・・・

今回の提携が Google Apps のシェアを一気に伸ばしたり、SaaS というサービス形態が一気に認知され普及するほどのインパクトを持つとは考えにくいです。
現実には既存の SalesForce ユーザの一部が恩恵を受けるのと、SaaS CRM 製品の導入を検討している新規のユーザにとって、SalesForce.com と競合する他社の Saas CRM 製品に対して多少のアドバンテージができる程度のものでしょう。今回の提携によって実現する機能は SalesForce を利用しないユーザにとって全く関係ありませんし、SalesForce を利用するユーザでもメールやオフィス等を Google Apps へ移行しないとメリットがありませんから。

とはいえ今回の提携は、SaaS アプリケーションのマッシュアップという、1つの事例を作ったと言えるでしょう。


「GoogleとSalesforceの提携は長続きしない」ZohoのCEOがコメント (INTERNET WATCH)
http://internet.watch.impress.co.jp/cda/news/2008/04/15/19219.html

まあ、これはライバル企業の発言なのでそれを勘案しておく必要がありますが・・・(^^;
Zoho としては何かコメントをしないで指をくわえているだけというワケにはいかない・・・という理由だけで出てきたコメントでしょう。


刺激的な言葉で満たされた大本営発表に、対抗しなければならないという理由だけで出てくるライバル企業のポーズ的な発言。
こういったやり方はとてもアメリカ的で、日本文化とは異質なモノを感じざるを得ません。日本の市場にしっかり根を下ろしたいのなら、それなりのマーケティング/プロモーションをしないと外資系ベンダの日本でのシェア拡大は難しいかもしれませんね。