app engine patchでアプリ書いたときのtipsとか

templates/404.html, 500.htmlを適当に書き換えておく

これらはdjango.zip内のhtmlに依存していて、その先がmyappに依存しているのでmyappを外すと

NoReverseMatch: Reverse for 'settings.myapp.views.list_people' with arguments '()' and keyword arguments '{}' not found.

などとエラーが出る。

openidgaeで認証

#urls.py
urlpatterns = patterns('',
    ('^openid/', include('openidgae.urls')),
) + auth_patterns + patterns('',                             
    ('^admin/(.*)', admin.site.root),
〜
#settings.py
LOGIN_URL = '/openid/login/'
LOGOUT_URL = '/openid/logout/'
PersonでなくUserを使う

以下は不要

      import models 
      persons = models.Person.gql('WHERE openid = :1', openid_url) 
      if persons.count() == 0: 
        p = models.Person() 
        p.openid = openid_url 
        p.ax_dict().update(ax_items) 
        p.sreg_dict().update(sreg_response) 
        p.put() 
      else: 
        p = persons[0] 
        changed = False 
        for key in sreg_response: 
          if not p.sreg_dict().has_key(key) or \ 
              p.sreg_dict()[key] != sreg_response[key]: 
            logging.debug("Setting sreg %s" % key) 
            p.sreg_dict()[key] = sreg_response[key] 
            changed = True 
        for key in ax_items: 
          if not p.ax_dict().has_key(key) or \ 
              p.ax_dict()[key] != ax_items[key]: 
            logging.info("Setting ax %s" % key) 
            p.ax_dict()[key] = ax_items[key] 
            changed = True 
        if changed: 
          p.put() 
      s = openidgae.get_session(request, response) 
      s.person = p.key() 
      request.openidgae_logged_in_person = p 
 
      s.put() 
    s = openidgae.get_session(request, response) 
    if s: 
      s.person = None 
      s.put() 

login/logout

URIdjangoのユーザー名として自動的にアカウントを作成している。
User.usernameは30文字以下、という制約は無視。(現在のところ問題になるシーンに遭遇していない)

def pretty(openid):
    s = openid.replace('http://','').replace('https://','').rstrip('/').split('#')[0]
    return s
def shortname(pretty):
    rfind = pretty.rfind('/')
    if rfind and 0 < rfind:
        return pretty[rfind + 1:]
    return pretty

pretty = pretty(openid)
shortname = shortname(pretty)
passwd = dummy_passwd(openid)

from django.contrib.auth.models import User
django_user = User.get_by_key_name(openid)
if not django_user:
    django_user = User(key_name=openid, username=pretty,
                       is_active=True,
                       is_staff=False,
                       is_superuser=False,
                       )
    django_user.set_password(passwd)
    django_user.put()
from django.contrib.auth import authenticate, login
django_user = authenticate(username=pretty, password=passwd)
if django_user is not None:
    if django_user.is_active:
        login(request, django_user)
    from django.contrib.auth import logout
    logout(request)

カウンタ

Google App Engine上でスケールするWebアプリを書く
http://www.java-users.jp/contents/events/ccc2009spring/materials/A-3-1.pdf
から改変

class Counter(db.Model):
    name  = db.StringProperty(required=True)
    count = db.IntegerProperty(required=True, default=0)
class CounterManager():
    def get_count_from_db(self, name):
        total = 0
        for counter in Counter.gql('WHERE name= :1', name):
            total += counter.count
        return total
    
    def get_count(self, name):
        """
        lazy_incr, incr, decr共通の集計関数。
        """
        from google.appengine.api import memcache
        count = memcache.get(name)
        if count == None:
            count = self.get_count_from_db(name)
            memcache.set(name, count, consts.COUNTER_EXPIRES)
        return count
    
    def lazy_incr(self, name, value=1):
        """
        一定時間memcacheでロックして、dbへのwriteを発生させない増加カウンタ。
        継続的にコールされる環境での使用を推奨。
        精密ではないことに注意。
        ロックが解け、その後memcache上のデータが揮発するまでにコールされないと、増加分は失われる。
        """
        from google.appengine.api import memcache
        def _set_lock(name):
            memcache.set('_c:%s' % name, 1, consts.COUNTER_MAX_DELAY_INTERVAL)
        def _get_lock(name):
            if None != memcache.get('_c:%s' % name):
                return True
            return False
        
        count = memcache.get(name)
        if count == None:
            db_count = self.get_count(name) 
            count = db_count
        else:
            db_count = None
        
        memcache.incr(name, delta=value)
        
        count += value
        if not _get_lock(name):
            _set_lock(name)
            if db_count == None:
                db_count = self.get_count_from_db(name) 
            diff = count - db_count
            if 0 < diff:
                self._write_db(name, diff)
        return count
        
    def _write_db(self, name, value):
        """
        データストアにcountを書き込む。
        エンティティ分散させることでトランザクションロック時間を低減させている。
        """
        def tr():
            import random
            rand = random.randint(0, consts.COUNTER_DIVISION - 1)
            counter_name = 'c:%s:%s' % (name, rand)
            counter = Counter.get_by_key_name(counter_name)
            if counter == None:
                counter = Counter(key_name=counter_name, name=name)
            counter.count += value
            counter.put()
        db.run_in_transaction(tr)
                
    def incr(self, name, value=1):
        """
        普通の増加カウンタ。
        """
        from google.appengine.api import memcache
        self._write_db(name, value)
        if memcache.get(name) != None:
            memcache.incr(name, delta=value)
    
    def decr(self, name, value=1):
        """
        普通の減少カウンタ。
        """
        from google.appengine.api import memcache
        db.run_in_transaction(self._write_db, name, -value)
        if memcache.get(name) != None:
            memcache.decr(name, delta=value)

アイコン

アップロード

1:1のアスペクト比で中央から切り抜く。

hogeForm = HogeForm(request.POST)
icon = hogeForm.cleaned_data['icon']
if icon:
    try:
        from google.appengine.api import images
        img = images.Image(icon.read())
        w = img.width
        h = img.height
        #crop
        crop_size = float(min(w, h))
        w_crop_rate = crop_size / w
        h_crop_rate = crop_size / h
        w_start = (1 - w_crop_rate) / 2
        h_start = (1 - h_crop_rate) / 2
        w_end = w_start + w_crop_rate
        h_end = h_start + h_crop_rate
        img.crop(w_start, h_start, w_end, h_end)
        #resize
        crop_size = consts.META_ICON_SIDE_LENGTH
        img.resize(width=crop_size, height=crop_size)
        
        icon = img.execute_transforms(output_encoding=images.JPEG)
    except:
        logging.info('Icon update fail.')
返す
response = HttpResponse(icon,
                    content_type='image/jpeg',
                    )

LANGUAGESで時間の調整

#templatetags/localdt.py
import datetime
from django.template import Library

register = Library()
    
def localdt(value, lang):
    lang = lang[1]
    if lang == 'Japanese':
        return value + datetime.timedelta(hours=9)
    else:
        return value
    
register.filter("localdt", localdt)
{% load localdt %}{{ post.updated|localdt:LANGUAGES|date:"Y m d - P" }}

正の数値を64進数に変換する

def encode_b64(n):
    table = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'
    result = []
    temp = n
    if 0 == temp:
        result.append('0')
    else:
        while 0 < temp:
            result.append(table[temp % 64])
            temp /= 64
    return ''.join([x for x in reversed(result)])

64進数から正の数値に変換

def decode_b64(str): 
    table = {"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, 
             "6": 6, "7": 7, "8": 8, "9": 9, 
             "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15, "g": 16, 
             "h": 17, "i": 18, "j": 19, "k": 20, "l": 21, "m": 22, "n": 23, 
             "o": 24, "p": 25, "q": 26, "r": 27, "s": 28, "t": 29, "u": 30, 
             "v": 31, "w": 32, "x": 33, "y": 34, "z": 35, 
             "A": 36, "B": 37, "C": 38, "D": 39, "E": 40, "F": 41, "G": 42, 
             "H": 43, "I": 44, "J": 45, "K": 46, "L": 47, "M": 48, "N": 49, 
             "O": 50, "P": 51, "Q": 52, "R": 53, "S": 54, "T": 55, "U": 56, 
             "V": 57, "W": 58, "X": 59, "Y": 60, "Z": 61, 
             "-": 62, "_": 63} 
    result = 0 
    for i in xrange(len(str)): 
        result *= 64 
        result += table[str[i]] 
    return result