Amazon

ラベル GAE の投稿を表示しています。 すべての投稿を表示
ラベル GAE の投稿を表示しています。 すべての投稿を表示

2011年11月30日水曜日

Google App Engineの新料金体系まとめ

Google App EngineがPreviewステージを卒業して正式サービスを開始したのを受けて新料金体系を発表したのは5月。適用は9/1とのことだったが、利用者からの猛反発を受けて以下に変更

幾つか新料金体系の変更を加えた
・新料金の適用を9/1から11/1へ延期
・無料Front Instance Hours 24 -> 28
・12/1のPython 2.7サポートまではFront Instance Hoursを課金を半額にする

さて、ここで自分がハマっていたことのメモ。
無料枠は依然残るものの、課金設定をしていると無料枠に収まっていても毎月$9(毎週$2.1)請求される。ただし、この毎週$2.1は最低利用料という扱いのため、例えばこの一週間で$1.3分を使用したとしても請求されるのは$2.1。つまり本請求は$2.1を超えたものから行われていく。

何も考えずにスケール対策のために課金設定にしていたので見事に毎週請求が来てました(ToT) 

課金設定を解除するにはBilling Setting -> Disable Billingにすること。
また、知らないうちに設定していたようだけど、9/1-10/31の間に課金設定にしてあれば、そのアプリケーションに対して$50分がもらえた様子。確かに自分のアプリケーションのいくつかも、Billing Historyの中でBalanseが($50)になっていた。ちなみに、請求されるとステータスはCharge Cancelとなるようです。

にしてもこの値上げは痛いなぁ。。。

さて、値上げ対策としてアプリケーションのチューニングが必要になるようだけど、
簡単な方法は
・Application SettingsでMax Idle Instancesを1、Min Pending Latencyを15Sにすること
・Datastoreへの問い合わせを極力減らして、Memcacheを使用

くらいのようです。

参考:Google App Engineの新料金体系対策

2011年11月27日日曜日

Aptana(Pydev)環境をWindowsからMacへ移動

MacBook Air(MacOS X Lion)を購入したので、
WindowsからMacへAptanaの環境を移行する方法をメモしておきます。

  1. Mac用のAptana Studioをダウンロード&インストール  
  2. Python 用 Google App Engine SDK 1.6のダウンロード&インストール
  3. WindowsからAptana Studio workspaceからプロジェクトフォルダをコピー
  4. Macの/Document/Aptana Studio 3 Workspaceの下に2.をコピー
  5. 各プロジェクト内の.pydevprojectファイルを編集
    * もしFinderで表示されていない場合はターミナルで以下のコマンドを打つと隠しファイルも表示されるようになる。
    % defaults write com.apple.finder AppleShowAllFiles TRUE
    逆に隠しファイルを隠す場合は
    % defaults write com.apple.finder AppleShowAllFiles FALSE

    .pydevprojectのファイルを編集

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <?eclipse-pydev version="1.0"?>

    <pydev_project>
    <pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
    <pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
    <pydev_variables_property name="org.python.pydev.PROJECT_VARIABLE_SUBSTITUTION">
    <key>GOOGLE_APP_ENGINE</key>
    <value>C:\Program Files\Google\google_appengine</value>
    </pydev_variables_property>
    <pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
    <path>/harnivorous/src</path>
    </pydev_pathproperty>
    <pydev_pathproperty name="org.python.pydev.PROJECT_EXTERNAL_SOURCE_PATH">
    <path>${GOOGLE_APP_ENGINE}</path>
    <path>${GOOGLE_APP_ENGINE}/lib/django</path>
    <path>${GOOGLE_APP_ENGINE}/lib/webob</path>
    <path>${GOOGLE_APP_ENGINE}/lib/yaml/lib</path>
    <path>C:\Python27\Lib\site-packages\nose-1.1.2-py2.7.egg</path>
    </pydev_pathproperty>
    </pydev_project>



    a) C:\Program Files\Google\google_appengine ->

    /Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine
    b) ${GOOGLE_APP_ENGINE}/lib/django -> ${GOOGLE_APP_ENGINE}/lib/django_1_2
    c) <path>C:\Python27\Lib\site-packages\nose-1.1.2-py2.7.egg</path> -> 削除
    d) <path>${GOOGLE_APP_ENGINE}/lib/simplejson</path> を追加
     
     
  6. Aptanaを起動してFile -> Import -> Existing Folder as New ProjectとしてProject TypeはデフォルトのWebのチェックをはずす
  7. GAEへのデプロイ(アップロード)起動オプションの作成。Run -> Run ConfigurationsでPyDev Google App RunをダブルクリックしてProjectは起動オプションを作りたいプロジェクト名を入れる。Browseを押しても出てこない場合は直接名前を入力。以下を設定したらApplyで保存
    -Main Tab-
    Project: THE_PROJECT_NAME
    Main Module: ${GOOGLE_APP_ENGINE}/appcfg.py
    -Arguments Tab-
    Program arguments: --email=GAE_ID --no_cookies update "${workspace_loc:THE_PROJECT_NAME/src}"
    Working directory: Defaultで${project_loc:_selected project name}

  8. GAEへのデプロイではなく、ローカルホストでの実行の場合は、上記の起動ウィザードのうち以下を変更。
    Main Module: ${GOOGLE_APP_ENGINE}/dev_appserver.py
    Program arguments: "{workspace_loc:THE_PROJECT_NAME/src}"

2011年8月15日月曜日

Google App Engineでローカルデバッグする際に


Google App Engineをローカルテストする際に、
ローカルのDatastoreにクエリを作成したりデータを更新したりする場合、
コードで書くのがめんどくさい時は、管理画面から操作が可能。

サーバを起動してから、以下のURLにアクセスする

http://localhost:8080/_ah/admin/

管理画面のDatastore Viewerページからデータを更新する(各モデルの要素の型を変更String->Integer)コードを書いて実施したけれども、
更新に時間差があるようですぐには反映されなかった。
そこで、同じく管理画面のDatstore Adminにて消したいmodelを選択して"Delete Entries"をクリック
するとDatastore Viewerに_AE_DatastoreAdmin_Operationというタスクが生成され、
エントリを消すタスクが処理待ちキューに入る。

2011年8月7日日曜日

Google App Engineでケータイサイトを作る際のポイント


参考:Google App Engineで、携帯サイトをつくる


○シフトJISのページを表示するには?

  1.  シフトJISで書いたテンプレートを使います(サンプルではtemplate_mobile.html)
  2.  self.response.headersを使って、ヘッダーにシフトJISである旨、情報を追加します
  3.  カスタムフィルター(サンプルではcustomfilters_m.py)を使って、テンプレートに差し込む文字列をユニコードからシフトJISに変換します

○シフトJISのページからPOSTされたデータを受け取るには?
  1.     POSTされたデータを受け取るためにself.request.get()を使っても、その結果はすでに文字化けしています
  2.     self.request.get()を使うまえに、self.request.charsetを定義します

○"~","-", "‐", "||"などの変換
上記の方法で body.encode('Shift-JIS')で変換しても以下のエラーが出る場合がある
UnicodeEncodeError: 'shift_jis' codec can't encode character u'\uff5e'
どうやらMSとUnicodeとShift-JISの間での変換マッピングにズレがあるようで、これを直す必要がある。

[参考]Pythonで「~」をエンコードすると例外が発生


サンプルコード
mobile.py
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import wsgiref.handlers, os
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template

webapp.template.register_template_library('customfilters_m')

class MainPage(webapp.RequestHandler):
    def get(self):
        message = u"ようこそ、テストのサイトへ"
        template_values = {
            'message': message,
        }
        path = os.path.join(os.path.dirname(__file__), "template_mobile.html")
        self.response.headers['Content-Type'] = "text/html; charset=Shift_JIS"
        self.response.out.write(template.render(path, template_values))
    def post(self):
        self.request.charset = "Shift_JIS"
        message = self.request.get("new_message")        
        template_values = {
            'message': message,
        }
        path = os.path.join(os.path.dirname(__file__), "template_mobile.html")
        self.response.headers['Content-Type'] = "text/html; charset=Shift_JIS"
        self.response.out.write(template.render(path, template_values))

def main():
    application = webapp.WSGIApplication(
            [('/m/', MainPage)],
            debug=True)
    wsgiref.handlers.CGIHandler().run(application)

if __name__ == "__main__":
    main()
customfilters_m.py
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import datetime
from google.appengine.ext import webapp

register = webapp.template.create_template_register()

def sjis(body):
body = string.replace(body, u'\uff5e', u'\u301c') # fullwidth tilde "~"
    body = string.replace(body, u'\u2015', u'\u2014') # Horizontal bar "―"
    body = string.replace(body, u'\u2225', u'\u2016') # Parallel to "||"
    body = string.replace(body, u'\uff0d', u'\u2212') # Fullwidth hyphen-minus "-"
    return body.encode('Shift_JIS')

register.filter(sjis)
template_mobile.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=SHIFT_JIS" />
<title>テスト</title>
</head>
<body>
{{ message|sjis }}
<form method="post" action="#">
<input type="text" name="new_message" /><br>
<input type="submit" value="送信"  />
</form>
</body>
</html>


○モデルに日本時刻で記録するには?
Google App Engineで以下のようにモデルをつくっておくと、データが挿入されたときに、自動的にその時の時刻が記録されていくので便利なのだが、この時刻はUTCであって日本時間ではない。UTCで記録していくのはよいとして、表示するときは、どうにかして9時間プラスしておかないとまずい。
from google.appengine.ext import db
class hoge(db.Model):
    timestamp = db.DateTimeProperty(auto_now_add=True)
解決方法

テンプレートに、与えられた時刻に9時間を足すカスタムフィルターを登録する。

(1) 以下のコードをcustomfilters.pyというファイル名で保存する。
import datetime
from google.appengine.ext import webapp
register = webapp.template.create_template_register()
def jshift(body):
    return body + datetime.timedelta(hours=9)
register.filter(jshift)
(2) メインとなるコードのほうに以下の1文を追加する。モジュールのインポートが終わったあたりがよい。
webapp.template.register_template_library('customfilters')
(3) テンプレートでは、以下のように記述する。
<p>{{ item.timestamp|jshift|date:"Y-m-d H:i" }}</p>

2011年8月1日月曜日

Google App Engineでテンプレートを使用する際の注意点

Google App EngineではDjangoを利用することができる。
テンプレートを使う時はテンプレートフォルダを作って、そこにHTMLのひな形ファイルを作成して使用する。

ファイル構成例
tmpl/hello.html
app.yaml
main.py

class TestHandler(webapp.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'application/xhtml+xml; charset=Shift_JIS'
        template_values = {
            'app_id': "test",
            'user_info': "userinfo",
        }
  
        path = os.path.join(os.path.dirname(__file__), 'tmpl/hello.html')
        self.response.out.write(template.render(path, template_values)) 
 


但し、一つ注意するのが、テンプレートファイルは静的ファイル扱いではなくスクリプトファイル扱いとなるので、app.yamlに
- url: /tmpl
  static_dir: tmpl
ではなくて
 - url: /tmpl
  script: tmpl/(.html)
とする。
そうしないとTemplateDoesNotExist: hello.htmlというエラーが出る。

2011年7月31日日曜日

フィーチャーフォンのアプリを作る際の注意点





Perlで作るモバイルサイトのコツ  より

・文字コード

携帯用サイトの構築を行う場合にはWebアプリケーション内部ではUTF-8を使用し、出力の際にはShift_JISを使用できるようにUTF-8とShift_JISを相互変換する処理を実装する必要があります。

・キャリアに応じたXHTMLの出力

キャリアの仕様


・セッション管理

セッション管理は、ユーザーからのリクエストごとに一意のID(セッションID)を発行し、常にそのIDをリクエストに含ませることで行います。 PCであればクッキーを使用してセッションIDをブラウザに保持しておくことができますが、前述したとおり携帯ではクッキーを使用できる端末が限られるた め、画面遷移時にセッションIDを直接リクエストのパラメータとして渡す必要があります。

 パラメータを引き継ぐ必要のある遷移のパターンは以下の3つです。

  1. リンクによる遷移
  2. フォームによる遷移
  3. リダイレクトによる遷移

 

○PythonでUser Agentの値を取得する

http://d.hatena.ne.jp/perezvon/20080919/1221844404

logging.info(dir(self.request))でuser_agentというプロパティ

# it works!
self.request.user_agentを使う
 
○User Agentの値を表示するPythonサンプル 
 
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

class Receiver(webapp.RequestHandler):
  def get(self):
    self.response.out.write(
    """
    <html>
      <body>
        <b>%s</b>
      </body>
    </html>
    """ % self.request.user_agent)
    
    
application = webapp.WSGIApplication([
  ("/", Receiver)
], debug=True)

def main():
  run_wsgi_app(application)

if __name__ == "__main__":
  main() 
 
 
Google App Engine for Pythonでのセッション管理の実装
セッション管理部分のみ抜粋

def getSession(request):
# クッキーのセッションIDとmemcache/Datastoreのセッション情報を照合する関数
# リクエストハンドラーのself.requestを引数として受け取る

    # クッキーからセッション番号を取得
    sid = request.cookies.get('SID', '')
    if sid:
        # まずmemcacheにそのセッション番号があるか探す
        session = memcache.get(sid)
        if session:
            return session

        # memcacheになければDatastoreから探す
        else:
            session = Session.get(sid)
            if session:
                # Datastoreにあればmemcacheにも入れておく
                memcache_key = str(session.key())
                memcache_value = {
                    'user' : {
                        'key' : str(session.user.key()),
                        'name' : session.user.name,
                    },
                }
                memcache.add(key=memcache_key, value=memcache_value, time=3600)

                return session
 
Google App EngineでDjangoのテンプレートエンジンを使う
import wsgiref.handlers
from google.appengine.ext import webapp

#テンプレートを使用するため、下記二つのimportを追加
import os
from google.appengine.ext.webapp import template

class MainHandler(webapp.RequestHandler):

    def get(self):
        #テンプレートに渡す果物リスト
        fruits = ('みかん','りんご','バナナ','パイナップル','スイカ')
        #テンプレートに渡す文字列
        j_str = '日本語文字列';
        
        #連想配列にテンプレートに渡す値を設定
        #テンプレートからは、連想配列のキーでアクセスする。
        template_values = {'list' : fruits,
                            'str_value' : j_str}
        
        #使用するテンプレートを指定
        path = os.path.join(os.path.dirname(__file__), 'template/index.html')
        #レンダリングを実行し、結果をレスポンスとしてブラウザに返却
        self.response.out.write(template.render(path, template_values))

def main():
    application = webapp.WSGIApplication([('/', MainHandler)],
                                       debug=True)
    wsgiref.handlers.CGIHandler().run(application)


if __name__ == '__main__':
    main()
 
アプリケーションの構成はapp.yamlと同階層フォルダにtemplateを作って、その下にindex.htmlテンプレートファイルを作る
template/index.html
app.yaml
index.yaml
main.py 

2011年3月26日土曜日

GAEでDirectoryIndexを指定する

例えば、http://www.hogehoge.com/にアクセスすると実はその下にあるhttp://www.hogehoge.com/index.htmlやindex.phpに自動でアクセスするようにすることをDirectoryIndexというらしい。(Apache的に?)

これをApacheのWebサーバーで実現する際には.htaccessファイルにDirectoryIndexという設定項目を記述する。

-----以下こちらより引用
DirectoryIndex index.html index.cgi index.php index.shtml
と書くと、「/(スラッシュ)」止めのURLでアクセスされた場合に表示させるファイル名の優先順位を指定できます。つまり、上のような設定をしている場 合は、index.htmlがそのディレクトリー内にあれば、「/(スラッシュ)」止めのURLでのアクセス時にindex.htmlのコンテンツが表示 されますが(http://www.example.comでアクセスしてもhttp://www.example.com/index.htmlでアクセスしても同一のコンテンツが表示されるということ)、index.htmlが無ければindex.cgiを探しに行き、さらに、それが無ければ、index.phpをと見ていきます。列挙されているファイル名全て見て、どれも存在しなかった場合で、かつ、「Options Indexes」がonに設定されている場合は、そのディレクトリー内のファイル一覧が表示されますので、注意が必要です。「Options Indexes」をonにする場合の危険性については、こちらでも説明していますので、参照してください。
 -----

で、同じことをGAEで実現しようとすると「.htaccessはどこにあるんだ?」となる。
少なくともユーザーは.htaccessファイルは触れないので代わりの方法としてapp.yamlに擬似的に記述する。以下がその記述。

app.yamlの記述詳細はGAEドキュメントに譲るとして




------- app.yamlの一部
handlers:

- url: /
  static_files: htdocs/index.html
  upload: /

- url: /
  static_dir: htdocs




-------

この記述がhttp://hogehoge.appspot.com/というアプリケーションのものだとすると、
ユーザーがhttp://hogehoge.appspot.com/にアクセスすると実際はhttp://hogehoge.appspot.com/static/index.htmlに自動転送される。このファイルは別にindex.htmlでなくても良い

2011年3月21日月曜日

GQLの注意点(まとめ)

  • StringPropertyは500バイト未満まで、それ以上はTextProperty
  • リクエスト1 つのクエリに対して最大 1,000 件の結果
  • IN!= の各演算子は、背後で複数のクエリを使用します。たとえば IN 演算子は、リスト内のどのアイテムについても、基礎となるデータストア クエリを個別に実行します。返されたエンティティは、すべての基礎となるデータストア クエリのクロス積の結果であり、重複が除去されます。1 つの GQL クエリに最大 30 のデータストア クエリが許可されます。(超えた場合はrc=504 errorを返す)
  • 静的 ListProperty には、空のリストを値として割り当てることができます。このプロパティはデータストアには存在しませんが、モデル インスタンスの動作は値が空のリストである場合と同じです。静的 ListProperty は、None の値を持つことはできません。
  • list 値を持つ動的プロパティには、空のリスト値を割り当てることはできません。ただし、この動的プロパティは None の値を持つことができるほか、del を使用して削除することもできます。
  •  
- 制限
最大エンティティ サイズ 1 MB
エンティティのインデックス値の最大数 (1) 1、000 個
バッチ put または delete の最大エンティティ数 500 エンティティ
バッチ get の最大エンティティ数 1,000 エンティティ
クエリの最大結果オフセット 1,000

GAE上のデータストアからデータを消す方法

GAEからデータを削除する方法は公式ドキュメントによれば以下の方法で削除できる。

q = db.GqlQuery("SELECT * FROM Message WHERE create_date < :1", earliest_date)
results = q.fetch(10)
for result in results:
  result.delete()

# or...

q = db.GqlQuery("SELECT * FROM Message WHERE create_date < :1", earliest_date)
results = q.fetch(10)
db.delete(results)


但し、GQLには以下の制限があって、保存してあるデータ量が多いと消えない可能性がある。
  • 一定時間内にリクエストを返すべし
  • 一度に取り出せる量が1000件まで
  • 使えるリソースの上限を超えた
そこでこちらのサイトを参考にしながら、制限を超えないように一回で100件消すリクエストを発行するpythonのスクリプトを作り、全部消えるまで自動でリロードするようにする。
getMyData, getMyURLを自分のGAEデータモデルに変更すること。

from google.appengine.ext import webapp
from google.appengine.ext import db
from google.appengine.ext.webapp.util import run_wsgi_app


class MyData1(db.Model):
    pass

class MyData2(db.Model):
    pass


class AbstractRemoveAll(webapp.RequestHandler):

    def get(self) :

        #limit = 50
        limit = 100
        list  = self.getMyList(limit)

    if list==None :
            self.response.headers['Content-Type'] = 'text/plain'
            self.response.out.write( 'Error.' )
            return 

        if len(list)<1:
            self.response.headers['Content-Type'] = 'text/plain'
            self.response.out.write( 'OK remove all data.' )
            return 


        for c in list:
            c.delete()

        self.response.headers['Content-Type'] = 'text/html'
        self.response.out.write( '<html>' )
        self.response.out.write( '<head>' )
        self.response.out.write( '<META HTTP-EQUIV="REFRESH" CONTENT="10;URL='+self.getMyUrl()+'">')
        self.response.out.write( '</head>' )
        self.response.out.write( '<body>' )
        self.response.out.write( '<p>' )
        self.response.out.write( len(list) )
        self.response.out.write( '</p>' )
        self.response.out.write( 'Again after 10seconds.' )
        self.response.out.write( '</body>' )
        self.response.out.write( '</html>' )


    def getMyUrl(self) :
        return '/RemoveAll'

    def getMyList(self,limit):
        return []


class RemoveAll1(AbstractRemoveAll):
    def getMyUrl(self) :
        return '/RemoveAll1'

    def getMyList(self,limit):
        q=MyData1.gql('LIMIT '+str(limit))
    return q[0:min(q.count(),limit)]

class RemoveAll2(AbstractRemoveAll):
    def getMyUrl(self) :
        return '/RemoveAll2'

    def getMyList(self,limit):
        q=MyData2.gql('LIMIT '+str(limit))
    return q[0:min(q.count(),limit)]



application = webapp.WSGIApplication(
    [
        ('/RemoveAll2', RemoveAllPage2),
        ('/RemoveAll1', RemoveAllPage1)
    ],
    debug=True)


def main(): 
    run_wsgi_app(application)

if __name__ == "__main__": 
    main()

2011年3月10日木曜日

GAEのgqlでIN演算子を使う

GAEのGQLのリファレンスによれば 
"IN 演算子は、プロパティの値とリスト内の各アイテムを比較します。IN 演算子は、各値に 1 つのクエリが OR 演算された多数の = クエリに相当します。このクエリでは、指定されたプロパティの値がリスト内の値と同じであるエンティティが返されます。"

friendInfo = ["f0001","f0002"]
someFriend = FriendModel.gql('WHERE friend IN :1",friendInfo)

という感じでFriendModel内のfriendプロパティ値がfriendInfoにあるアイテムの値("f0001","f0002")であるときに抽出してくる。

とても使いやすいのだけど、この比較するアイテムリストを空にした場合はどうなるか?
直感的には何もヒットしないのが正解と思うのだけど、
実際のところは全部がヒットする。つまり、この場合はWHERE句がないのと同じようです。
someFriend = FriendModel.gql('WHERE friend IN :1 AND gender=:2",friendInfo,gender)
だと、friend IN friendInfoは無視されて、gender=genderの条件だけ適用されるみたい。
なんだか変な感じ。
自分の環境では、このIN演算子は無視されるよう。

2011年2月27日日曜日

OpenSocialアプリ開発の注意点

こちらより
opensocial系のアーキテクチャは、以下の前提を持つ

  • トリガーとなる html は、opensocialのコンテナが作成するものであり、基本的に gadget 開発者の制御下にない。
  • トリガーとなる html 内で gadget 開発者の制御下にあるのは、gadget 定義 xml の Content 要素内のみで、これが トリガーとなる html の body 内に配置される。
  • トリガーとなる html 内に iframe で外部サイトを参照する場合、iframe 内からは opensocial 系機能を一切使えない。つまり、トリガーとなる html 内から直接的な関係にある javascript や flash からのみ opensocial 系機能を使用することができる。
  • 外部サーバとの連携には、予め用意された特定の API から以外アクセスすることができない。つまり、独自の RPC 等ラップしないかぎり不可能。

2011年2月26日土曜日

JavaScriptでJSON形式を受け取るには?

GAEからsimplejsonを使用してJSON形式で取得したデータはJavaScriptのeval()メソッドで解釈する。
但し、その際はJSON文字列を()でくくると良い。

ここで使用しているGaeEncoderはこちらを参考。


>>> jsonstr = "{\"key\":\"value\"}"
"{"key":"value"}"
>>> obj = eval("("+jsonstr+")")
[object Object]
>>> obj.key
"value"
実際にGAE側のPythonからJavaScriptに送るには
1.データモデルからgqlを使ってデータを抽出
2.GaeEncoder()でgql StringをJSON形式(list)に変換
3.content_typeを"application/json"に指定
4.responseを返却 
 
def get(self):
g_list = Field.gql('order by fieldID')
        g_listJson = GaeEncoder().encode(g_list) 
        self.response.content_type = "application/json"
        self.response.out.write(g_listJson)

2011年2月20日日曜日

GAEとDjangoと

GAEで開発するためにローカルでテストしているとデータストアにデータが溜まってく一方で消したくなる。


データストアの使用に開発用サーバーのデータを消す方法が書かれているものの

dev_appserver.py --clear_datastore helloworld/

自分のAptanaの環境ではdev_appserver.pyを使っていない。(実際はC:\Program Files\Google\google_appengineに入っている。)

仕方ないので

def deleteDB(self):
        g_list = DataStore.gql('order by element')
        for element in g_list:
                element.delete()

というメソッドを作っていちいち消さなきゃいけないのかなぁ。

Google App EngineでDjango1.2を動かす5

File "C:\Documents and Settings\Administrator\My Documents\Aptana Studio Workspace\Festival\src\django.zip\django\conf\__init__.py", line 38, in _setup
    raise ImportError("Settings cannot be imported, because environment variable %s is undefined." % ENVIRONMENT_VARIABLE)
ImportError: Settings cannot be imported, because environment variable DJANGO_SETTINGS_MODULE is undefined.

というエラーが出た。

とりあえずググってみると、こちらにエラーの回避方法を発見


場合はmodels.pyに

djangoのモデルをインポートする前に
from google.appengine.ext.webapp import template
をimportする必要があるとのこと。

つまりmodels.pyの冒頭に
from appengine_django.models import BaseModel

from google.appengine.ext.webapp import template
ではなく

from google.appengine.ext.webapp import template
from appengine_django.models import BaseModel



GAEでDjangoを動かす際の有用リンク集

Djangoの基本を勉強するなら

Python Webフレームワーク、第1回: DjangoとPythonを使ってWeb開発




もう少し進んで簡単なアプリケーションを作成する

Google App EngineでDjango1.2を動かす4

Aptana + Djangoでハマった際のメモ。

ローカルサーバーでテンプレートフォルダにテンプレートファイル(index.html)を入れて表示したい場合に、
settings.pyのTEMPLATE_DIRSにテンプレートフォルダをos.path.join(ROOT_PATH, "templates")で追加しているが、
ROOT_PATHはどこかというとos.path.dirname(__file__)
じゃあ実際にos.path.dirname(__file__)はどこかというと
"C:\Documents and Settings\Administrator\My Documents\Aptana Studio Workspace\Festival\src"

views.pyでテンプレートページの指定として
    return render_to_response('../templates/festa/index.html', {'g_list':g_list})
としているが、render_to_responseの第一引数が問題。

これを上記で指定したC:\Documents and Settings\Administrator\My Documents\Aptana Studio Workspace\Festival\src\templateだと思って、第一引数をfesta/index.htmlとするとそんなファイルがないと怒られる。

どうも、views.pyのファイル位置はC:\Documents and Settings\Administrator\My Documents\Aptana Studio Workspace\Festival\src\festaというパッケージの中なので、そりゃfesta/index.htmlは見つからない。よって相対パスとして'../templates/festa/index.html'とすれば正しく動く。

うーむ、TEMPLATE_DIRSが上手く反映されていないようだ。



なぞが解けたら追記しよう。

-----

Google Codeヘルプ"Google App Engine Helper for Django の使用"より

GAE上のDjangoの管理サイト

Django 管理サイトは SQL データベースのコンセプトと密接に関係しているため、Google App Engine ではサポートされていません。開発用 appserver により、代わりの管理インターフェースが、/_ah/admin(たとえば http://localhost:8080/_ah/admin)で自動的に提供されます。













2011年1月4日火曜日

Google App EngineでDjango1.2を動かす3

[稼動環境] Aptana Studio 2.0.5  + Python 2.7.1+ Pydev 1.6.4+ Django 1.2.4 + Google App Engine Helper for Django 1.0.9

前回、前々回からのどハマリ続きのDjango 1.2.4 on GAE
Google App EngineでDjango1.2を動かす2
Google App EngineでDjango1.2を動かす1

引き続き動かないので、問題判別のために幾つか実験をば。

  • [Djangoのバージョン問題?] django.zipの中身を1.2.4から1.1.3に変更 -> 変わらず
  • [Eclipseの問題?] django.zipを消して、LocalのPCにインストールされているDjango1.2.4を使用、main.pyを通常のmain.pyに変更 -> "google.appengine.dist._library.UnacceptableVersionError"となる
  • [Eclipseの問題?] django.zipを消して、LocalのPCにインストールされているDjango1.2.4を1.1.3に変更、main.pyを通常のmain.pyに変更 -> 正常に稼動

以上の検証結果より、main.pyが悪さをしている様子。

もう一度main.pyを見直してみる。
もともとappengine_helper_for_django-r109.zipを展開したmain.pyは以下の通り



# Standard Python imports.
import os
import sys
import logging

from appengine_django import InstallAppengineHelperForDjango
InstallAppengineHelperForDjango()

from appengine_django import have_django_zip
from appengine_django import django_zip_path

# Google App Engine imports.
from google.appengine.ext.webapp import util

# Import the part of Django that we use here.
import django.core.handlers.wsgi

def main():
  # Ensure the Django zipfile is in the path if required.
  if have_django_zip and django_zip_path not in sys.path:
    sys.path.insert(1, django_zip_path)

ここでInstallAppengineHelperForDjango()の行でインストールされているDjangoのバージョンをチェックして、1.2以上だとgoogle.appengine.dist._library.UnacceptableVersionErrorをはいていた。

そこでGoogleの"Zipimport を使って App Engine で Django 1.0 を使用する"を参照して作成したmain.pyが以下


# Standard Python imports.
import os
import sys
import logging

from appengine_django import InstallAppengineHelperForDjango
InstallAppengineHelperForDjango()

from appengine_django import have_django_zip
from appengine_django import django_zip_path

# Google App Engine imports.
from google.appengine.ext.webapp import util

# Import the part of Django that we use here.
import django.core.handlers.wsgi


# Uninstall Django 0.96.
for k in [k for k in sys.modules if k.startswith('django')]:
    del sys.modules[k]

# Add Django 1.0 archive to the path.
django_path = 'django.zip'
sys.path.insert(0, django_path)
print sys.path

# Django imports and other code go here...
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'


def main():
  # Ensure the Django zipfile is in the path if required.
  if have_django_zip and django_zip_path not in sys.path:
      sys.path.insert(1, django_zip_path)

"# Uninstall Django 0.96."行から"def main()"までが追記したコード。

だけど、これをよくよく考えてみると"InstallAppengineHelperForDjango()"の後に、Django 0.96モジュールを抜いて、zipにしたDjango1.2.4をimportしている。

要は、せっかくモジュールのチェックをしたのにその後にモジュールを入れ替えているわけです。そりゃ意味ないわな。。。

ということでモジュール入れ替えを先にしたコードが以下。


# Standard Python imports.
import os
import sys
import logging

# Uninstall Django 0.96.
for k in [k for k in sys.modules if k.startswith('django')]:
    del sys.modules[k]

# Add Django 1.2.4 archive to the path.
django_path = 'django.zip'
sys.path.insert(0, django_path)

from appengine_django import InstallAppengineHelperForDjango
InstallAppengineHelperForDjango()

from appengine_django import have_django_zip
from appengine_django import django_zip_path

# Google App Engine imports.
from google.appengine.ext.webapp import util

# Import the part of Django that we use here.
import django.core.handlers.wsgi

def main():
  # Ensure the Django zipfile is in the path if required.
  if have_django_zip and django_zip_path not in sys.path:
      sys.path.insert(1, django_zip_path)

これで正常に稼動した。

今回のどハマリの原因は
Eclipseの起動設定とGoogle App Engine Helper for DjangoのDjango受け入れバージョン問題, Django 1.2の使用, という3つの点が相まって問題を複雑にしていたわけですね。。。(--;

Google App EngineでDjango1.2を動かす2

[稼動環境] Aptana Studio 2.0.5  + Python 2.7.1+ Pydev 1.6.4+ Django 1.2.4 + Google App Engine Helper for Django 1.0.9

前回(google App EngineでDjango1.2を動かす1)の続き

エラーの中身を見るあたり"ImportError: No module named antlr3"とのことで、
要はantlr3のモジュールが見つからないということ。
antlr3モジュールはgoogle_appengine\libの下にあるが、どうもこのフォルダへのパスが通ってなかったのが原因ぽい。


ということで、プロジェクトを右クリックしてProperties -> PyDev - PYTHONPATH -> External Libraryに
${GOOGLE_APP_ENGINE}/lib/antlr3
${GOOGLE_APP_ENGINE}/lib/ipaddr
${GOOGLE_APP_ENGINE}/lib/fancy_urllib
を追加。
自分の環境では、${GOOGLE_APP_ENGINE}はC:\Program Files\Google\google_appengineを指す


さて改めて実行したが、またエラー。今度は
"ImportError: No module named admin.options"
これはdjango.zipファイルのdjango\contrib\admin\options.pyを指している様子。

GAEのドキュメント"Zipimport を使って App Engine で Django 1.0 を使用する"ではadminフォルダを消してもいいって書いてあるのに。。。
仕方ないのでDjangoフルパッケージを再度django.zipにしてプロジェクトのdjango.zipと置き換える。

気を取り直して再実行。けどまたエラー。
"google.appengine.tools.dev_appserver.AppConfigNotFoundError"です。
このエラーはapp.yamlファイルが見つからないのが原因のようです。
appengine_helper_for_django-r109.zipファイルをimportした際にsrcフォルダ下にちゃんとapp.yamlファイルがあるのに正しく認識されないのはどうしてだ?と思っていたら、
今度はEclipse側の設定の問題だったようです。

Run -> Run Configurationで、先ほど作成したPydev Google App Engineの起動設定のArgumentsのWorking directoryをDefaultの${project_loc}からOtherにチェックを入れて${project_loc}/srcに設定を変更。



これで再度実行。すると、consoleに色々とメッセージが出る。

"INFO:root:zipimporter('C:\\Documents and Settings\\Administrator\\My Documents\\Aptana Studio Workspace\\DjangoOnGAE\\src\\django.zip', 'django\\contrib\\')"
Djangoライブラリが読み込まれている


"WARNING:root:Could not read datastore data from c:\temp\django_djangoongae.datastore"
django_djangoongae.datastoreが見つからないと言われるが、そもそもファイルがないからそのまま放置

"WARNING:root:Could not initialize images API; you are likely missing the Python "PIL" module. ImportError: No module named _imaging"
これもnamed_imagingモジュールがないと言われているが解決策はGAEのサイトにある。とりあえず今は画像を使わないのでこれも放置


とはいえ
"INFO:root:Running application djangoongae on port 8001: http://localhost:8001"が表示されたので起動しているっぽいのでアクセス。

するとまだエラーが出る。

ChromeでアクセスするとResolver404エラー

 EclipseのConsoleにも"django\core\handlers\wsgi.py:222:  RuntimeWarning: Parent module 'django.core.handlers' not found while handling absolute import"などが表示されて、"INFO:root:"GET / HTTP/1.1" 500 -"が出ている。

むー。なかなか先は長いですね。


2011年1月3日月曜日

Google App EngineでDjango1.2を動かす1


Google App Engine上でDjangoを使用する場合、幾つかの制限がある。
それを補完するためにGoogle App Engine Helper for Djangoというツールが提供されている。
使用方法はこちらを参照。

ただ、このガイドではEclipseから利用する方法が書いていないので、今回はこの手順をまとめてみる。

使用するのは
[稼動環境] Aptana Studio 2.0.5  + Python 2.7.1+ Pydev 1.6.4+ Django 1.2.4 + Google App Engine Helper for Django 1.0.9

1. Aptana(Eclipse)を起動し、Pydev Package Explorerを右クリックしてNew->Projectを選択し、Pydev->Pydev Google App Engine Projectを選択


2. Project nameを適当につけて、Project typeをPython, Grammar Versionを2.7にしてNext>

3. Google App Engine Dictionaryのパスを指定。google_appengineフォルダを指定すると、自動で${GOOGLE_APP_ENGINE}, ${GOOGLE_APP_ENGINE}/lib/django, ${GOOGLE_APP_ENGINE}/lib/webob, ${GOOGLE_APP_ENGINE}/lib/yaml/libが追加される

4. application id registered for this projectをGAEに登録しているIDとし、templateはひとまずEmpty Projectとする

5. できたプロジェクトのsrcフォルダ内に、Google App Engine Helper for Djangozipファイルをimportする。

6. importするファイルタイプをArchive Fileを選択

7. ダウンロードしたzipファイルを選択
 8. プロジェクトにzipファイルの中身がimportされる。

9.appengine_helper_for_djangoパッケージは不要なので、それより下のappengine_djangoパッケージなどをsrcフォルダに移動して、appengine_helper_for_djangoフォルダを削除する

10. プロジェクトを選択肢、Run -> Run ConfigurationでPydev Google App Runをダブルクリック。Nameは適当に入力し、Projectを実行したいプロジェクトを選択、Main Moduleをimportしたmanage.pyを選択

 11. argumentsタブでprogram argumentsに"runserver"を入力して、Run

12.実行後コンソールには"google.appengine.dist._library.UnacceptableVersionError"が表示される

どうやらappengine_djangoパッケージにある__init__.pyのなかでDjangoのバージョンを1.0か1.1に限定しているようで、このままでは使用できないようだ。

次回(Google App EngineでDjango1.2を動かす2)へ続く。

Amazon3