<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;D0QARnk6fCp7ImA9WhRaFEk.&quot;"><id>tag:blogger.com,1999:blog-4177643508104568252</id><updated>2012-02-17T10:22:27.714+08:00</updated><category term="pydev" /><category term="recipe" /><category term="openid" /><category term="tags" /><category term="quota" /><category term="transaction" /><category term="scale" /><category term="python" /><category term="snow leopard" /><category term="counter" /><category term="mac" /><category term="tutorial" /><category term="readme" /><category term="datastore" /><category term="property" /><category term="entity group" /><category term="eclipse" /><category term="datetime" /><category term="model" /><category term="template" /><category term="osx" /><category term="account" /><category term="ide" /><title>Google App Engine 最佳實踐</title><subtitle type="html" /><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://practicalappengine.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://practicalappengine.blogspot.com/" /><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="26" height="32" src="http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>12</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/practicalappengine" /><feedburner:info uri="practicalappengine" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry gd:etag="W/&quot;DkAESXc5fyp7ImA9Wx5RGEQ.&quot;"><id>tag:blogger.com,1999:blog-4177643508104568252.post-5711704327516302629</id><published>2010-08-27T15:38:00.000+08:00</published><updated>2010-08-27T15:38:28.927+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-08-27T15:38:28.927+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="datastore" /><category scheme="http://www.blogger.com/atom/ns#" term="datetime" /><category scheme="http://www.blogger.com/atom/ns#" term="model" /><title>App Engine 上的時區問題</title><content type="html">在 App Engine 上如果要儲存時間，在 model 中就會用 DateTimeProperty, DateProperty 或 TimeProperty 來做為資料型態，比如說：&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;...
from google.appengine.ext import db

class FooModel(db.Model):
  ...
  ...
  created = db.DateTimeProperty(auto_now_add=True)
&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;以 &lt;code&gt;DateTimeProperty&lt;/code&gt; 來說，它對應到的資料結構是 python 標準函式庫中的 &lt;code&gt;datetime.datetime&lt;/code&gt;，不過，當你加上了 &lt;code&gt;auto_now_add=True&lt;/code&gt; 這個設定之後，每當建立一個新的 entity 時，Datastore API 就會自動加上「現在時間」，也就是 &lt;code&gt;datetime.datetime.now()&lt;/code&gt; 的資料，但是 App Engine 所使用的預設時區是 UTC（也就是 GMT+0），簡單地說，如果你的網站使用者都是台灣的用戶，那就會感覺到這個時間晚了八個小時（因為台灣的時區是 CST，也就是 GMT+8），如果要處理這個問題，這裡提供兩個可能的解決方法：&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;不要依賴自動產生的時間，DIY&lt;/h3&gt;第一個解決方法，就是在產生/儲存 entity 時不要讓 Datastore API 幫你產生現在的時間，每次在產生或是更新 entity 資料時，自己手動產生出正確的時間。在解決問題之前，首先要製作一個台灣時區的 &lt;code&gt;tzinfo&lt;/code&gt;，才可以去改變 &lt;code&gt;datetime.datetime&lt;/code&gt; 所表示的時區：&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;...
from datetime import tzinfo, timedelta

class TaiwanTimeZone(tzinfo):
    def utcoffset(self, dt):
        return timedelta(hours=8)

    def tzname(self, dt):
        return 'CST'

    def dst(self, dt):
        return timedelta(hours=0)
&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;
接著，在產生或更新 entity 資料時：&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;...
foo = FooModel(......, 
               created=datetime.datetime.now(TaiwanZone()))
foo.put()
&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;這樣就會用台灣時區（GMT+8）的時間來儲存資料了。&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;在輸出時用 filter 換掉&lt;/h3&gt;另外一個方式，就是在 template 要輸出時再換掉時區，當然還是要準備一個 &lt;code&gt;TaiwanTimeZone&lt;/code&gt; 的 &lt;code&gt;tzinfo&lt;/code&gt; 類別，然而在儲存資料時，還是使用預設的 UTC 時區去處理，但是可以自訂一個 template filter （詳細的作法請見&lt;a href="http://practicalappengine.blogspot.com/2010/08/filter.html" target="_blank"&gt;這篇文章&lt;/a&gt;）在 template 中顯示時動態換掉時間及時區：&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;# get template register
register = webapp.template.create_template_register()

@register.filter
def twtz(value):
    from datetime import datetime, timedelta
    return (value + timedelta(hours=8)).replace(tzinfo=TaiwanTimeZone())
&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;這樣只要在 template 中加上這個 filter 來顯示就可以了。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4177643508104568252-5711704327516302629?l=practicalappengine.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/q6uqGmRBN3GBe8-8-Y6nLnePWt0/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/q6uqGmRBN3GBe8-8-Y6nLnePWt0/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/q6uqGmRBN3GBe8-8-Y6nLnePWt0/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/q6uqGmRBN3GBe8-8-Y6nLnePWt0/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/practicalappengine/~4/q3TJ3xPoECU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://practicalappengine.blogspot.com/feeds/5711704327516302629/comments/default" title="張貼意見" /><link rel="replies" type="text/html" href="http://practicalappengine.blogspot.com/2010/08/app-engine.html#comment-form" title="2 個意見" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/5711704327516302629?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/5711704327516302629?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/practicalappengine/~3/q3TJ3xPoECU/app-engine.html" title="App Engine 上的時區問題" /><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="26" height="32" src="http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://practicalappengine.blogspot.com/2010/08/app-engine.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0cCQ3c4fSp7ImA9Wx5RE0U.&quot;"><id>tag:blogger.com,1999:blog-4177643508104568252.post-6042858320661625438</id><published>2010-08-21T16:57:00.000+08:00</published><updated>2010-08-21T16:57:42.935+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-08-21T16:57:42.935+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="template" /><title>自訂範本系統中的 filter</title><content type="html">在 App Engine 上製作網頁時，若是沒有使用 django 或是其它的 template 函式庫，應該都會直接使用 App Engine 所提供的 &lt;a href="http://code.google.com/intl/en/appengine/docs/python/gettingstarted/templates.html" target="_blank"&gt;django template wrapper&lt;/a&gt;（用的是 django 0.96 版的 template），不過這就沒辦法（很簡單地）照著 django 所提供的方式自訂標籤及 filter。不過 App Engine 還是有提供自訂 filter 的方式，只要按照下列的步驟（假設你應用程式的目錄是在 &lt;code&gt;$APP&lt;/code&gt;）：&lt;br /&gt;
&lt;ol&gt;&lt;li&gt;建立一個 Python 模組來定義 filters:&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;# $APP/my_filters.py
from google.appengine.ext import webapp

# 取得 template filters register
register = webapp.template.create_template_register()

@register.filter
def tolower(string_value):
    return string_value.lower()
&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;這樣一來便建立了一個自訂的 filter: &lt;code&gt;tolower&lt;/code&gt;，等等便可以用在 template 中。&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;雖然建立好了自訂的 filter(s)，但是 App Engine 的 template 函式庫還不知道有這個東西的存在，所以在使用範本引擎輸出前，記得加入下列的程式碼註冊你自訂的 module(s):&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;...
from google.appengine.ext.webapp import template

# 註冊自訂 filters 的模組（載入模組的名稱）
template.register_template_library('my_filters')
&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;/li&gt;
&lt;li&gt;完成以上步驟後，就可以在範本中像這樣來使用自訂的 filters：&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class="html" name="code"&gt;...
Description: {{ description|tolower }}
&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;br /&gt;
如此一來，便不必在 request handler 中預先處理輸出的內容，可以把這部份的程式碼用 filter 來解決。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4177643508104568252-6042858320661625438?l=practicalappengine.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/-eXj4HUCwwsrbD87OUCrXDvABeM/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/-eXj4HUCwwsrbD87OUCrXDvABeM/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/-eXj4HUCwwsrbD87OUCrXDvABeM/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/-eXj4HUCwwsrbD87OUCrXDvABeM/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/practicalappengine/~4/yrhu53UYy3w" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://practicalappengine.blogspot.com/feeds/6042858320661625438/comments/default" title="張貼意見" /><link rel="replies" type="text/html" href="http://practicalappengine.blogspot.com/2010/08/filter.html#comment-form" title="0 個意見" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/6042858320661625438?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/6042858320661625438?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/practicalappengine/~3/yrhu53UYy3w/filter.html" title="自訂範本系統中的 filter" /><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="26" height="32" src="http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://practicalappengine.blogspot.com/2010/08/filter.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEcBQnY8cSp7ImA9WxFaEUw.&quot;"><id>tag:blogger.com,1999:blog-4177643508104568252.post-9108324747480338674</id><published>2010-07-14T22:00:00.000+08:00</published><updated>2010-07-14T22:00:53.879+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-07-14T22:00:53.879+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="openid" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="account" /><title>使用 OpenID 作為帳號驗證</title><content type="html">AppEngine 在 1.3.4 版本之後，開始實驗支援 OpenID 的身份驗證，除了可以在建立 app 時選擇使用 OpenID 作帳號驗證之外，也可以在後台設定。&lt;br /&gt;
&lt;br /&gt;
&lt;img alt="" src="http://code.google.com/appengine/docs/images/authoptions.png" /&gt;&lt;br /&gt;
&lt;small&gt;建立 app 時可以選擇使用 Open ID&lt;/small&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;img border="0" src="http://3.bp.blogspot.com/_hNy_9UI1_R8/TD2-ZueVAGI/AAAAAAAARD0/M32SgwmMWRA/s320/auth.png" /&gt;&lt;br /&gt;
&lt;small&gt;在管理後台的 Administration &amp;gt; Application Settings 中設定&lt;/small&gt;&lt;br /&gt;
&lt;br /&gt;
設定好了之後，其實程式也不用修改太多，還是可以直接使用 &lt;code&gt;google.appengine.api.users&lt;/code&gt; 模組中的函式來做身份認證，App Engine 已經實作了 Open ID 的規格，所以可以根據 Open ID 找出認證網址，但首先要在 app.yaml 檔案中加入 &lt;code&gt;/_ah/login_required&lt;/code&gt; 的 URL 像是這樣：&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class="yaml" name="code"&gt;(app.yaml)
...
- url: /_ah/login_required
  script: openid_login.py
...&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;
然後在你的登入頁面中，將登入的動作導向 &lt;code&gt;/_ah/login_required&lt;/code&gt; 這個 URL，而處理的程式就像這樣：&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;(openid_login.py)...
from google.appengine.ext import webapp
from google.appengine.api import users

class OpenIdHandler(webapp.RequestHandler):
    def get(self):
        ....
        # 使用者輸入的 Open ID URL
        openid_url = self.request.get('openid')
        # Open ID 認證結束後導向的 URL
        continue_url = '....'
        # 將使用者導向 Open ID provider 的認證網址：
        self.redirect(users.create_login_url(continue_url, None, openid_url))
...&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;如果 &lt;code&gt;openid_url&lt;/code&gt; 是空值，則 App Engine 會利用 Google Account API 來完成認證。&lt;br /&gt;
&lt;br /&gt;
當使用者用 OpenID 認證成功之後，就可以使用利用下面的方式來取得使用者的 OpenID 資訊：&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;...
from google.appengine.api import users
...
user = users.get_current_user()
if user:
    # 取得 openid identity
    id = user.federated_identity()
    # 取得 openid provider URL
    provider = user.federated_provider()
...&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4177643508104568252-9108324747480338674?l=practicalappengine.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/BaEcwwIzOljQKUmvKqhknwId8fw/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/BaEcwwIzOljQKUmvKqhknwId8fw/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/BaEcwwIzOljQKUmvKqhknwId8fw/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/BaEcwwIzOljQKUmvKqhknwId8fw/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/practicalappengine/~4/4JP9MkoG-XE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://practicalappengine.blogspot.com/feeds/9108324747480338674/comments/default" title="張貼意見" /><link rel="replies" type="text/html" href="http://practicalappengine.blogspot.com/2010/07/openid.html#comment-form" title="0 個意見" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/9108324747480338674?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/9108324747480338674?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/practicalappengine/~3/4JP9MkoG-XE/openid.html" title="使用 OpenID 作為帳號驗證" /><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="26" height="32" src="http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_hNy_9UI1_R8/TD2-ZueVAGI/AAAAAAAARD0/M32SgwmMWRA/s72-c/auth.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://practicalappengine.blogspot.com/2010/07/openid.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CU8DRH8-eCp7ImA9WxBRGEo.&quot;"><id>tag:blogger.com,1999:blog-4177643508104568252.post-7805012231763050718</id><published>2010-01-07T22:04:00.002+08:00</published><updated>2010-01-07T22:17:55.150+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2010-01-07T22:17:55.150+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="snow leopard" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><category scheme="http://www.blogger.com/atom/ns#" term="osx" /><category scheme="http://www.blogger.com/atom/ns#" term="mac" /><title>[Mac] 在 Snow Leopard 上開發 Google App Engine</title><content type="html">Mac OSX 在 Snow Leopard (10.6) 之後，已經將預設的 python 設定為 2.6，不過系統還是有安裝 2.5 版本，所以開發基本上沒有什麼問題，只是要稍微作一些調整：&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;如果你的電腦還沒有安裝過 &lt;a href="http://developer.apple.com/tools/xcode/" target="_blank"&gt;XCode&lt;/a&gt;（Mac 系統安裝光碟內及iPhone SDK 都有），必須要先安裝，讓系統有安裝編譯的工具&lt;/li&gt;&lt;br /&gt;&lt;li&gt;雖然系統內建了 Python 2.5，不過並沒有安裝 &lt;a href="http://www.pythonware.com/products/pil/" target="_blank"&gt;PIL&lt;/a&gt; 這個 Python 處理影像的函式庫，因為 App Engine 中的 image API 會用到 PIL，所以也要安裝這個函式庫。為了讓函式庫能支援 JPEG 檔案的處理，所以就要先來安裝 libjpeg。&lt;/li&gt;&lt;br /&gt;&lt;li&gt;首先到&lt;a href="http://www.ijg.org/" target="_blank"&gt;這裡&lt;/a&gt;下載 &lt;code&gt;jpegsrc.v7.tar.gz&lt;/code&gt; 檔案，然後在文字模式下依照下列步驟編譯及安裝：&lt;blockquote&gt;&lt;code&gt;&lt;br /&gt;$ tar zxvf jpegsrc.v7.tar.gz&lt;br /&gt;....&lt;br /&gt;$ cd jpeg-7&lt;br /&gt;$ export CC=/usr/bin/gcc-4.0&lt;br /&gt;$ ./configure --enable-shared --enable-static&lt;br /&gt;$ make&lt;br /&gt;$ sudo make install&lt;/code&gt;&lt;/blockquote&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;如果一切都很順利的話，那就可以到 PIL 網站下載 &lt;code&gt;Python Imaging Library 1.1.6 Source Kit&lt;/code&gt; 原始檔案回來編譯：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;br /&gt;$ tar zxvf Imaging-1.1.6.tar.gz&lt;br /&gt;...&lt;br /&gt;$ cd Imaging-1.1.6&lt;br /&gt;# 將 setup.py 檔案中找到 JPEG_ROOT 然後改成 JPEG_ROOT = "/usr/local/lib"&lt;br /&gt;$ /usr/bin/python2.5 setup.py build&lt;br /&gt;$ sudo /usr/bin/python2.5 setup.py install&lt;/code&gt;&lt;/blockquote&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;如果一切都沒有問題的話，那應該就沒什麼問題了。只是記住當你在啟動 &lt;code&gt;dev_appserver.py&lt;/code&gt; 時，要使用 &lt;code&gt;/usr/bin/python2.5&lt;/code&gt; 來啟動，而不要使用 &lt;code&gt;/usr/bin/python&lt;/code&gt; 以免用到 Python 2.6 版。&lt;br /&gt;&lt;br /&gt;若是使用了 &lt;b&gt;GoogleAppEngineLauncher.app&lt;/b&gt; 這個應用程式的話，可以在 Preferences... 中設定 Python 的路徑為 &lt;code&gt;/usr/bin/python2.5&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4177643508104568252-7805012231763050718?l=practicalappengine.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/j3u3ZdHioTVf_xR43XRKTr5iBn8/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/j3u3ZdHioTVf_xR43XRKTr5iBn8/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/j3u3ZdHioTVf_xR43XRKTr5iBn8/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/j3u3ZdHioTVf_xR43XRKTr5iBn8/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/practicalappengine/~4/odik63-Tsfo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://practicalappengine.blogspot.com/feeds/7805012231763050718/comments/default" title="張貼意見" /><link rel="replies" type="text/html" href="http://practicalappengine.blogspot.com/2010/01/mac-snow-leopard-google-app-engine.html#comment-form" title="8 個意見" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/7805012231763050718?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/7805012231763050718?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/practicalappengine/~3/odik63-Tsfo/mac-snow-leopard-google-app-engine.html" title="[Mac] 在 Snow Leopard 上開發 Google App Engine" /><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="26" height="32" src="http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg" /></author><thr:total>8</thr:total><feedburner:origLink>http://practicalappengine.blogspot.com/2010/01/mac-snow-leopard-google-app-engine.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkUNRH4zeSp7ImA9WxJaGEU.&quot;"><id>tag:blogger.com,1999:blog-4177643508104568252.post-5745980196476984578</id><published>2009-08-10T13:24:00.002+08:00</published><updated>2009-08-10T14:04:55.081+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-08-10T14:04:55.081+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="datastore" /><category scheme="http://www.blogger.com/atom/ns#" term="quota" /><title>減輕資料查詢、更新的負擔</title><content type="html">在 Google App Engine 上開發應用程式，很多人會面臨到使用額度（Quota）的問題，所以開發者在將應用程式上線後，必須不斷觀察應用程式的存取狀況，以便隨時調整應用程式，避免某一項使用額度過份衝高。&lt;br /&gt;&lt;br /&gt;這篇文章要介紹的是－－儘可能地減少資料查詢、更新的動作，因為 Datastore API 的呼叫次數有限制，而且一個 request 的處理時間也有 30 秒的限制，如果在操作資料時沒有注意到一些細節，可能就會碰到問題。&lt;br /&gt;&lt;h4&gt;平行讀取、更新或刪除 data entities&lt;/h4&gt;&lt;br /&gt;在「&lt;a href="http://practicalappengine.blogspot.com/2009/07/blog-post.html" target="_blank"&gt;避免資料寫入衝突&lt;/a&gt;」這篇文章中，提到可以使用 App Engine 所提供的 &lt;code&gt;memcache&lt;/code&gt; API 來作資料快取，這樣也是一種減少呼叫 Datastore API 的策略。&lt;br /&gt;&lt;br /&gt;除此之外，善加利用每個 data entity 的 key 也可以達到減少呼叫 Datastore API 的目標。例如以下這段程式碼：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;...&lt;br /&gt;# 修改許多 data entity&lt;br /&gt;for product in products:&lt;br /&gt;    product.price = product.price * 1.1&lt;br /&gt;    product.put()&lt;br /&gt;...&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;在這個例子中，&lt;code&gt;products&lt;/code&gt; 中有多少個 entities，就會呼叫多少次 &lt;code&gt;put()&lt;/code&gt;，但若是將上述的程式碼修改為：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;...&lt;br /&gt;from google.appengine.ext import db&lt;br /&gt;# 修改許多 data entity&lt;br /&gt;for product in products:&lt;br /&gt;    product.price = product.price * 1.1&lt;br /&gt;db.put(products)&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;由於 &lt;code&gt;db.put()&lt;/code&gt; 函式支援 list 資料型態的參數，所以這樣的作法僅會算作一次 Datastore API 的呼叫，比起上述的方式大大節省了 Datastore API 的呼叫次數。&lt;br /&gt;&lt;br /&gt;讀取資料時也是以此類推，若原本的程式為：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;...&lt;br /&gt;products = []&lt;br /&gt;# keys 為一個 key list&lt;br /&gt;for key in keys:&lt;br /&gt;    products.append(db.get(key))&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;也可以改寫成：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;...&lt;br /&gt;from google.appengine.ext import db&lt;br /&gt;# keys 為一個 key list&lt;br /&gt;products = db.get(keys)&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;同理可證，若要同時刪除許多 entities，也可以使用 &lt;code&gt;db.delete&lt;/code&gt;。&lt;br /&gt;&lt;h4&gt;有效率地使用 GQL&lt;/h4&gt;&lt;br /&gt;另外，若是要使用 &lt;code&gt;GqlQuery&lt;/code&gt; 作資料查詢時，若是只需要取出 entities 的 key 值，在 GQL 查詢語句中僅需取出 &lt;code&gt;__key__&lt;/code&gt; 欄位即可：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;...&lt;br /&gt;from google.appengine.ext import db&lt;br /&gt;&lt;br /&gt;product_keys = db.GqlQuery('SELECT __key__ FROM Product WHERE title = :title', title='...')&lt;br /&gt;# 或是 product_keys = Product.gql('WHERE title = :title', title='...', keys_only=True)&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;如此一來，讀取的時間就會比讀取全部的欄位還要快許多（如果資料量夠多的話...）&lt;br /&gt;&lt;br /&gt;另外，若是查詢語句會重複使用，可以將該語句建立成一個 &lt;code&gt;GqlQuery&lt;/code&gt; 物件後，再利用 &lt;code&gt;bind()&lt;/code&gt; 方法重新利用該查詢語句。例如本來的程式碼可能是：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;...&lt;br /&gt;from google.appengine.ext import db&lt;br /&gt;&lt;br /&gt;conditions = [['x', 'y'], ['1', '2'], .....]&lt;br /&gt;for cond in conditions:&lt;br /&gt;    query = db.GqlQuery('SELECT * FROM Foo WHERE first = :first, second = :second', first=cond[0], second=cond[1])&lt;br /&gt;    ....&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;如此一來，迴圈每執行一次就會建立一個 &lt;code&gt;GqlQuery&lt;/code&gt; 物件，資料查詢會變得很沒有效率，如果碰到這樣的情況，程式碼應該改寫為：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;...&lt;br /&gt;from google.appengine.ext import db&lt;br /&gt;&lt;br /&gt;conditions = [['x', 'y'], ['1', '2'], .....]&lt;br /&gt;prepared_query = db.GqlQuery('SELECT * FROM Foo WHERE first = :first, second = :second')&lt;br /&gt;for cond in conditions:&lt;br /&gt;    query = prepared_query.bind(first=cond[0], second=cond[1])&lt;br /&gt;    ....&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;如此便能重複利用 &lt;code&gt;GqlQuery&lt;/code&gt; 這個物件了。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4177643508104568252-5745980196476984578?l=practicalappengine.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/Z6w0YTsDrKfXr40k5xJMWRF9YzQ/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Z6w0YTsDrKfXr40k5xJMWRF9YzQ/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/Z6w0YTsDrKfXr40k5xJMWRF9YzQ/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/Z6w0YTsDrKfXr40k5xJMWRF9YzQ/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/practicalappengine/~4/HZLou4T84F4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://practicalappengine.blogspot.com/feeds/5745980196476984578/comments/default" title="張貼意見" /><link rel="replies" type="text/html" href="http://practicalappengine.blogspot.com/2009/08/blog-post.html#comment-form" title="2 個意見" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/5745980196476984578?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/5745980196476984578?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/practicalappengine/~3/HZLou4T84F4/blog-post.html" title="減輕資料查詢、更新的負擔" /><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="26" height="32" src="http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://practicalappengine.blogspot.com/2009/08/blog-post.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUQARHw9cCp7ImA9WxJaFEg.&quot;"><id>tag:blogger.com,1999:blog-4177643508104568252.post-8245550254649025006</id><published>2009-08-05T14:33:00.017+08:00</published><updated>2009-08-05T15:29:05.268+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-08-05T15:29:05.268+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="ide" /><category scheme="http://www.blogger.com/atom/ns#" term="eclipse" /><category scheme="http://www.blogger.com/atom/ns#" term="pydev" /><category scheme="http://www.blogger.com/atom/ns#" term="tutorial" /><title>使用 Eclipse + PyDev 開發 Google App Engine 專案</title><content type="html">在 Windows 上開發應用程式，大多數的開發者會藉著使用 &lt;abbr title="Integrated Development Environment"&gt;IDE&lt;/abbr&gt; 來輔助開發，簡化一些設定、啟動或測試等步驟。而在 Windows 上若要開發 Google App Engine 的應用程式，使用 &lt;a href="http://eclipse.org/" target="_blank"&gt;Eclipse&lt;/a&gt; 作為 IDE，並且搭配 &lt;a href="http://pydev.sourceforge.net/" target="_blank"&gt;PyDev&lt;/a&gt; 這個 Eclipse 的 plugin，會簡化許多開發的設定，尤其是最新的 PyDev 甚至還直接支援了 Google App Engine 專案呢！以下就為各位做個簡單的介紹。&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;安裝及設定&lt;/h4&gt;&lt;br /&gt;在開始之前，先確定您的 Windows 環境已經安裝了 &lt;a href="http://java.sun.com/javase/downloads/index.jsp" target="_blank"&gt;Java SDK (JDK)&lt;/a&gt; 以及 &lt;a href="http://python.org/download/releases/2.5.4/"&gt;Python&lt;/a&gt; （目前 Google App Engine 僅支援 Python 2.5.x） 。Java 是為了執行 Eclipse，而 Python 當然就是為了用來執行 Google App Engine 的專案囉。&lt;br /&gt;&lt;br /&gt;首先，到 &lt;a href="http://eclipse.org/downloads/"&gt;Eclipse 的官方網站&lt;/a&gt;下載 Eclipse Classic 3.5.0&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_hNy_9UI1_R8/Snkpz8rLG6I/AAAAAAAACok/dbPYRbYuSWQ/s1600-h/download_eclipse.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 227px;" src="http://2.bp.blogspot.com/_hNy_9UI1_R8/Snkpz8rLG6I/AAAAAAAACok/dbPYRbYuSWQ/s320/download_eclipse.png" alt="" id="BLOGGER_PHOTO_ID_5366366403425934242" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;下載回來後，將 zip 檔案解壓縮，執行 &lt;b&gt;eclipse&lt;/b&gt; 目錄中的 &lt;code&gt;eclipse.exe&lt;/code&gt; 便可以開啟 Eclipse：&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_hNy_9UI1_R8/SnksaQQ6XhI/AAAAAAAACos/UyZS26T3KIs/s1600-h/eclipse_ide.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 240px;" src="http://3.bp.blogspot.com/_hNy_9UI1_R8/SnksaQQ6XhI/AAAAAAAACos/UyZS26T3KIs/s320/eclipse_ide.png" alt="" id="BLOGGER_PHOTO_ID_5366369260542778898" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;開啟 Eclipse 之後，首先將環境的設定作點修改，從功能表列的 &lt;b&gt;Window&lt;/b&gt; » &lt;b&gt;Preferences&lt;/b&gt; 進入設定畫面，首先設定用空白取代 tab 字元，因為 Python 對於程式碼的縮排有嚴格的一致性，所以為了避免不必要的煩惱，在 &lt;b&gt;General&lt;/b&gt; » &lt;b&gt;Editor&lt;/b&gt; » &lt;b&gt;Text Editors&lt;/b&gt; 的設定中，&lt;em&gt;將 tab 取代為 4 個空白字元&lt;/em&gt;。&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_hNy_9UI1_R8/Snktff_veXI/AAAAAAAACo0/STgI29iOZJc/s1600-h/s2t.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 155px;" src="http://4.bp.blogspot.com/_hNy_9UI1_R8/Snktff_veXI/AAAAAAAACo0/STgI29iOZJc/s320/s2t.png" alt="" id="BLOGGER_PHOTO_ID_5366370450176702834" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;除此之外，也將由 Eclipse 所建立的專案，調整成&lt;em&gt;預設使用 UTF-8 作為字元編碼&lt;/em&gt;，以及&lt;em&gt;使用 UNIX 換行字元&lt;/em&gt;：&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_hNy_9UI1_R8/SnkuHtoA21I/AAAAAAAACo8/kkmgEbGCLiw/s1600-h/charset_and_line.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 74px;" src="http://2.bp.blogspot.com/_hNy_9UI1_R8/SnkuHtoA21I/AAAAAAAACo8/kkmgEbGCLiw/s320/charset_and_line.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5366371141030042450" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;為了讓 Eclipse 能夠作為開發 Python 專案的 IDE，此時還需要安裝 PyDev 這個 plugin，可以從&lt;a href="http://pydev.sourceforge.net/download.html" target="_blank"&gt;PyDev 的官方網站&lt;/a&gt;上看到安裝 URL（如：http://nightly.aptana.com/pydev/site.xml），將這個 URL 複製下來，回到 Eclipse，到功能表列的 &lt;b&gt;Help&lt;/b&gt; &amp;raquo; &lt;b&gt;Install New Software...&lt;/b&gt;，將剛才複製的 URL 貼在 &lt;em&gt;Work with:&lt;/em&gt; 的文字框中，並按下 &lt;b&gt;Add&lt;/b&gt; 按鈕：&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_hNy_9UI1_R8/SnkvgToWffI/AAAAAAAACpM/8aNppn16Gsc/s1600-h/Add+PyDev+Repo.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 297px;" src="http://3.bp.blogspot.com/_hNy_9UI1_R8/SnkvgToWffI/AAAAAAAACpM/8aNppn16Gsc/s320/Add+PyDev+Repo.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5366372663060495858" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;接著就是勾選 PyDev 然後將它安裝完成，安裝完畢後，Eclipse 便會請你重新啟動或是套用變更將 plugin 完成整合到 Eclipse 中。&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_hNy_9UI1_R8/Snkv5slfw6I/AAAAAAAACpU/vibYwC4y84Y/s1600-h/Install+PyDev.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 297px;" src="http://1.bp.blogspot.com/_hNy_9UI1_R8/Snkv5slfw6I/AAAAAAAACpU/vibYwC4y84Y/s320/Install+PyDev.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5366373099256136610" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;安裝完 PyDev 之後，別忘了先設定 PyDev，讓它瞭解 Python 被安裝在哪裡：&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_hNy_9UI1_R8/SnkyMKN5e_I/AAAAAAAACpk/-1sj8cv6-YQ/s1600-h/setup+pydev.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 275px;" src="http://1.bp.blogspot.com/_hNy_9UI1_R8/SnkyMKN5e_I/AAAAAAAACpk/-1sj8cv6-YQ/s320/setup+pydev.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5366375615471123442" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;建立 Google App Engine 專案&lt;/h4&gt;&lt;br /&gt;設定完 Python 之後，建立新專案時，就有 &lt;b&gt;PyDev Google App Engine Project&lt;/b&gt; 可以選擇了：&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_hNy_9UI1_R8/Snkxh25_SjI/AAAAAAAACpc/7hLyTR9nC4I/s1600-h/pydev_gae.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 318px;" src="http://2.bp.blogspot.com/_hNy_9UI1_R8/Snkxh25_SjI/AAAAAAAACpc/7hLyTR9nC4I/s320/pydev_gae.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5366374888732838450" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;輸入專案名稱，以及別忘了選擇正確的 Python 版本：&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_hNy_9UI1_R8/SnkynQkaE_I/AAAAAAAACps/Hrg-ZRiBGww/s1600-h/create+gae+proj.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 302px; height: 320px;" src="http://3.bp.blogspot.com/_hNy_9UI1_R8/SnkynQkaE_I/AAAAAAAACps/Hrg-ZRiBGww/s320/create+gae+proj.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5366376081032614898" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;接著選擇 Google App Engine 的安裝位置：&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_hNy_9UI1_R8/Snky-_DPqHI/AAAAAAAACp0/tll4YqnG6tU/s1600-h/setup+gae.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 302px; height: 320px;" src="http://2.bp.blogspot.com/_hNy_9UI1_R8/Snky-_DPqHI/AAAAAAAACp0/tll4YqnG6tU/s320/setup+gae.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5366376488646977650" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;最後就是填入你的 application ID 及專案的範本：&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_hNy_9UI1_R8/SnkzcTOYrvI/AAAAAAAACp8/RW-thzrti-Y/s1600-h/app+id+and+template.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 302px; height: 320px;" src="http://1.bp.blogspot.com/_hNy_9UI1_R8/SnkzcTOYrvI/AAAAAAAACp8/RW-thzrti-Y/s320/app+id+and+template.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5366376992278621938" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;如此一來專案就建立完成了。&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;執行及部署&lt;/h4&gt;&lt;br /&gt;當你的程式寫完，想要啟動開發用伺服器來作測試時，在專案視窗中的 &lt;b&gt;src&lt;/b&gt; 目錄上按下右鍵，選擇 &lt;b&gt;Run as...&lt;/b&gt; 就有 &lt;b&gt;Run: Google App&lt;/b&gt; 可以選擇了：&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_hNy_9UI1_R8/Snk0GnigXnI/AAAAAAAACqE/X6mIIVJMVrM/s1600-h/run+gae.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 254px;" src="http://2.bp.blogspot.com/_hNy_9UI1_R8/Snk0GnigXnI/AAAAAAAACqE/X6mIIVJMVrM/s320/run+gae.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5366377719286226546" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;如果要將應用程式部署至 Google App Engine 上，則一樣在 &lt;b&gt;src&lt;/b&gt; 目錄上按下右鍵，選擇 &lt;b&gt;PyDev: Google App Engine&lt;/b&gt; 就有 &lt;b&gt;Upload&lt;/b&gt; 及 &lt;b&gt;Manage&lt;/b&gt; 可以使用。&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_hNy_9UI1_R8/Snk0nnaLpGI/AAAAAAAACqM/QDATDk52zug/s1600-h/deploy+gae.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 278px; height: 320px;" src="http://1.bp.blogspot.com/_hNy_9UI1_R8/Snk0nnaLpGI/AAAAAAAACqM/QDATDk52zug/s320/deploy+gae.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5366378286186996834" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;這樣是不是簡單多了呢？祝各位開發愉快 :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4177643508104568252-8245550254649025006?l=practicalappengine.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/LoQ4lADhoxiy1cMm7FYTAdlYwXw/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/LoQ4lADhoxiy1cMm7FYTAdlYwXw/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/LoQ4lADhoxiy1cMm7FYTAdlYwXw/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/LoQ4lADhoxiy1cMm7FYTAdlYwXw/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/practicalappengine/~4/V2A5zpoHsUM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://practicalappengine.blogspot.com/feeds/8245550254649025006/comments/default" title="張貼意見" /><link rel="replies" type="text/html" href="http://practicalappengine.blogspot.com/2009/08/eclipse-pydev-google-app-engine.html#comment-form" title="6 個意見" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/8245550254649025006?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/8245550254649025006?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/practicalappengine/~3/V2A5zpoHsUM/eclipse-pydev-google-app-engine.html" title="使用 Eclipse + PyDev 開發 Google App Engine 專案" /><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="26" height="32" src="http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_hNy_9UI1_R8/Snkpz8rLG6I/AAAAAAAACok/dbPYRbYuSWQ/s72-c/download_eclipse.png" height="72" width="72" /><thr:total>6</thr:total><feedburner:origLink>http://practicalappengine.blogspot.com/2009/08/eclipse-pydev-google-app-engine.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkUARXo_fyp7ImA9WxJbEko.&quot;"><id>tag:blogger.com,1999:blog-4177643508104568252.post-4292364081606642444</id><published>2009-07-22T23:57:00.000+08:00</published><updated>2009-07-22T23:57:24.447+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-07-22T23:57:24.447+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="datastore" /><category scheme="http://www.blogger.com/atom/ns#" term="scale" /><title>避免資料寫入衝突</title><content type="html">在使用 App Engine 時，常常會建立 model 來作 sequence（如：手動配置資料的 id）、counter（計數器）之類的工作，以前面文章提到的 counter 為例，如果 &lt;code&gt;Counter&lt;/code&gt; 在 datastore 中的 data entity 只有一個，每次新增資料時，程式都會到同一個地方作資料寫入（update 也是一種 write）的動作，如果新增資料的頻率很高，那就很容易發生寫入衝突（write contention）而使得程式的效能低落。&lt;br /&gt;&lt;br /&gt;試想你在作一個類似 &lt;a href="http://twitter.com/" target="_blank"&gt;Twitter&lt;/a&gt; 的網站，使用者增加訊息的速度是非常驚人的，如果只使用單一個 data entity 來儲存、更新計數器，那每則訊息的更新時間就會變長，因為都在等待更新計數器的動作。如果你曾經學習過用在磁碟機上的 &lt;a href="http://en.wikipedia.org/wiki/RAID" target="_blank"&gt;&lt;acronym title="Redundant Array of Inexpensive Disks"&gt;RAID&lt;/acronym&gt;&lt;/a&gt; 技術，那便可以借鏡 RAID 5 規格中分散資料擺放的方式，使得各個讀寫資料的動作可以同時進行。&lt;br /&gt;&lt;br /&gt;以計數器為例，可以試著把計數器拆開成好幾個碎片（shard），在更新計數器時，可以只要隨便選一個碎片來更新就可以了，如果要取得完整的數值，只要把各碎片的值加總就得到答案了。&lt;br /&gt;&lt;br /&gt;這樣的概念，程式要怎麼寫呢？首先把 &lt;code&gt;Counter&lt;/code&gt; 的定義修改成這樣：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;from google.appengine.ext import db&lt;br /&gt;&lt;br /&gt;class Counter(db.Model):&lt;br /&gt;    name = db.StringProperty(required=True)&lt;br /&gt;    count = db.IntegerProperty(required=True, default=0)&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;增加了一個 &lt;code&gt;name&lt;/code&gt; 欄位，這是為了標記碎片是屬於哪一個計數器，也就是 &lt;code&gt;name&lt;/code&gt; 欄位相同的碎片就是屬於同一個計數器。有了這樣的 model 之後，更新計數器的程式碼可以改寫成這樣：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;...&lt;br /&gt;import random&lt;br /&gt;&lt;br /&gt;shard_name = 'product_counter_%d' % random.randint(0, 3) # 假設我分成4個碎片&lt;br /&gt;counter = Counter.get_by_key_name(shard_name)&lt;br /&gt;if counter is None:&lt;br /&gt;    counter = Counter(key_name=shard_name, name='product_counter')&lt;br /&gt;counter.count += 1&lt;br /&gt;counter.put()&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;上面的例子是我預計將計數器切成 4 個碎片，然後每個「碎片 entity」的 &lt;code&gt;key_name&lt;/code&gt; 分別為 &lt;code&gt;product_counter_0&lt;/code&gt;, ..., &lt;code&gt;product_counter_3&lt;/code&gt;，於是便能夠直接根據 &lt;code&gt;key_name&lt;/code&gt; 將碎片的 entity 取出，若是碎片不存在就立刻產生一個新的 entity，而新的 entity 除了要設定 &lt;code&gt;key_name&lt;/code&gt; 之外，最重要的就是要記得標上 &lt;code&gt;name&lt;/code&gt; 欄位的值，因為當我們需要加總計數器時，就是靠著這個欄位來取出各個碎片。最後則是將 &lt;code&gt;count&lt;/code&gt; 欄位的值加 1 後儲存，完成更新計數器的動作。&lt;br /&gt;&lt;br /&gt;接下來要處理的就是加總的動作囉，因為可以藉著 &lt;code&gt;name&lt;/code&gt; 欄位來查詢，所以程式也變得很簡單：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;...&lt;br /&gt;counters = Counter.all().filter('name =', 'product_counter')&lt;br /&gt;total = 0&lt;br /&gt;for counter in counters:&lt;br /&gt;    total += counter.count&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;所以 &lt;code&gt;total&lt;/code&gt; 的值就是計數器正確的數值了。&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;改善效能&lt;/h4&gt;&lt;br /&gt;上面的例子雖然改善了寫入衝突的問題，但光是這樣作，除了每次讀取計數器數值時都要做一次查詢（耗費 Datastore API 呼叫次數）以外，碎片如果太多也會增加計算時間，為了改善這個問題，可以為計數器作 cache。&lt;br /&gt;&lt;br /&gt;Google App Engine 有提供 Memcache 的服務，我們可以直接拿它來作為計數器的 cache，首先從計數器的加總下手，一旦數值被計算出來後就放進 cache，如此一來，讀取計數器數值時就可以先從 cache 讀取，不在 cache 裡才去作資料查詢：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;# 讀取計數器總數時，使用 cache&lt;br /&gt;from google.appengine.api import memcache&lt;br /&gt;&lt;br /&gt;counter_name = 'product_counter'&lt;br /&gt;total = memcache.get(counter_name) # 這裡用 counter_name 作為 cache key&lt;br /&gt;if total is None:&lt;br /&gt;    total = 0&lt;br /&gt;    counters = Counter.all().filter('name =', counter_name)&lt;br /&gt;    for counter in counters:&lt;br /&gt;        total += counter.count&lt;br /&gt;    memcache.set(counter_name, total)&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;那當計數器被更新的時候怎麼辦？除了更新 entity 之外，若是計數器的 cache 還存在，可以直接把 cache 裡的值加 1，這樣計數器的總數就不必重新計算了：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;# 更新計數器時，別忘了更新 cache&lt;br /&gt;from google.appengine.api import memcache&lt;br /&gt;import random&lt;br /&gt;&lt;br /&gt;counter_name = 'product_counter'&lt;br /&gt;shard_name = 'product_counter_%d' % random.randint(0, 3) # 假設我分成4個碎片&lt;br /&gt;counter = Counter.get_by_key_name(shard_name)&lt;br /&gt;if counter is None:&lt;br /&gt;    counter = Counter(key_name=shard_name, name=counter_name)&lt;br /&gt;counter.count += 1&lt;br /&gt;counter.put()&lt;br /&gt;&lt;br /&gt;# 更新 cache&lt;br /&gt;total = memcache.get(counter_name)&lt;br /&gt;if total:&lt;br /&gt;    memcache.incr(counter_name) # 使用 incr 函式直接對數值資料加 1 &lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;這樣就不必擔心更新了計數器，卻忘記更新快取了。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4177643508104568252-4292364081606642444?l=practicalappengine.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/TXB6OXAuqTJtsk1zURDNdOEeF50/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/TXB6OXAuqTJtsk1zURDNdOEeF50/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/TXB6OXAuqTJtsk1zURDNdOEeF50/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/TXB6OXAuqTJtsk1zURDNdOEeF50/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/practicalappengine/~4/dbKzkwvvbqA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://practicalappengine.blogspot.com/feeds/4292364081606642444/comments/default" title="張貼意見" /><link rel="replies" type="text/html" href="http://practicalappengine.blogspot.com/2009/07/blog-post.html#comment-form" title="2 個意見" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/4292364081606642444?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/4292364081606642444?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/practicalappengine/~3/dbKzkwvvbqA/blog-post.html" title="避免資料寫入衝突" /><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="26" height="32" src="http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://practicalappengine.blogspot.com/2009/07/blog-post.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkUFQXo5fCp7ImA9WxJVGEs.&quot;"><id>tag:blogger.com,1999:blog-4177643508104568252.post-6232289791857596004</id><published>2009-07-06T13:30:00.000+08:00</published><updated>2009-07-06T14:03:30.424+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-07-06T14:03:30.424+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="counter" /><category scheme="http://www.blogger.com/atom/ns#" term="datastore" /><category scheme="http://www.blogger.com/atom/ns#" term="entity group" /><category scheme="http://www.blogger.com/atom/ns#" term="transaction" /><title>支援交易運算的計數器</title><content type="html">&lt;h4&gt;支援交易運算&lt;/h4&gt;&lt;br /&gt;在許多資料庫運算中，為了保持相關的資料庫運算結果能夠不被分割地將同時送進資料庫（也就是一旦有一個操作發生失敗，則整串操作都會同時取消），所以提供了「交易」（transaction）的運算。&lt;br /&gt;&lt;br /&gt;Google App Engine 上的 datastore 當然也支援「交易」運算，在上述的例子中，共有兩個資料庫運算－－&lt;code&gt;Product&lt;/code&gt; entity 的寫入以及 &lt;code&gt;Counter&lt;/code&gt; entity 的更新，試著想像這兩個操作若各自（或同時）發生錯誤時會有什麼問題，若是 &lt;code&gt;Product&lt;/code&gt; entity 在寫入時發生錯誤，程式就中斷了，那就沒什麼太大的問題，若是 &lt;code&gt;Product&lt;/code&gt; entity 成功寫入 datastore，但 &lt;code&gt;Counter&lt;/code&gt; entity 在更新時發生錯誤，這時就會造成計數器的統計數量不一致，這時候就適合使用交易運算了。&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;交易運算的限制&lt;/h4&gt;&lt;br /&gt;在 Google App Engine 中，交易運算只能操作相同 entity group 的資料，如果我們要將 &lt;code&gt;Product&lt;/code&gt; 及 &lt;code&gt;Counter&lt;/code&gt; 的新增動作在同一個交易運算中完成的話，就必須讓它們成為同一個 entity group。&lt;br /&gt;&lt;br /&gt;要成為同一個 entity group，不管是 &lt;code&gt;Product&lt;/code&gt; 還是 &lt;code&gt;Counter&lt;/code&gt; 都需要一個共同的 parent，因此需要一個額外的 data model 來作為這個 entity group 的 root，這裡我提供一個 &lt;code&gt;Index&lt;/code&gt; 的 model，順便用來作為每一筆 &lt;code&gt;Product&lt;/code&gt; 資料的「序號產生器」 :p&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;class Index(db.Model):&lt;br /&gt;    max_index = db.IntegerProperty(required=True, default=0)&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;有了這個 model 後，新增 &lt;code&gt;Product&lt;/code&gt; 及更新 &lt;code&gt;Counter&lt;/code&gt; 的動作就可以改寫為：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;# 新增 product 的程式片段...&lt;br /&gt;...&lt;br /&gt;# 從 Index 中取出 Product 的 index&lt;br /&gt;ind = Index.get_by_key_name('product')&lt;br /&gt;if ind is None:&lt;br /&gt;    ind = Index()&lt;br /&gt;ind.max_index += 1&lt;br /&gt;ind.put()&lt;br /&gt;&lt;br /&gt;# 新增 product entity 並設定 parent 為 ind&lt;br /&gt;p = Product(parent=ind, key_name="product_%d" % ind.max_index,........) &lt;br /&gt;p.put()&lt;br /&gt;&lt;br /&gt;# 根據 key_name 取得 counter，並且指定 parent&lt;br /&gt;counter = Counter.get_by_key_name('product_counter', parent=ind.key())&lt;br /&gt;if counter is None:&lt;br /&gt;    # 如果 counter 不存在，則建立一個新的，別忘了指定 key_name 及 parent&lt;br /&gt;    counter = Counter(parent=ind, key_name='product_counter')&lt;br /&gt;&lt;br /&gt;counter.count += 1&lt;br /&gt;counter.put()&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;在新增 &lt;code&gt;Product&lt;/code&gt; 及 &lt;code&gt;Counter&lt;/code&gt; 時，都加上指定 &lt;code&gt;parent&lt;/code&gt; 的參數，以此將這些資料建構成一個 entity group，然而，一旦資料在 entity group 中，在取出時也要加上 &lt;code&gt;parent&lt;/code&gt; 參數。&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;作成交易運算的函式&lt;/h4&gt;&lt;br /&gt;因此，我們可以把上面的程式碼包成一個函式，比方說是 &lt;code&gt;create_product&lt;/code&gt;，這樣就可以利用 Datastore API 中的 &lt;code&gt;run_in_transaction&lt;/code&gt; 函式來作成交易運算了。&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;....&lt;br /&gt;def create_product():&lt;br /&gt;    # 放入上述的程式碼&lt;br /&gt;&lt;br /&gt;....&lt;br /&gt;# 新增資料時...&lt;br /&gt;from google.appengine.ext import db&lt;br /&gt;db.run_in_transaction(create_product)&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;如此一來，只有當 &lt;code&gt;Index&lt;/code&gt;, &lt;code&gt;Product&lt;/code&gt; 及 &lt;code&gt;Counter&lt;/code&gt; 的資料操作都成功時，更新的資料才會進入 datastore 中。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4177643508104568252-6232289791857596004?l=practicalappengine.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/g_OS5kke00BDlgQgTnB6ZWavzh0/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/g_OS5kke00BDlgQgTnB6ZWavzh0/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/g_OS5kke00BDlgQgTnB6ZWavzh0/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/g_OS5kke00BDlgQgTnB6ZWavzh0/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/practicalappengine/~4/xwxPkwUh4tU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://practicalappengine.blogspot.com/feeds/6232289791857596004/comments/default" title="張貼意見" /><link rel="replies" type="text/html" href="http://practicalappengine.blogspot.com/2009/06/blog-post_4940.html#comment-form" title="0 個意見" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/6232289791857596004?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/6232289791857596004?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/practicalappengine/~3/xwxPkwUh4tU/blog-post_4940.html" title="支援交易運算的計數器" /><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="26" height="32" src="http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://practicalappengine.blogspot.com/2009/06/blog-post_4940.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0EMRXc-eCp7ImA9WxJWE04.&quot;"><id>tag:blogger.com,1999:blog-4177643508104568252.post-8752955892674440834</id><published>2009-06-18T23:41:00.000+08:00</published><updated>2009-06-18T23:41:24.950+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-18T23:41:24.950+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="counter" /><category scheme="http://www.blogger.com/atom/ns#" term="datastore" /><title>資料計數器</title><content type="html">&lt;h3&gt;Datastore API 的限制&lt;/h3&gt;&lt;br /&gt;在使用 Datastore API 時有個限制，就是每作一次 query ，最多能存取到的資料數量為 1000 筆（註1），也因為這個限制，所以也不能直接使用 &lt;code&gt;Query&lt;/code&gt; 物件下的 &lt;code&gt;count()&lt;/code&gt; 方法來計算資料數量。&lt;br /&gt;&lt;br /&gt;舉例來說，如果要設計一個網路商店，每一件商品是一個 &lt;code&gt;Product&lt;/code&gt; entity，假如網站中的商品數量超過 1000 筆，那這樣的程式碼：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;from google.appengine.ext import db&lt;br /&gt;&lt;br /&gt;class Product(db.Model):&lt;br /&gt;    ...&lt;br /&gt;    # product 各欄位&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;query = Product.all()&lt;br /&gt;number_of_products = query.count()&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;並不會幫你統計出究竟有多少 &lt;code&gt;Product&lt;/code&gt; entity，因為 &lt;code&gt;query&lt;/code&gt; 的結果最多就是 1000 筆資料。&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;手動記錄&lt;/h3&gt;&lt;br /&gt;為了解決這樣的問題，其實可以自己定義一個「計數器」的資料，用來統計究竟有多少資料，於是立刻可以寫出這樣的 data model:&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;from google.appengine.ext import db&lt;br /&gt;&lt;br /&gt;class Counter(db.Model):&lt;br /&gt;    count = db.IntegerProperty(required=True, default=0)&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;有了這樣的資料模型，便能夠用來統計網站中任何資料的數量。我們可以經由不同的 &lt;code&gt;key_name&lt;/code&gt; 來區分出不同的計數器，根據上述的例子，便可以在新增 &lt;code&gt;Product&lt;/code&gt; 時，在計數器上加1以便統計。&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;# 新增 product 的程式片段...&lt;br /&gt;...&lt;br /&gt;p = Product(........) # 新增 product entity&lt;br /&gt;p.put()&lt;br /&gt;&lt;br /&gt;# 根據 key_name 取得 counter&lt;br /&gt;counter = Counter.get_by_key_name('product_counter')&lt;br /&gt;if counter is None:&lt;br /&gt;    # 如果 counter 不存在，則建立一個新的，別忘了指定 key_name&lt;br /&gt;    counter = Counter(key_name='product_counter')&lt;br /&gt;&lt;br /&gt;counter.count += 1&lt;br /&gt;counter.put()&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;以此類推，當要刪除一個或多個 &lt;code&gt;Product&lt;/code&gt; entities 時，也要同步更新對應的計數器資料，以保持資料數量的一致性（consistency）。&lt;br /&gt;&lt;br /&gt;&lt;hr&gt;&lt;br /&gt;&lt;h6&gt;註&lt;/h6&gt;&lt;ol&gt;&lt;li&gt;之後會介紹如何讀出超過1000筆的資料&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4177643508104568252-8752955892674440834?l=practicalappengine.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/rjxSFnYKWg37RLZum-TASoX3Yag/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/rjxSFnYKWg37RLZum-TASoX3Yag/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/rjxSFnYKWg37RLZum-TASoX3Yag/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/rjxSFnYKWg37RLZum-TASoX3Yag/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/practicalappengine/~4/1n91j-o78B0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://practicalappengine.blogspot.com/feeds/8752955892674440834/comments/default" title="張貼意見" /><link rel="replies" type="text/html" href="http://practicalappengine.blogspot.com/2009/06/blog-post_18.html#comment-form" title="2 個意見" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/8752955892674440834?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/8752955892674440834?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/practicalappengine/~3/1n91j-o78B0/blog-post_18.html" title="資料計數器" /><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="26" height="32" src="http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg" /></author><thr:total>2</thr:total><feedburner:origLink>http://practicalappengine.blogspot.com/2009/06/blog-post_18.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEEBQnw-eSp7ImA9WxJWE0w.&quot;"><id>tag:blogger.com,1999:blog-4177643508104568252.post-8510020425655330089</id><published>2009-06-18T17:10:00.003+08:00</published><updated>2009-06-18T17:17:33.251+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-18T17:17:33.251+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="datastore" /><category scheme="http://www.blogger.com/atom/ns#" term="property" /><title>屬性中的 indexed=False</title><content type="html">如果你的程式中，有個 data model 的 property 很多，那資料在存取時會隨著 property 數量的成長而降低效能（尤其是發生在呼叫 &lt;code&gt;put()&lt;/code&gt; 來儲存資料時），這是因為在儲存或更新資料時，datastore 也會針對這些 property 作一些 index 的動作，使得整個操作的時間變長。&lt;br /&gt;&lt;br /&gt;如果確定不會用某個 property 來作資料查詢 filter 的條件，那麼可以在定義 property 時加上 &lt;code&gt;indexed=False&lt;/code&gt; 參數，提示 datastore 這個 property 不要做 index，範例程式如下：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;from google.appengine.ext import db&lt;br /&gt;&lt;br /&gt;class Foo(db.Model):&lt;br /&gt;    ....&lt;br /&gt;    bar = db.StringProperty(indexed=False)&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4177643508104568252-8510020425655330089?l=practicalappengine.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/UZSMldMniNNLrFTSCHQdY5XYDZs/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/UZSMldMniNNLrFTSCHQdY5XYDZs/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/UZSMldMniNNLrFTSCHQdY5XYDZs/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/UZSMldMniNNLrFTSCHQdY5XYDZs/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/practicalappengine/~4/LvhSpdSbHko" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://practicalappengine.blogspot.com/feeds/8510020425655330089/comments/default" title="張貼意見" /><link rel="replies" type="text/html" href="http://practicalappengine.blogspot.com/2009/06/indexedfalse.html#comment-form" title="0 個意見" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/8510020425655330089?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/8510020425655330089?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/practicalappengine/~3/LvhSpdSbHko/indexedfalse.html" title="屬性中的 indexed=False" /><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="26" height="32" src="http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://practicalappengine.blogspot.com/2009/06/indexedfalse.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkIGQX87eSp7ImA9WxJWEEs.&quot;"><id>tag:blogger.com,1999:blog-4177643508104568252.post-8707104635172801838</id><published>2009-06-15T11:40:00.008+08:00</published><updated>2009-06-15T20:22:00.101+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-15T20:22:00.101+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="tags" /><category scheme="http://www.blogger.com/atom/ns#" term="recipe" /><title>為儲存的內容加上標籤（簡單版）</title><content type="html">現在很多網站都會為儲存的內容加上標籤，像 &lt;a href="http://flickr.com/" target="_blank"&gt;Flickr&lt;/a&gt; 的每一張相片都可以設定標籤：&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_hNy_9UI1_R8/SjXDm12OBnI/AAAAAAAACZ4/eBRmflaNYig/s1600-h/flickr_tags.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 144px; height: 70px;" src="http://1.bp.blogspot.com/_hNy_9UI1_R8/SjXDm12OBnI/AAAAAAAACZ4/eBRmflaNYig/s400/flickr_tags.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5347395204629268082" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;或是像 delicious 的書籤也可以設定標籤：&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_hNy_9UI1_R8/SjXEaa8al5I/AAAAAAAACaA/c63Aim9OZ5A/s1600-h/delicious_tags.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 43px;" src="http://3.bp.blogspot.com/_hNy_9UI1_R8/SjXEaa8al5I/AAAAAAAACaA/c63Aim9OZ5A/s400/delicious_tags.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5347396090760697746" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;在 App Engine 上開發應用程式，如果要為儲存的內容加上標籤的支援，那麼在資料模型（Data Model）的定義時，可以加上一個 property：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;from google.appengine.ext import db&lt;br /&gt;class MyContent(db.Model):&lt;br /&gt;    ....&lt;br /&gt;    tags = db.StringListProperty()&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;如此一來，&lt;code&gt;tags&lt;/code&gt;就可以儲存&lt;b&gt;由字串所組成的 list&lt;/b&gt;資料，也就是可以儲存 MyContent 物件的標籤。&lt;br /&gt;&lt;br /&gt;假設使用者透過表單送出標籤的資料，而標籤的資料是一個&lt;em&gt;以逗號（,）隔開的字串&lt;/em&gt;，那麼要儲存標籤的作法就是：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;....&lt;br /&gt;tags_string = self.request.get('tags')&lt;br /&gt;content = MyContent()&lt;br /&gt;....&lt;br /&gt;content.tags = map(lambda x: x.strip(), tags_string.split(','))&lt;br /&gt;content.put()&lt;br /&gt;....&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;雖然 &lt;code&gt;tags_string.split(',')&lt;/code&gt; 已經產生一個由字串所組成的 list 了，但是使用者輸入的字串，可能會在逗號的前後留下空白，所以用了 &lt;code&gt;map&lt;/code&gt; 函數將 list 中的每個元素空白消掉（&lt;code&gt;strip&lt;/code&gt; 函數）&lt;br /&gt;&lt;br /&gt;如果要取出含有某個標籤值（如：&lt;code&gt;food&lt;/code&gt;）的內容，則可以直接使用 GQL 中特殊的語法來取出：&lt;br /&gt;&lt;blockquote&gt;&lt;code&gt;&lt;pre class="python" name="code"&gt;....&lt;br /&gt;query = MyContent.gql('WHERE tags = :1', 'food')&lt;br /&gt;for content in query:&lt;br /&gt;    ....&lt;/pre&gt;&lt;/code&gt;&lt;/blockquote&gt;&lt;br /&gt;因為 GQL 的設計，雖然 &lt;code&gt;tags&lt;/code&gt; 欄位是一個 list，但是只要比對的元素有出現在 list 中，則 &lt;code&gt;=&lt;/code&gt; 運算的結果就會是 &lt;code&gt;True&lt;/code&gt;，所以就能夠輕易地根據 list 中的元素來查詢資料。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4177643508104568252-8707104635172801838?l=practicalappengine.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/sHTnbninhtOaS8B8hzvuh8NZI6c/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/sHTnbninhtOaS8B8hzvuh8NZI6c/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/sHTnbninhtOaS8B8hzvuh8NZI6c/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/sHTnbninhtOaS8B8hzvuh8NZI6c/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/practicalappengine/~4/VDq2FBAteeI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://practicalappengine.blogspot.com/feeds/8707104635172801838/comments/default" title="張貼意見" /><link rel="replies" type="text/html" href="http://practicalappengine.blogspot.com/2009/06/blog-post_15.html#comment-form" title="2 個意見" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/8707104635172801838?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/8707104635172801838?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/practicalappengine/~3/VDq2FBAteeI/blog-post_15.html" title="為儲存的內容加上標籤（簡單版）" /><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="26" height="32" src="http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_hNy_9UI1_R8/SjXDm12OBnI/AAAAAAAACZ4/eBRmflaNYig/s72-c/flickr_tags.png" height="72" width="72" /><thr:total>2</thr:total><feedburner:origLink>http://practicalappengine.blogspot.com/2009/06/blog-post_15.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DE4MQnwyeSp7ImA9WxJWEE4.&quot;"><id>tag:blogger.com,1999:blog-4177643508104568252.post-4566863610077175011</id><published>2009-06-15T11:33:00.002+08:00</published><updated>2009-06-15T11:36:23.291+08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-06-15T11:36:23.291+08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="readme" /><title>本部落格的宗旨</title><content type="html">筆者因為深感 Google App Engine 的中文討論資源不足，所以打算把自己的研究整理成專門的 blog 來發表，順便與同好們互相討論學習。&lt;br /&gt;&lt;br /&gt;未來這個部落格將會不定期更新一些在 Google App Engine 上撰寫程式的技巧及範例，主要會以 Python 版本為主，若有筆誤或是觀念錯誤的部份，還請各位讀者不吝賜教。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4177643508104568252-4566863610077175011?l=practicalappengine.blogspot.com' alt='' /&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="http://feedads.g.doubleclick.net/~a/CNMmCuZ22TJrwE9TdPONEF4Gaag/0/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/CNMmCuZ22TJrwE9TdPONEF4Gaag/0/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;br/&gt;
&lt;a href="http://feedads.g.doubleclick.net/~a/CNMmCuZ22TJrwE9TdPONEF4Gaag/1/da"&gt;&lt;img src="http://feedads.g.doubleclick.net/~a/CNMmCuZ22TJrwE9TdPONEF4Gaag/1/di" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/practicalappengine/~4/uiRcZmy14vE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://practicalappengine.blogspot.com/feeds/4566863610077175011/comments/default" title="張貼意見" /><link rel="replies" type="text/html" href="http://practicalappengine.blogspot.com/2009/06/blog-post.html#comment-form" title="0 個意見" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/4566863610077175011?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/4177643508104568252/posts/default/4566863610077175011?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/practicalappengine/~3/uiRcZmy14vE/blog-post.html" title="本部落格的宗旨" /><author><name>ericsk</name><uri>http://www.blogger.com/profile/18370675023130925184</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="26" height="32" src="http://blog.ericsk.org/wp-content/uploads/2006/09/171838031_7ae90ee7dd_t.jpg" /></author><thr:total>0</thr:total><feedburner:origLink>http://practicalappengine.blogspot.com/2009/06/blog-post.html</feedburner:origLink></entry></feed>

