October 11, 2008

GeoPt Property 用のカスタムウィジェット

こないだ Maps API hack-a-thon Tokyo に参加してきました。その時作ったのが Google App Engine の GeoPt Property をとても扱いやすくするためのユーティリティクラスです。

この記事では、そのクラスを使って geopoint データを datastore に保存するサンプルコードをお見せします。始めるまえに、こちらのdjangoforms に関する記事を読んでおくとコードがすぐ理解できるでしょう。

まずはこの記事用のディレクトリを作ります。そして拙作の my_geopt.py をその中にコピーし、さらに新しく3つのファイルを作ります。app.yaml, main.py と index.html です。

ファイルを作成したら、開発用サーバーを機動して localhost にアクセスします。すると Google Map をクリックするだけで特定の GeoPt が指定できる事がわかります。

また ライブデモもあります(少し複雑になってますが、基本は同じです)。

Happy coding :-)

app.yaml
application: geotest
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: main.py


main.py
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

import os
import wsgiref.handlers

from google.appengine.ext import db
from google.appengine.ext.webapp import template
from google.appengine.ext.db import djangoforms
import my_geopt

class Place(db.Model):
  name = db.StringProperty()
  description = db.StringProperty()
  geopoint = db.GeoPtProperty()
  date = db.DateTimeProperty(auto_now=True)

class PlaceForm(djangoforms.ModelForm):
  class Meta:
    model = Place

class MainPage(webapp.RequestHandler):
  def post(self):
    data = PlaceForm(data=self.request.POST)
    if data.is_valid():
      # Save the data, and redirect to the view page
      entity = data.save(commit=False)
      entity.put()
      self.redirect('/')
    else:
      # Reprint the form
      contents = {'form': data}
      path = os.path.join(os.path.dirname(__file__), 'index.html')
      self.response.out.write(template.render(path, contents))
  def get(self):
    contents = {'form': PlaceForm()}
    path = os.path.join(os.path.dirname(__file__), 'index.html')
    self.response.out.write(template.render(path, contents))

application = webapp.WSGIApplication(
  [('/', MainPage),],
  debug=True)

def main():
  run_wsgi_app(application)

if __name__ == "__main__":
  main()


index.html (Please replace 2 YOURAPIKEYs with your Maps API Key)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>GeoPtWidgets Example</title>
<script src="http://maps.google.com/maps?file=api&v=2&key=YOURAPIKEY"
type="text/javascript"></script>
<script src="http://www.google.com/uds/api?file=uds.js&v=1.0&key=YOURAPIKEY"
type="text/javascript"></script>
<script src="http://www.google.com/uds/solutions/localsearch/gmlocalsearch.js"
type="text/javascript"></script>
<style type="text/css">
@import url("http://www.google.com/uds/css/gsearch.css");
@import url("http://www.google.com/uds/solutions/localsearch/gmlocalsearch.css");
</style>
</head>

<body onunload="GUnload()">
<h1>GeoPtWidgets Example</h1>
<form method="post" action="/">
{{ form.as_p }}
<input type="submit" value="register"/>
</form>
</body> </html>

A custom widgets for GeoPt Property

I attended a Maps API hack-a-thon in Tokyo. There I wrote some utility class which helps us to handle GeoPt Property very easy.

In this article, I'll show you some sample code which store geopoint data by just clicking on Google Map. Before we go, perhaps you can read the djangoforms documentation. This will help you to understand the codes in this article.

Please create a directory for this article, copy my_geopt.py to the directory and create 3 brand new files app.yaml, main.py, and index.html. That's all.

After creating these files, start local dev server, get access to localhost, and see that you can specify a particular GeoPt by just clicking on the Google Map.

And here is a live demo(slightly more complicated, but basically almost the same).

Happy coding :-)

app.yaml
application: geotest
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: main.py


main.py
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

import os
import wsgiref.handlers

from google.appengine.ext import db
from google.appengine.ext.webapp import template
from google.appengine.ext.db import djangoforms
import my_geopt

class Place(db.Model):
  name = db.StringProperty()
  description = db.StringProperty()
  geopoint = db.GeoPtProperty()
  date = db.DateTimeProperty(auto_now=True)

class PlaceForm(djangoforms.ModelForm):
  class Meta:
    model = Place

class MainPage(webapp.RequestHandler):
  def post(self):
    data = PlaceForm(data=self.request.POST)
    if data.is_valid():
      # Save the data, and redirect to the view page
      entity = data.save(commit=False)
      entity.put()
      self.redirect('/')
    else:
      # Reprint the form
      contents = {'form': data}
      path = os.path.join(os.path.dirname(__file__), 'index.html')
      self.response.out.write(template.render(path, contents))
  def get(self):
    contents = {'form': PlaceForm()}
    path = os.path.join(os.path.dirname(__file__), 'index.html')
    self.response.out.write(template.render(path, contents))

application = webapp.WSGIApplication(
  [('/', MainPage),],
  debug=True)

def main():
  run_wsgi_app(application)

if __name__ == "__main__":
  main()


index.html (Please replace 2 YOURAPIKEYs with your Maps API Key)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>GeoPtWidgets Example</title>
<script src="http://maps.google.com/maps?file=api&v=2&key=YOURAPIKEY"
type="text/javascript"></script>
<script src="http://www.google.com/uds/api?file=uds.js&v=1.0&key=YOURAPIKEY"
type="text/javascript"></script>
<script src="http://www.google.com/uds/solutions/localsearch/gmlocalsearch.js"
type="text/javascript"></script>
<style type="text/css">
@import url("http://www.google.com/uds/css/gsearch.css");
@import url("http://www.google.com/uds/solutions/localsearch/gmlocalsearch.css");
</style>
</head>

<body onunload="GUnload()">
<h1>GeoPtWidgets Example</h1>
<form method="post" action="/">
{{ form.as_p }}
<input type="submit" value="register"/>
</form>
</body> </html>

July 13, 2008

Using the newest zipped pytz on GAE

I posted an entry about how to use zipped pytz on GAE[1].

1. http://takashi-matsuo.blogspot.com/2008/07/using-zipped-pytz-on-gae.html

It works well with older versions of pytz. Stefano pointed out that my method doesn't work with the newest pytz distribution. This article describes how to use the newest zipped pytz on GAE.

First, retrieve the newest pytz from pypi[2] and extract the archive.

2. http://pypi.python.org/pypi/pytz/

$ tar xjf pytz-2008c.tar.bz2
$ cd pytz-2008c/pytz
$ zip -q zoneinfo.zip `find zoneinfo -type f ! -name '*.pyc' -print`
$ rm -rf zoneinfo

After that, you have to edit pytz/__init__.py and modify open_resource function to use zipped zoneinfo database like following:
(pkg_resources stuff were struck out. Thank you again Stefano!)

def open_resource(name):
"""Open a resource from the zoneinfo subdir for reading.

Uses the pkg_resources module if available.
"""
import zipfile
from cStringIO import StringIO

if resource_stream is not None:
return resource_stream(__name__, 'zoneinfo/' + name)
else:

name_parts = name.lstrip('/').split('/')
for part in name_parts:
if part == os.path.pardir or os.path.sep in part:
raise ValueError('Bad path segment: %r' % part)
zoneinfo = zipfile.ZipFile(os.path.join(os.path.dirname(__file__),
'zoneinfo.zip'))
return StringIO(zoneinfo.read(os.path.join('zoneinfo', *name_parts)))

Then, the only thing to do is to copying your original pytz directory into your application directory.

$ cd ../
$ cp -r pytz /your/application/directory


If you'd like to avoid using CPU to unzip zoneinfo data every time, perhaps you could use following function:

from google.appengine.api import memcache
import logging
import pytz
from pytz import timezone

def getTimezone(tzname):
try:
tz = memcache.get("tz:%s" % tzname)
except:
tz = None
logging.debug("timezone get failed: %s" % tzname)
if tz is None:
tz = timezone(tzname)
memcache.add("tz:%s" % tzname, tz, 86400)
logging.debug("timezone memcache added: %s" % tzname)
else:
logging.debug("timezone memcache hit: %s" % tzname)

return tz


Happy coding :-)

July 12, 2008

Using zipped pytz on GAE

To handle timezone of all over the world crrectly, I think it is the most easy way to use pytz[1]. Besides that, there are many modules and programs which depends on pytz modes, so I believe that quite a few people would like to use pytz on GAE. However, pytz contains over 500 files in its zoneinfo directory, so it often bears on the 1000 files limit.

1. http://pytz.sourceforge.net/

This is an instruction about how to use zipped pytz on GAE environment. In this article, we use my_zipimport.py[2] provided by Guido van Rossum.

2. http://code.google.com/p/googleappengine/issues/detail?id=161#c19

First, make your own pytz.zip as following:

$ cd pytz-2006p
$ zip -q pytz.zip `find pytz -type f ! -name '*.pyc' -print`



Secondly, make sure to put my_zipimport.py and pytz.zip into your GAE app directory.
Lastly, put following code into your script.

import my_zipimport
import sys

my_zipimport.install()
sys.path.insert(0, 'pytz.zip')


That's all. Now you can use pytz module seamlessly.

July 4, 2008

Hackathon kit

Google I/O のプレゼンを和訳したこれ使ってセミナーしても良い?本家グループ にメールしたら、ある US の googler からメールが来まして、なんと hackathon kit を提供してくれるようです。 わーい。

という亊で 7月の終わりか8月の始めに hackathon or campfire やりたいな...

Meet-at

Google-App-Engine-Japan グループ で Meet-at というオープンソースプロジェクトを始める際の共同開発者を募集したら結構反応がありました。かなり強力なメンバーが揃ったので、個人的にはとても期待しています。

早速 7/5 にキックオフミーティングをやる予定です。どの技術を使おうとか相談するのもなかなか楽しいですが早くコーディングが始まらないかな...

June 27, 2008

グループオーナー (続き)

python-ml-jp にて Google-App-Engine-Japan グループを宣伝したら 30 人ほどメンバーが増えました!
みなさんようこそいらっしゃいました。

メンバーも増えた亊だし少し語りかけてみようかなと思います。

June 25, 2008

グループオーナー

さて、google-app-engine-japan グループのオーナーになったわけでありますが、イマイチ人の集まりが悪い感じであります。やはり python や django のコミュニティへ一度宣伝をした方が良いのかもしれませんね。あとは python を使って無い人へアピールとかも必要なのかもしれません。

実は勤めている社内で Google App Engine のコードラボを企画中なのですが、こちらでも思ったより人の集まりが悪いです。日本では python は馴染みが少ないからかも知れません(まあうちの会社の開発者は Java がメインというのもあるか)。

ゆくゆくは一般向けにコードラボやキャンプファイアのような企画をやりたいと考えていますが、まずはグループの存在を知ってもらい参加してもらう方が先決ですね。

June 13, 2008

Google I/O セッション

I/O セッションのビデオとプレゼンが出ました!

http://sites.google.com/site/io/

後でゆっくり見るぞ〜

June 5, 2008

App Engine ドキュメントの和訳

Retrieving Authenticated Google Data Feeds with Google App Engineというドキュメントを和訳しました。Google App Engine 上で認証が必要な Google Data Feeds を取得する方法がそれです。いちおう公開前にオリジナル作者の Jeff Scudder さんに公開の許可を取っているので問題は無いはずです。

この文書では App Engine 上で gdata-python-client ライブラリを有効に使う方法が解説されています。App Engine 上で gdata client を自由自在に使えれば、作成できるアプリケーションの幅がひろがりますね。

今後も時間があればいろいろと翻訳していこうと思います。

June 3, 2008

App Engine の SMS verification

I/O で一般公開された(はずの) Google App Engine ですが、日本のみなさんは SMS の verification がなかなかできずに困っているのではないでしょうか。

Verify のページでは「We are currently experiencing issues with DoCoMo and KDDI phones.」と表示されており、どうやら Docomo と KDDI では SMS が受け取れないようですし、私の持っている softbank 携帯でも SMS が受け取れませんでした。ちなみに嫁の softbank 携帯だと受け取る亊が出来ました。

嫁は昔から softbank (Jフォン時代から)ひとすじで、私は昔 Docomo だったのを MNP で softbank に乗り換えたクチですが、それが関係してるのかな?

ちなみに softbank 携帯に SMS を送った時は、Country and Carrier には「Other」を選び、Mobile Numberに「+81 90xxxxxxxx」(xは番号です)というように国番号「+81」の次に、始めの「0」は無しで電話番号を入力すれば送る亊ができました。参考になればと思い書いておきます。

しかし、そろそろ Developper Day が開催するので、それまでにはなるべく多くの人が App Engine を使えるようになると良いのですが...

June 1, 2008

My first app on App Engine

I attended the conference named 'Google I/O'. It was a very exciting event for an engineer like me. I attended many sessions that were, in most cases, related to Google App Engine.

Google App Engine is a web platform where we can run our web application programs on Google's infrastructure. In my opinion, that could be the biggest paradigm shift this decade.

In those sessions, I learned many techniques about how to build applications on Google App Engine, so I finally wrote up my first application on App Engine called 'InterNovel'. The URL is below.

http://internovel.appspot.com/

In this application, there are novels. Each novel has its own state. The state is one of the following three: 1. Accepting fragments. 2. Votes. 3. Complete.

1. Accepting fragments
When the novel is in this state, everyone can post a continuous fragment. If the number of posted fragments reaches a particular count (that the owner of the novel decided beforehand), the state will change to 2.

2. Votes
When the novel is in Votes state, everyone can vote on their favorite fragments among the candidates. If the total number of the votes reaches a particular count (this number is also editable by the owner), the state will change to 1 or 3.

3. Complete
If the number of iterations between states 1 and 2 reaches a particular number, the completion comes. The state will never change from this time.

Well, that's all about this application. Isn't it interesting?

May 30, 2008

Google I/O が終わりました

終わってしまいました。 Guido のセッションがものすごく High Level だったのが意外でした。 High Level というのは 「 core では無い」という意味ですが。

全体的な感想ですが、今回は主な興味の対象である App Engine のセッションを中心に見てきまして、 App Engine についていろいろあやふやだった点がはっきりしてきました。とても有意義なセッション達でした。

ところで今回同行した a2c さんが面白い亊を言っていました。興味の無いテクノロジーのセッションを聞くのも意外と楽しい...と。そういうものかもしれません。発見する亊はそもそも楽しいですからね。

そうそう。以前のポストで書いた App Engine 用のソフトウェアがだいたい完成しましたので、URL を書いときます。
http://internovel.appspot.com/

まだ説明とか全然足りないので、多分正体や使い方も皆目分からないと思います。それらは日本に帰ってからおいおい書くつもりです。

今日はとりあえずこんなところで。

May 25, 2008

Google I/O前夜

Google I/O が近づいて来ましたので準備をはじめました。まずはプログラムのスケジュールをダウンロードして印刷しておきます。これを飛行機の中で眺めておけば、参加したいプログラムをある程度しぼれるでしょう。荷物は少なめです。いつも使っているトラベルバッグに収まりそう。

Google I/O 自体は 5/28, 5/29 の2日間なのですが、私自身は 5/27 のうちに San Francisco に到着予定です。この日の夕食をどうするか悩んでいたのですが、何人かの Googler がホストしてくれる亊になったので気が楽になりました :-)

私個人として、今一番注目しているテクノロジーは App Engine です。Google のインフラを使って Web アプリが動かせるのですから、冗長化の面でもスケーラビリティの面でも大きなメリットがあります。それに python だし。 python は私のお気に入りです。

そうそう。まだ内容は内緒ですが、 App Engine で動く Web アプリを作っている最中なのです。もう少しでプロトタイプが完成しそうなので、飛行機の中で書き終えられたら良いなと思っています。とりあえず動くものが完成したら App Engine Group にポストしてみるつもりです。(5/26追記) グループにポストするよりも Applications Gallery に登録する方が良さそうですね。