2010年8月27日 星期五

App Engine 上的時區問題

在 App Engine 上如果要儲存時間,在 model 中就會用 DateTimeProperty, DateProperty 或 TimeProperty 來做為資料型態,比如說:
...
from google.appengine.ext import db

class FooModel(db.Model):
  ...
  ...
  created = db.DateTimeProperty(auto_now_add=True)
DateTimeProperty 來說,它對應到的資料結構是 python 標準函式庫中的 datetime.datetime,不過,當你加上了 auto_now_add=True 這個設定之後,每當建立一個新的 entity 時,Datastore API 就會自動加上「現在時間」,也就是 datetime.datetime.now() 的資料,但是 App Engine 所使用的預設時區是 UTC(也就是 GMT+0),簡單地說,如果你的網站使用者都是台灣的用戶,那就會感覺到這個時間晚了八個小時(因為台灣的時區是 CST,也就是 GMT+8),如果要處理這個問題,這裡提供兩個可能的解決方法:

不要依賴自動產生的時間,DIY

第一個解決方法,就是在產生/儲存 entity 時不要讓 Datastore API 幫你產生現在的時間,每次在產生或是更新 entity 資料時,自己手動產生出正確的時間。在解決問題之前,首先要製作一個台灣時區的 tzinfo,才可以去改變 datetime.datetime 所表示的時區:

...
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)

接著,在產生或更新 entity 資料時:
...
foo = FooModel(......, 
               created=datetime.datetime.now(TaiwanZone()))
foo.put()
這樣就會用台灣時區(GMT+8)的時間來儲存資料了。

在輸出時用 filter 換掉

另外一個方式,就是在 template 要輸出時再換掉時區,當然還是要準備一個 TaiwanTimeZonetzinfo 類別,然而在儲存資料時,還是使用預設的 UTC 時區去處理,但是可以自訂一個 template filter (詳細的作法請見這篇文章)在 template 中顯示時動態換掉時間及時區:
# 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())
這樣只要在 template 中加上這個 filter 來顯示就可以了。

2010年8月21日 星期六

自訂範本系統中的 filter

在 App Engine 上製作網頁時,若是沒有使用 django 或是其它的 template 函式庫,應該都會直接使用 App Engine 所提供的 django template wrapper(用的是 django 0.96 版的 template),不過這就沒辦法(很簡單地)照著 django 所提供的方式自訂標籤及 filter。不過 App Engine 還是有提供自訂 filter 的方式,只要按照下列的步驟(假設你應用程式的目錄是在 $APP):
  1. 建立一個 Python 模組來定義 filters:
    # $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()
    
    這樣一來便建立了一個自訂的 filter: tolower,等等便可以用在 template 中。
  2. 雖然建立好了自訂的 filter(s),但是 App Engine 的 template 函式庫還不知道有這個東西的存在,所以在使用範本引擎輸出前,記得加入下列的程式碼註冊你自訂的 module(s):
    ...
    from google.appengine.ext.webapp import template
    
    # 註冊自訂 filters 的模組(載入模組的名稱)
    template.register_template_library('my_filters')
    
  3. 完成以上步驟後,就可以在範本中像這樣來使用自訂的 filters:
    ...
    Description: {{ description|tolower }}
    

如此一來,便不必在 request handler 中預先處理輸出的內容,可以把這部份的程式碼用 filter 來解決。

2010年7月14日 星期三

使用 OpenID 作為帳號驗證

AppEngine 在 1.3.4 版本之後,開始實驗支援 OpenID 的身份驗證,除了可以在建立 app 時選擇使用 OpenID 作帳號驗證之外,也可以在後台設定。


建立 app 時可以選擇使用 Open ID


在管理後台的 Administration > Application Settings 中設定

設定好了之後,其實程式也不用修改太多,還是可以直接使用 google.appengine.api.users 模組中的函式來做身份認證,App Engine 已經實作了 Open ID 的規格,所以可以根據 Open ID 找出認證網址,但首先要在 app.yaml 檔案中加入 /_ah/login_required 的 URL 像是這樣:
(app.yaml)
...
- url: /_ah/login_required
  script: openid_login.py
...

然後在你的登入頁面中,將登入的動作導向 /_ah/login_required 這個 URL,而處理的程式就像這樣:

(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))
...
如果 openid_url 是空值,則 App Engine 會利用 Google Account API 來完成認證。

當使用者用 OpenID 認證成功之後,就可以使用利用下面的方式來取得使用者的 OpenID 資訊:

...
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()
...

2010年1月7日 星期四

[Mac] 在 Snow Leopard 上開發 Google App Engine

Mac OSX 在 Snow Leopard (10.6) 之後,已經將預設的 python 設定為 2.6,不過系統還是有安裝 2.5 版本,所以開發基本上沒有什麼問題,只是要稍微作一些調整:

  1. 如果你的電腦還沒有安裝過 XCode(Mac 系統安裝光碟內及iPhone SDK 都有),必須要先安裝,讓系統有安裝編譯的工具

  2. 雖然系統內建了 Python 2.5,不過並沒有安裝 PIL 這個 Python 處理影像的函式庫,因為 App Engine 中的 image API 會用到 PIL,所以也要安裝這個函式庫。為了讓函式庫能支援 JPEG 檔案的處理,所以就要先來安裝 libjpeg。

  3. 首先到這裡下載 jpegsrc.v7.tar.gz 檔案,然後在文字模式下依照下列步驟編譯及安裝:

    $ tar zxvf jpegsrc.v7.tar.gz
    ....
    $ cd jpeg-7
    $ export CC=/usr/bin/gcc-4.0
    $ ./configure --enable-shared --enable-static
    $ make
    $ sudo make install

  4. 如果一切都很順利的話,那就可以到 PIL 網站下載 Python Imaging Library 1.1.6 Source Kit 原始檔案回來編譯:

    $ tar zxvf Imaging-1.1.6.tar.gz
    ...
    $ cd Imaging-1.1.6
    # 將 setup.py 檔案中找到 JPEG_ROOT 然後改成 JPEG_ROOT = "/usr/local/lib"
    $ /usr/bin/python2.5 setup.py build
    $ sudo /usr/bin/python2.5 setup.py install

如果一切都沒有問題的話,那應該就沒什麼問題了。只是記住當你在啟動 dev_appserver.py 時,要使用 /usr/bin/python2.5 來啟動,而不要使用 /usr/bin/python 以免用到 Python 2.6 版。

若是使用了 GoogleAppEngineLauncher.app 這個應用程式的話,可以在 Preferences... 中設定 Python 的路徑為 /usr/bin/python2.5