Google App Engineのテストを実環境と同一のホスト名でアクセスする
twitter連携などをする場合、コールバックURLの制限があるため、
テスト環境でも、実環境と同一のURLにてアクセスしたい場合があります。
通常、Google App Engine Launcherなどで起動した場合、
http://localhost:8080/
にマッピングされるかと思います。
これを、
http://my-android-server.appspot.com/
などにマッピングして起動する方法です。
- hostsファイルを用意する
以下はWindowsの場合です。
C:\Windows\System32\drivers\etc\
にある、hostsファイルをhosts.origにコピーします。
また、hostsファイルをhosts.testにコピーし、下記を追加します。
[自マシンのIPアドレス] [ホスト名(例:my-android-server.appspot.com)]
↓コマンドで行う場合(管理者権限でコマンドプロンプトを起動する必要あり)
copy C:\Windows\System32\drivers\etc\hosts C:\Windows\System32\drivers\etc\hosts.orig copy C:\Windows\System32\drivers\etc\hosts C:\Windows\System32\drivers\etc\hosts.test echo 192.168.1.99 my-android-server.appspot.com>>C:\Windows\System32\drivers\etc\hosts.test
- 起動用のバッチファイルを作成する
起動用のバッチの内容は以下の通り。
-
- テスト用のhostsファイルを適用する
ホスト名を追加したhostsファイルを適用します。
-
- プロジェクトディレクトリまで移動
- サーバ起動
"start /WAIT"
で別窓でサーバを起動し、その終了を待ちます。
テストが終了したら、サーバが起動しているコマンドプロンプトにてCtrl-Cで中断します。
-
- 通常のhostsファイルを復元
元のコマンドプロンプトウィンドウの処理が続行されると、hostsファイルを復元します。
実際は以下のような感じ
copy /Y C:\Windows\System32\drivers\etc\hosts.test C:\Windows\System32\drivers\etc\hosts cd C:\Users\noboru\workspace_python\my-android-server start /WAIT C:\Python25\python.exe "C:\Program Files (x86)\Google\google_appengine\dev_appserver.py" src --datastore_path="C:\Users\noboru\workspace_python\my-android-server\db\dev_appserver.datastore" --addres=192.168.1.99 --port=80 copy /Y C:\Windows\System32\drivers\etc\hosts.orig C:\Windows\System32\drivers\etc\hosts pause
サーバ起動時にDBファイルの位置も指定しています。(--datastore_path)
- 起動用のバッチファイルを管理者権限で実行します
hostsファイルの操作や、80番ポートの使用などがあるため、管理者権限での実行が必要です。
起動後、実環境と同様のURLにてアクセスできます。
jQueryとjQuery mobileでボタンの有効・無効を切り替える
jQueryを利用する場合も、jQuery mobileを利用する場合も、ボタンは
<input id="myButton" type="button" value="OK" />
のように記述するかと思います。
ただし、jQuery mobileを利用した場合はdivタグなどに変換されます。
ボタンの有効・無効をJavaScript内で変更する場合、
PC/mobileを気にせずに使いたい場面があったため、
jQueryプラグイン(?)を作って見ました。
(function($) { $.fn.disableButton = function() { if (this.button) { // jQuery mobileの場合 this.button('disable'); this.button('refresh'); } else { // jQueryの場合 this.attr({ disabled: "disabled", }); } return this; } $.fn.enableButton = function() { if (this.button) { // jQuery mobileの場合 this.button('enable'); } else { // jQueryの場合 this.removeAttr("disabled"); } return this; } })(jQuery);
で、使いたいときは
$(function() { $('myButton').disableButton(); });
で、無効に出来ました。
属性が存在しない場合、デフォルト値を設定する
例:クラスKyouenPuzzleのリスト:puzzlesに動的に属性を付与します。
for p in puzzles: if 特定の条件: p.clear = '1'
この状態で、p.clearにアクセスすると、例外が発生しました。
そのため、利用する際に下記のようにしました。
for p in puzzles: clear = p.clear if hasattr(p, 'clear') else '0'
これによって、pがclearという属性を持っていなければ、'0'を、
持っていれば、その属性を返します。
まぁ、クラスにデフォルト値入れておけばいいんですが・・・
Oracleでのnullの扱いについて
たぶん、前にも学習しただろうけど、また忘れてたのでメモ。
たとえば、以下のようなテーブル(TABLE_1)があった場合。
MNG_NO | FLAG | ... |
---|---|---|
001 | 1 | ... |
002 | (null) | ... |
003 | 2 | ... |
004 | 3 | ... |
で、以下のようなSQLを発行した場合
select MNG_NO from TABLE_1 where FLAG not in ('1', '2')
とした場合、MNG_NO='004'しか取得できない。
nullに対する演算はtrueにはならないそうで、MNG_NO='002'は取得できない。
上記のSQLを日本語で言うと、
「FLAGが'1'でも'2'でもないMNG_NOを取得する」
となるため、直感的には'002'も取れる気がする。
全てのHTMLリクエストをPythonで処理してから出力する
【宣伝】下記のページでも利用している技術です。
共円(http://my-android-server.appspot.com/)
twitter連携などを行うと、ログイン状態や情報の取得など、全てのページで必要な処理が出てきました。
そのため、全てのHTMLリクエストをpythonのモジュールを通して処理するようにしてみました。
html.py
#!/usr/local/bin/python # -*- coding: utf-8 -*- import os import logging import math import Cookie import tweepy import uuid from google.appengine.ext import webapp, db from google.appengine.api import memcache from google.appengine.ext.webapp import template from google.appengine.ext.webapp.util import run_wsgi_app def render_page(handler, page, values=None): template_values = values or {} # ここに常に行う処理を記述する(ログイン情報を'template_values'に詰めるとか) path = os.path.join(os.path.dirname(__file__), 'template', page) html = template.render(path, template_values) handler.response.out.write(html) class StaticPage(webapp.RequestHandler): def get(self, value=None): if not value: value = 'index.html' render_page(self, value) def post(self): self.get(self) application = webapp.WSGIApplication([('/', StaticPage), ('/page/(.*)', StaticPage), ], debug=True) def main(): run_wsgi_app(application) if __name__ == "__main__": main()
(import周りは精査するのがめんどくさくて、そのままですが・・・)
それ以外の部分は以前の記事とほぼ同じです。
GAE/Pythonで、テンプレートを利用したHTMLの表示 - 混沌とした備忘録
これにより、welcome pageとして、'template/index.html'が表示され、
それ以外の'/page/hogehoge.html'へのリクエストは、共通処理を行ったあとに、
'template/hogehoge.html'の内容が表示されます。
webapp.WSGIApplicationの引数に、'(.*)'のように記述すると、
get関数の、selfの次の引数(第一引数と言うの?)として渡されるようです。
その変数を'render_page'という自作の関数に渡し、その中で共通処理を実行します。
結果を'template_values'に入れたりして、引数で指定されたページをrenderに渡しています。
特別な処理が必要なページに対しては、
class HogePage(webapp.RequestHandler): def get(self): # 特別な処理 render_page(self, 'hoge.html', template_values) def post(self): self.get(self) application = webapp.WSGIApplication([('/', StaticPage), ('/page/hoge.html', HogePage), ('/page/(.*)', StaticPage), ], debug=True)
のように、'/page/(.*)'より前に記述したら、そちらが優先されます。
ログイン処理などの非機能要件(?)は、本来はロジックと分離すべきかとは思います。
ただ、Javaで言う、「javax.servlet.Filter継承のクラスをweb.xmlに登録」みたいな機能がGAE+Pythonで見つかりませんでした。
知ってる人がいたら、教えてください。
書いてる途中で気がつきましたが、継承とか使えば、もうちょっと綺麗に書けるかも・・・
Linux環境でデータベースが保存されない
一時的には保存されるのですが、
再起動などをすると、保存したエンティティが削除されていました。
上記にあるとおり、google_appengine\dev_appserver.pyを引数なしで実行したところ、下記のように表示されました。
--datastore_path=DS_FILE Path to file to use for storing Datastore file
stub data.
(Default /tmp/dev_appserver.datastore)
デフォルトである、"tmp"ディレクトリ配下は、cronで定期的に削除されるようなので、
この指定を変更してやれば、削除されない気がする。
これで消えない。はず。
jQuery+jquery.color.jsで背景色をアニメーション
jQueryだけでは、animeteにbackgroundColorを渡しても何も起こりません。
そこで、文字色・背景色をアニメーションで徐々に変化させたかったら、プラグインが必要です。
http://plugins.jquery.com/project/color
から、
View all releases -> jquery.color.js.txtを保存。
jquery.color.jsにリネームし、HTMLから読み込む。
で、
$button = $('div#button'); $button.stop().animate({ backgroundColor: "#eee", color: "#333", }, 200);
とやれば、背景色がアニメーションで徐々に変わっていきます。
(<div id="button" />が表示されている前提です。)
ただ、この方法で背景色、文字色を変更した場合、CSSで
div#button:hover { background-color: #00f; color: #f00; }
としてあってもアニメーション終了時の色がstyle属性に直接設定されるようで、
ホバー時も色が変わりません。
なので、下記のようにCSSを追加
div#button { background-color: #fff; color: #000; } // 終了時と同じ色 div#button.enable { background-color: #eee; color: #333; }
と、JavaScriptも変更。
$button = $('div#button'); $button.stop().animate({ backgroundColor: "#eee", color: "#333", }, 200 , 'linear' , function() { $button.addClass('enable'); $button[0].style['background-color'] = null; $button[0].style['color'] = null; });
こんな感じで、アニメーション終了後にstyle属性から、それぞれのパラメータを削除します。
そして、その代わりに'enable'のclassを追加して、終了時の状態と合わせます。
こうすることで、色の情報をCSSから読み込んでいる状態になるので、同様にCSSに記載されている:hoverが有効になるようです。
GAE/Pythonで、テンプレートを利用したHTMLの表示
http://〜.appspot.com/page/test.html
などのURLをPythonのテンプレート機能を利用して表示する方法です。
app.yaml
handlers: - url: /template/(.*) script: /template/\1 - url: /page/.* script: test.py
1つ目のurlでは、テンプレート用のHTMLファイルが置いてある場所を定義しています。
HTMLファイルなので、"static_dir"でよいかと思っていたんですが、
scriptで指定しないと動かないようです。
2つ目のurlでは処理するPythonスクリプトファイルを指定しています。
今回は"/paga/〜"でリクエストがあった場合、すべてのリクエストを1つのスクリプトで処理することになっています。
(内部で処理するクラスを分けることは可能です)
/test.py
#!/usr/local/bin/python # -*- coding: utf-8 -*- import os import random from google.appengine.ext import webapp from google.appengine.ext.webapp import template from google.appengine.ext.webapp.util import run_wsgi_app class MyData(): stageNo = 0 size = 0 class MainPage(webapp.RequestHandler): def get(self): index = 0 if len(self.request.get('index')) == 0 else int(self.request.get('index')); summary = {} summary['count'] = 3 summary['index'] = index; myDatas = [] for i in xrange(3): m = MyData() m.stageNo = i + index m.size = random.randint(1, 20) myDatas.append(m) template_values = { 'summary': summary, 'myDatas': myDatas, } path = os.path.join(os.path.dirname(__file__), 'template', 'test.html') html = template.render(path, template_values) self.response.out.write(html) application = webapp.WSGIApplication([('/page/test.html', MainPage), ], debug=True) def main(): run_wsgi_app(application) if __name__ == "__main__": main()
辞書型である'summary'と、オブジェクトのリストである'mydatas'を設定しています。
設定値は適当なものを入れてみました。
それを'template_values'という辞書型に設定しています。
'path'にはテンプレートHTMLファイルのパスを設定しています。
実際に'/template/test.html'というファイルを利用するため、'join'を利用してこのように記述しています。
あとは'template.render'にパスとパラメータを渡して、出力します。
'webapp.WSGIApplication'の引数に'/page/test.html'となっているため、該当のURLでアクセスした場合に'MainPage'の関数が呼ばれます。
複数のURLを1つのクラスで処理したい場合は、'/page/.*html'などとしたら出来るようです。
(つまり、上記は'/page/test.html'と記述してあるため、'/page/testahtml'などでもアクセス出来てしまいます。
本来は'/page/test\.html'と記述するべきかもしれません。)
/template/test.html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>テスト</title> </head> <body> <!-- ヘッダ --> <header> <h1>テスト</h1> </header> <div id="summary"> 登録件数:{{ summary.count }}<br> インデックス:{{ summary.index }} </div> <div id="pager"> {{ pager }} </div> <div id="stages"> <ul> {% for myData in myDatas %} <li>StageNo:{{ myData.stageNo }}, size:{{ myData.size }}</li> {% endfor %} </ul> </div> </body> </html>
'{{'と'}}'で囲まれた箇所は、JSPの'<%='と'%>'と同じような感じかと思います。
(ただ、どちらかと言うと、JSP2.0以降のEL式の方が似てる気がします。)
'{%'と'%}'で囲まれた箇所は、JSPの'<%'と'%>'と同じような感じかと思います。
以上で'/page/test.html'とアクセスされた場合に、Pythonのスクリプトで処理した結果をHTMLに出力することができます。
ちなみに、HTMLを利用すると、JavaScriptのファイル・CSSファイルなどが必要になるかと思います。
その場合のapp.yamlの設定は下記のように出来ます。
- url: /js static_dir: js - url: /css static_dir: css
この設定によって、あとは普通に
<script src="/js/jquery-1.6.4.js"></script>
のように記述するとJavaScriptファイルを読み込めます。
詰め共円のページを作りました。
詰め共円のステージを一覧で見ることが出来るページを作成しました。
http://my-android-server.appspot.com/page/list.html
HTML5のcanvas要素を使っているため、
対応ブラウザ以外は正常に表示されない恐れがあります。
ゆくゆくは、このページで詰め共円アプリと同じように遊べるようにしたいと思っています。
現状は、ステージの確認とその拡大表示のみです。