Google App Engineのテンプレートエンジン(Django 0.9.6)のfirstofタグが多バイト文字列に対応してない件

Google App Engineの個人的な印象

Google App Engine

  1. 作ったアプリのデプロイが楽
  2. 普段使っていて慣れ親しんでいるPythonを採用
  3. 標準で採用されている各フレームワーク(WebOb, Djangoのテンプレートエンジン)のセンスがいい

ということで,今のところ割と好きです.ただ,Google App Engineが現時点(2008年6月8日)ではまだPreview Releaseなので,今回,人柱の役目を果たそうと思います.

ちなみにこの問題のために1時間ぐらいはまりました.

djangoのテンプレートエンジンにおけるfirstof タグとは?

{% firstof var1 var2 var3 %}

こうするとvar1, var2, var3の順に変数を評価していって

  • 最初に偽でないと評価された変数のみを(文字列化して)出力する
  • 最後の変数まですべて偽だったら何も出力しない

というもの.

症状

firstofタグの引数に多バイト文字列な変数を与えると以下のようにUnicodeEncodeErrorがthrowされる

line 96, in render
    return self.template.render(context)
  File "/usr/local/google_appengine/google/appengine/ext/webapp/template.py", line 95, in _template_render_replacement
    return _original_template_render(self, context)
  File "/usr/local/google_appengine/lib/django/django/template/__init__.py", line 168, in render
    return self.nodelist.render(context)
  File "/usr/local/google_appengine/lib/django/django/template/__init__.py", line 705, in render
    bits.append(self.render_node(node, context))
  File "/usr/local/google_appengine/lib/django/django/template/__init__.py", line 718, in render_node
    return(node.render(context))
  File "/usr/local/google_appengine/lib/django/django/template/defaulttags.py", line 57, in render
    return str(value)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)

原因

以下に示す通り,
django 0.96のfirstofタグ
が多バイト文字列の環境に完全には対応していない.57行目にてsys.getdefaultencoding()の文字コードでデコードされると考えられるが,この値はPython2.5においてデフォルトのエンコーディングはasciiである.一方で,Google App Engineではsys.setdefaultencoding()にてデフォルトのエンコーディングを変更する方法も無いため,多バイト文字列が入力されるとUnicodeEncdeErrorがthrowされる.

46	class FirstOfNode(Node):
47	    def __init__(self, vars):
48	        self.vars = vars
49	
50	    def render(self, context):
51	        for var in self.vars:
52	            try:
53	                value = resolve_variable(var, context)
54	            except VariableDoesNotExist:
55	                continue
56	            if value:
57	                return str(value)
58	        return ''

対策

対策方法1

多バイト文字列を扱う場合は firstof タグを使わず通常の{% if var %}タグで処理する.ifタグだと文字列を与えなくてもdecodeしないため,UnicodeEncodeErrorはthrowされない.

対策方法2

Google App Engine付属のテンプレートエンジン(django 0.96相当)ではなく,後述のとおり本問題が解決されている最新開発版のdjangoを,自分のGoogle App Engineの領域にデプロイして,そちらのテンプレートエンジンを使う

補足

なお,2008年6月9日時点での最新開発版である
django trunk(revision 7557)のfirstofタグでは,このバグは修正されている模様である.83行目のsmart_unicodeという関数のネーミングに開発者の苦労が忍ばれる.

72	class FirstOfNode(Node):
73	    def __init__(self, vars):
74	        self.vars = map(Variable, vars)
75	
76	    def render(self, context):
77	        for var in self.vars:
78	            try:
79	                value = var.resolve(context)
80	            except VariableDoesNotExist:
81	                continue
82	            if value:
83	                return smart_unicode(value)
84	        return u''