「必要なインデックスがねえよ。」と言われたから困った

Google App Engineに新しいアプリをアップして実行しようとしたら

「必要なインデックスがねえよ。」

と言われた。

ローカルではうまく動くのにと思いながらダッシュボードで状況を確認したら、そのインデックスは存在するんだよね。

ただ、building(構築中)になっているので、使えない。

どうしようかなと思いながら朝になったので試しにアクセスしてみたら、ちゃんと構築が終わっていた。

昔は、すぐ使えるようになっていたような気がするんだけど、いつのまにか時間がかかるようになったのか、何かがまずかったのか・・・。

Share

CSSでテキストボックスのフォントを指定したら失敗

こっそり運用しているお小遣い帳をiPhone対応にしたものの、どうもテキストボックスの中だけ文字が小さなと思い、「20px」と絶対指定したら縦表示の時に画面全体が縮小されてしまった。

追記:

iPhoneの言語環境を英語にした後にアクセスしたら、今度は縮小されなかった。

言語環境の違いは関係ないような気がするし、何か他に不確定な要素があるんだろうか。

確かに他のiPhone対応サイトでもたまに、縮小されてしまう時があったような気がする。

Share

ドキュメントが日本語化された

6 月 10 日に開催された Google Developer Day 2008 Japan にて、Google は技術ドキュメントの日本語化の推進を表明いたしました。Google Developer Day を開催した時点で既に多くの技術ドキュメントが日本語化されていましたが、この度話題の Google App Engine のドキュメントが日本語化されたことをお伝えいたします。

Google Japan Blog: Google App Engine の技術ドキュメント 日本語化のお知らせ/Google Developer Day 2008 Japan セッションレポート 前編

id:naoki0311さんのトラックバックで気づきましたがドキュメントが公式に日本語化されました。

元々、比較的平易な英文だったのとサンプル込みで頑張ればなんとかなるとは思っていましたが、日本語化されたのは、嬉しいですね。

最近、あまり触ってませんが(iPhoneにくびったけ)、iPhoneアプリとGoogle App Engineを連携できたらいいなぁと思っています。

ちなみに、公式に日本語化される今までの間、ドキュメント代わりにお世話になっていたのは、

http://d.hatena.ne.jp/hamatsu1974/20080424/1208980226

のサイトです。

最近、Google App EngineといいiPhone SDKといい2008年はアプリケーションインフラ変革の年だなと思います。

後から振り返ったとき2008年に始まったのだなと思えるような。そんな気がします。

Share

お小遣い帳のテンプレートで出力値をエスケープしていなかったのでエスケープするようにした

通常djangoでパラメータを出力する時は

{{ parameter }}

としますが、パラメータの値中にあるhtmlにとっての特殊文字をエスケープして出力する場合(ver0.95まで)

{{ parameter | escape }}

といったようにescapeフィルタをつけてあげることで実現します。

このような仕様によって、コードからテンプレートにエスケープ処理を追い出す事が可能になっていて、結果的に複雑度を分散しそれぞれがシンプルになっています。

また、出力仕様は出力するところに書くことで、出力仕様に関連するスコープも狭くする事ができました。

余計な話ですが、開発版である0.97preでは、自動エスケープがデフォルトの設定で

{{ parameter }}

の状態でエスケープが行われます。

これを解除(つまりエスケープしないようにする)場合は明示的に値が安全であることを示すために

{{ parameter | safe }}

safeフィルタをつけてあげる必要があります。

私もASP.NETでカスタムコントロールを利用して似たような事をしていたので、こちらの仕様に変わる事については歓迎です。

というわけで、djangoのテンプレートの部分だけ再掲

{{ parameter }}

となっていた部分のうち数値以外が入る部分について

{{ parameter | escape }}

としてあります。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html lang="ja" xml:lang="ja" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{{ userName|escape }}のお小遣い帳</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<link href="./styles/styles.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="./scripts/util.js" ></script>
<script type="text/javascript" src="./scripts/index.js" ></script>
</head>
<body>
<div id="body">
<div id="header">
<div id="user">
お小遣い帳
</div>
<div id="navigationOperation" >■</div>
</div>
<div id="navigation" >
<div id="logonControl">
{{ userName|escape }} <br />
<a href="{{ logoutUrl }}">ログオフ</a>
</div>
<div id="settings">
<form action="/" method="post">
<div>チャージ額:<br /><input type="text" name="chargedValue" value="" tabindex="1" accesskey="1" /><br /></div>
<div>前回チャージ後総額:<br />{{ setting.properValue }}円<br /></div>
<div>総利用額:<br />{{ setting.totalPayment }}円<br /></div>
<div>残額:<br />{{ currentValue }}円<br /></div>
<div><input type="submit" value="チャージ"  tabindex="2" accesskey="s" /></div>
{% if settingErrorMessage %}
<div class="errorMessage">{{ settingErrorMessage|escape }} </div>
{% endif %}
</form>
</div>
</div>
<div id="contents">
<form action="/" method="post">
<div>使った内容:<select name="titles" tabindex="3">
<option value="" selected="selected"></option>
{% for value in titles %}
<option value="{{ value }}" >{{ value|escape }}</option>
{% endfor %}
</select><input type="text" name="title" value="" tabindex="4" accesskey="c" /></div>
<div>使った金額:<input type="text" name="payment" value="" tabindex="5" accesskey="3" /><br /></div>
<div><input type="submit" value="設定"  tabindex="6" accesskey="s" /></div>
{% if paymentErrorMessage %}
<div class="errorMessage">{{ paymentErrorMessage|escape }} </div>
{% endif %}
</form>
<div id="list" >
過去20件の利用状況:
<table border="1" cellspacing="0" >
<tr>
<th>日付</th>
<th>使った内容</th>
<th>金額</th>
</tr>
{% for value in outputValues  %}
<tr>
<td>{{ value.date }}</td>
<td>{{ value.title|escape }}</td>
<td>{{ value.value }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
<div id="footer">ご利用は計画的に。</div>
</div>
</body>
</html>
Share

とりあえず、暫定的に日本時間で表示されるようにした。

google app engineは通常だと標準時間の表示になります。

つまり日本時間-9時間になります。

そこで、本当はtz_info等を利用してちゃんとするべきなのですが、暫定的に

PaymentInfo(支払い情報)モデルクラスにjapan_timeメソッドを追加し、djangoのテンプレート側でそれを呼んであげることで、PaymentInfo.dateの日付情報を+9時間して表示するようにしました。

まず最初にmodels.pyの該当部分ですが、コメントで解説しています。

class PaymentInfo(db.Model):
author = db.UserProperty()
title = db.StringProperty()
value = db.IntegerProperty()
date = db.DateTimeProperty(auto_now_add=True)
#ここで、japan_timeというメソッドを追加しておき、dateプロパティの値に9時間足しています。
def japan_time(self):
return self.date + datetime.timedelta(hours=9)

そして、django側の該当部分ですが、

{% for value in outputValues  %}
<tr>
{# value.japan_timeとすることで、PaymentInfoで定義したメソッドを呼ぶようになっています。#}
<td>{{ value.japan_time }}</td>
<td>{{ value.title|escape }}</td>
<td>{{ value.value }}</td>
</tr>
{% endfor %}

ちなみに、今までイメージを貼ってませんでしたが、お小遣い帳のイメージはこんな感じです。

f:id:NAL-6295:20080708012444p:image

Share

この前作ったサンプルのデザインだけ流用して1時間以内でお小遣い帳にしてみた

仕様は、

・持ち金をチャージできる。

・チャージ時の総額と総利用額と残金を表示。

・使った時は、何に使ったかと金額を入力。

・過去20件の利用データを表示。

・過去100件の利用データ中の何に使ったかをドロップダウン表示して選択できる。

・エラーチェックはまだしていない。

・オモローと表示していた部分を「ご利用は計画的に。」に変更。

気づいたこと。知ったこと。

・djangoのテンプレートに渡す値にmodelを使えるのが管理が煩雑にならないので楽だと思った。

・GQLでSQLのTOP 20をやりたいときは、ORDER BY等の後にLIMIT 20とやる。

・ORDER BY等はおおむねSQLと同じ。

・時間制限のため、コードが汚い(エクスキューズです。)。

・datetime型を使う場合はdatetimeモジュールをインポートする。

・djangoのテンプレート側で四則演算が出来ない。

・スペルミスで結構エラーになった。

サンプルか修正したオブジェクトだけ表示。

・テンプレート

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html lang="ja" xml:lang="ja" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{{ userName }}のお小遣い帳</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<link href="./styles/styles.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="./scripts/util.js" ></script>
<script type="text/javascript" src="./scripts/index.js" ></script>
</head>
<body>
<div id="body">
<div id="header">
<div id="user">
お小遣い帳
</div>
<div id="navigationOperation" >■</div>
</div>
<div id="navigation" >
<div id="logonControl">
{{ userName }} <br />
<a href="{{ logoutUrl }}">ログオフ</a>
</div>
<div id="settings">
<form action="/" method="post">
<div>年間チャージ額:<br /><input type="text" name="chargedValue" value="{{ setting.chargedValue }}" tabindex="1" accesskey="1" /><br /></div>
<div>前回チャージ後総額:<br />{{ setting.properValue }}円<br /></div>
<div>総利用額:<br />{{ setting.totalPayment }}円<br /></div>
<div>残額:<br />{{ currentValue }}円<br /></div>
<div><input type="submit" value="設定"  tabindex="2" accesskey="s" /></div>
{% if settingErrorMessage %}
<div class="errorMessage">{{ settingErrorMessage }} </div>
{% endif %}
</form>
</div>
</div>
<div id="contents">
<form action="/" method="post">
<div>使った内容:<select name="titles" tabindex="3">
<option value="" selected="selected"></option>
{% for value in titles %}
<option value="{{ value }}" >{{ value }}</option>
{% endfor %}
</select><input type="text" name="title" value="" tabindex="4" accesskey="c" /></div>
<div>使った金額:<input type="text" name="payment" value="" tabindex="5" accesskey="3" /><br /></div>
<div><input type="submit" value="設定"  tabindex="6" accesskey="s" /></div>
{% if paymentErrorMessage %}
<div class="errorMessage">{{ paymentErrorMessage }} </div>
{% endif %}
</form>
<div id="list" >
過去20件の利用状況:
<table border="1" cellspacing="0" >
<tr>
<th>日付</th>
<th>使った内容</th>
<th>金額</th>
</tr>
{% for value in outputValues  %}
<tr>
<td>{{ value.date }}</td>
<td>{{ value.title }}</td>
<td>{{ value.value }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
<div id="footer">ご利用は計画的に。</div>
</div>
</body>
</html>

・データモデル

# -*- coding: utf-8 -*-
from google.appengine.ext import db
class UserMaster(db.Model):
#paymentを利用するユーザのマスタを定義するクラスです。
#db.Modelを継承しています。
author = db.UserProperty()
chargedValue = db.IntegerProperty()
totalPayment = db.IntegerProperty()
properValue = db.IntegerProperty()
class PaymentInfo(db.Model):
author = db.UserProperty()
title = db.StringProperty()
value = db.IntegerProperty()
date = db.DateTimeProperty(auto_now_add=True)

・index.py

# -*- coding: utf-8 -*-
import wsgiref.handlers
import cgi
import datetime
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.api import users
import models
import os
from google.appengine.ext.webapp import template
from google.appengine.ext.db import djangoforms
class MainPage(webapp.RequestHandler):
def view(self,user,settingErrorMessage = None,paymentErrorMessage = None):
settings = models.UserMaster.gql("where author = :author",author=user)
setting = None
if settings.count() > 0:
setting = settings[0]
else:
setting = models.UserMaster()
setting.author = user
setting.chargedValue = 600000
setting.properValue = setting.chargedValue
setting.totalPayment = 0
setting.put()
outputValues = models.PaymentInfo.gql("where author = :author order by date DESC LIMIT 20",author=user)
titles = self.titles(user)
template_values = {
'userName' : user.nickname(),
'logoutUrl': cgi.escape(users.create_logout_url("/")),
'setting' : setting,
'currentValue':setting.properValue - setting.totalPayment,
'outputValues': outputValues,
'titles': titles,
'settingErrorMessage': settingErrorMessage,
'paymentErrorMessage': paymentErrorMessage
}
path = os.path.join(os.path.dirname(__file__), 'index.html')
self.response.out.write(template.render(path, template_values))
def titles(self,user):
selections = models.PaymentInfo.gql("where author = :author order by title",author=user)
values = []
prev = ''
for value in selections:
if prev != value.title:
values.append(value.title)
prev = value.title
return values
def get(self):
user = users.get_current_user()
if not user:
self.redirect(users.create_login_url(self.request.uri))
return
self.view(user)
def post(self):
user = users.get_current_user()
if not user:
self.redirect(users.create_login_url(self.request.uri))
return
if self.request.get('chargedValue'):
if self.request.get('chargedValue').isdigit():
settings = models.UserMaster.gql("where author = :author",author=user)
setting = settings[0]
setting.properValue = setting.properValue + int(self.request.get('chargedValue'))
setting.put()
else:
self.view(user,u'チャージ金額には数字を入力してください。')
return
else:
payment = models.PaymentInfo()
payment.author = user
if len(self.request.get('titles')) == 0:
payment.title = self.request.get('title')
else:
payment.title = self.request.get('titles')
payment.value = int(self.request.get('payment'))
payment.put()
settings = models.UserMaster.gql("where author = :author",author=user)
setting = settings[0]
setting.totalPayment = setting.totalPayment + payment.value
setting.put()
self.view(user)
def main():
application = webapp.WSGIApplication(
[('/', MainPage)],
debug=True)
wsgiref.handlers.CGIHandler().run(application)
if __name__ == '__main__':
main()
Share

試しにサンプルアプリにはてブアイコンを追加してみた。

下記の記事でアップロードしてあったサンプルアプリにはてブアイコンを追加してみた。

ブログとは違ってurlが可変じゃないから、ただリンクを貼っただけだから簡単なんだけど。

過去の記事で下記のようなものを作成した。

このサンプルの見た目を、JavaScriptやCSSの勉強もかねて、デザインだけ変更してアップしてみた。

あと、

・入力検証

・ログインしなくてもゲストとして使える

という仕様変更を行った。

http://nal6295test.appspot.com/

やっとアプリケーションをデプロイできるようになったので、過去のサンプルをアップしてみた – NAL-6295の舌先三寸

Share

やっとアプリケーションをデプロイできるようになったので、過去のサンプルをアップしてみた

過去の記事で下記のようなものを作成した。

このサンプルの見た目を、JavaScriptやCSSの勉強もかねて、デザインだけ変更してアップしてみた。

あと、

・入力検証

・ログインしなくてもゲストとして使える

という仕様変更を行った。

http://nal6295test.appspot.com/

最初に、サンプルアプリの仕様を説明

概要:

・1からインクリメントしながら番号を表示し、3の倍数と3の付く数字のときに別の文字列を、5の倍数のときにも更に別の文字列を表示する。

・終点となる番号が設定できる。

・3の倍数と3のつく数字のときに読み上げる文字列を設定できる。

・5の倍数のときに読み上げる文字列を設定できる。

・設定した値はユーザ毎に永続化される。

・アプリケーション名は世界の○○とする。○○のところはユーザのnicknameを設定したい。

・formのvalidationについては、今回は行わない。(本当は行う必要があるので、次回以降のエントリで機能追加します。

DJangoのテンプレートを利用したサンプルアプリの作成 – NAL-6295の舌先三寸

Share

ユーザ数制限が無くなったと思ったら・・・

auにはSMSが送信できませんでした。

最初、躍起になって、3,4回送信して、届かなくて、知り合いのソフトバンクに代理で送信しようとしたら、

君、SMS送信しすぎ。

とエラーメッセージが出てしまった・・・。

んー。とはいえ、特にアプリケーションも無いので、SDKで遊んでいるだけでも良いんだけれども。

Share