前回に引き続き国際化についてです。
前の記事で書いていた「Environment variable DJANGO_SETTINGS_MODULE is undefined.」のエラーについてですが、あの記事を書いた後も何度か発生しており、今日、GAE上にアップしたアプリを動かしていると、一時ページを表示した状態で放置し、改めて操作するとAjax部分の通信で上記のエラーが出ていました。
それで、色々調べているとimportの順番が問題だったようで、試してみるとエラーが出なくなりました。その修正内容を書いておきたいと思います。
■編集前
1 2 3 4 5 6 7 8 9 | from django.utils import translation from google.appengine.ext import webapp from org.fukata.mapshare.system.utils.cookies import Cookies import os from django.conf import settings os.environ.__setitem__('DJANGO_SETTINGS_MODULE', 'conf.settings') # Force Django to reload settings settings._target = None |
■編集後
1 2 3 4 5 6 7 8 9 10 | import os from google.appengine.ext import webapp from google.appengine.ext.webapp import template from django.utils import translation from org.fukata.mapshare.system.utils.cookies import Cookies from django.conf import settings os.environ.__setitem__('DJANGO_SETTINGS_MODULE', 'conf.settings') # Force Django to reload settings settings._target = None |
全体的に順序も入れ替わってしまっているので分かりにくいと思いますが、
from django.utils import translation
の前に
from google.appengine.ext import webapp from google.appengine.ext.webapp import template
を読み込むということです。
また、エラーが出るようでしたら追記したいと思います。これで、最後であってほしい。。。(直接的な原因を調べろよ。。。)
前の記事でGAE/Pの標準環境にて国際化に対応した際の記事を書いたのですが、実際にGAE上にデプロイして動かしてみると何点かエラーが出たので、追加したいと思います。
■UnicodeDecodeErrorの発生
実際にアップロードして、.poファイルで日本語が定義しているmsgidが存在するページを開いた際に同様のエラーが発生しました。これは、.poファイルの文字コードが設定されていなかった為に起こったようです。下記が、現在の.poファイルになります。
msgid "" msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Last-Translator: Tatsuya Fukata <tatsuya .fukata@gmail.com>\n" "Report-Msgid-Bugs-To: Tatsuya Fukata </tatsuya><tatsuya .fukata@gmail.com>\n" "Language-Team: Japanese\n" "Project-Id-Version: MapShare\n" msgid "Spot" msgstr "スポット" msgid "Spot List" msgstr "スポット一覧" msgid "other" msgstr "その他" </tatsuya>
■DJANGO_SETTINGS_MODULEの未設定
次に、何度かデプロイしている間に上記のようなエラーが発生するようになりました。実際に使われているのは前の記事で作成したクラス「I18NRequestHandler」のファイル内で同様の記述が存在します。直接キーを指定している部分を下記のように修正して、エラーが出なくなりました。
#os.environ['DJANGO_SETTINGS_MODULE'] = 'conf.settings' os.environ.__setitem__('DJANGO_SETTINGS_MODULE', 'conf.settings')
まだ、完全に国際化に対応されていないかもしれませんが、何かエラーが発生したら随時載せていきたいと思います。
現在、GAE/P環境にて作成しているアプリの国際化を対応してみました。Django-1.0やKayといったFWを使って国際化も対応できるようですが、今更変更するのもだるかったので、標準の環境で対応しました。
多分、次に作成するなら、標準の環境ではなく、他のFWを試すかもしれません。
とまぁ、自分のことは置いといて早速、国際化の手順を書いていきたいと思います。まずは、最終的な構成を書きます。
.
`-- src
|-- app.yaml
|-- conf
| |-- locale
| | |-- en
| | | `-- LC_MESSAGES
| | | |-- django.mo
| | | `-- django.po
| | `-- ja
| | `-- LC_MESSAGES
| | |-- django.mo
| | `-- django.po
| `-- settings.py
`-- org
`-- fukata
`-- mapshare
|-- public
| |-- css
| |-- img
| `-- js
`-- system
|-- handler
| |-- i18NRequestHandler.py
| |-- pc
| | `-- index.py
| `-- webapi
|-- model
|-- template
| `-- pc
`-- utils
`-- cookies.py
今回、国際化の為に新規に追加のは以下のファイル群都なります。
次に各ファイルの内容になります。言語ファイル(.po, .mo)に関しては省きたいと思います。
■config/settings.py
1 2 3 4 5 6 7 8 9 10 11 | USE_I18N = True # Valid languages LANGUAGES = ( # 'en', 'zh_TW' should match the directories in conf/locale/* ('en', _('English')), ('ja', _('Japanese')), ) # This is a default language LANGUAGE_CODE = 'ja' |
■org/fukata/mapshare/system/i18NRequestHandler.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | from django.utils import translation from google.appengine.ext import webapp from org.fukata.mapshare.system.utils.cookies import Cookies import os os.environ['DJANGO_SETTINGS_MODULE'] = 'conf.settings' from django.conf import settings # Force Django to reload settings settings._target = None class I18NRequestHandler(webapp.RequestHandler): def initialize(self, request, response): webapp.RequestHandler.initialize(self, request, response) self.request.COOKIES = Cookies(self) self.request.META = os.environ self.reset_language() def reset_language(self): # Decide the language from Cookies/Headers language = translation.get_language_from_request(self.request) translation.activate(language) self.request.LANGUAGE_CODE = translation.get_language() # Set headers in response self.response.headers['Content-Language'] = translation.get_language() # translation.deactivate() |
■org/fukata/mapshare/system/utils/cookies.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | import UserDict from Cookie import BaseCookie class Cookies(UserDict.DictMixin): def __init__(self, handler, **policy): self.response = handler.response self._in = handler.request.cookies self.policy = policy if 'secure' not in policy and handler.request.environ.get('HTTPS', '').lower() in ['on', 'true']: policy['secure'] = True self._out = {} def __getitem__(self, key): if key in self._out: return self._out[key] if key in self._in: return self._in[key] raise KeyError(key) def __setitem__(self, key, item): self._out[key] = item self.set_cookie(key, item, **self.policy) def __contains__(self, key): return key in self._in or key in self._out def keys(self): return self._in.keys() + self._out.keys() def __delitem__(self, key): if key in self._out: del self._out[key] self.unset_cookie(key) if key in self._in: del self._in[key] p = {} if 'path' in self.policy: p['path'] = self.policy['path'] if 'domain' in self.policy: p['domain'] = self.policy['domain'] self.delete_cookie(key, **p) #begin WebOb functions def set_cookie(self, key, value='', max_age=None, path='/', domain=None, secure=None, httponly=False, version=None, comment=None): """ Set (add) a cookie for the response """ cookies = BaseCookie() cookies[key] = value for var_name, var_value in [ ('max-age', max_age), ('path', path), ('domain', domain), ('secure', secure), ('HttpOnly', httponly), ('version', version), ('comment', comment), ]: if var_value is not None and var_value is not False: cookies[key][var_name] = str(var_value) if max_age is not None: cookies[key]['expires'] = max_age header_value = cookies[key].output(header='').lstrip() self.response.headers._headers.append(('Set-Cookie', header_value)) def delete_cookie(self, key, path='/', domain=None): """ Delete a cookie from the client. Note that path and domain must match how the cookie was originally set. This sets the cookie to the empty string, and max_age=0 so that it should expire immediately. """ self.set_cookie(key, '', path=path, domain=domain, max_age=0) def unset_cookie(self, key): """ Unset a cookie with the given name (remove it from the response). If there are multiple cookies (e.g., two cookies with the same name and different paths or domains), all such cookies will be deleted. """ existing = self.response.headers.get_all('Set-Cookie') if not existing: raise KeyError( "No cookies at all have been set") del self.response.headers['Set-Cookie'] found = False for header in existing: cookies = BaseCookie() cookies.load(header) if key in cookies: found = True del cookies[key] header = cookies.output(header='').lstrip() if header: self.response.headers.add('Set-Cookie', header) if not found: raise KeyError( "No cookie has been set with the name %r" % key) |
となります。で、通常のハンドラに対して「webapp.RequestHandler」ではなく、「I18NRequestHandler」を継承するようにします。これで、基本的には大丈夫です。テンプレート内で、国際化のタグを使用するには、テンプレートの先頭などで、{% load i18n %}と記述します。これで、国際化用のタグが使えるようになります。
また、.poファイルに関してですが、sdkにスクリプトが付随しているようですが、自分は下記のように.moファイルを生成しています。まぁ、スクリプトを使った方が便利だと思います^^;
1 2 | msgfmt django.po mv messages.mo django.mo |
今回使用した、コードに関しては、参考サイトの方にほぼすべてあります。というか、掲載されているコードそのままで動きました。
■参考サイト
最近、GAE for Pythonでアプリを作っているのですが、必須カラムの値がうまく登録できなかった問題が解決したので、メモとして残しておきたいと思います。
■A.py
1 2 | class A(db.Model): owner = db.UserProperty(required=True,auto_current_user_add=True) |
■B.py
1 2 | class B(db.Model): a = db.ReferenceProperty(required=True,reference_class=A) |
■main.py
1 2 3 4 5 6 7 | a_key = [エンティティAのKeyを取得] a = A.get(a_key) # コンストラクタで指定しないと登録時にエラーになります。 b = B(a=a) # 以下のようにフィールドを指定して代入してもエラーとなる。 b.a = a b.put() |
ずっと、後者のようにフィールドを指定して代入していたので、「なんで代入してるのにエラーになるんだ?」という風に思っていて、コンストラクタに指定するというのに気づくのがかなり遅くなってしまいました。
とりあえず、なんとか登録することもでき、前に進むことができるようになりました。今作っているアプリはjsごりごりのアプリなので、デバッグがいつもに比べ手間がかかります^^;しかも、Python自体もまだ勉強し始めてそんなに時間が経っていないのでどういう関数があるのかすら分からない状態で、常にググりながらのコーディングとなっています。
pythonのライブラリリファレンスを見ているとディレクトリをzip圧縮するメソッドがなかったので作成してみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # -*- coding: utf-8 -*- import os,zipfile def zip_write_dir(base_dir, src_dir, zip_name): """ディレクトリをzip圧縮する base_dir: src_dirが属するディレクトリを表す src_dir: 圧縮対象のディレクトリを表す zip_name: 出力先のzipファイルのフルパスを表す """ zf = zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) current_dir = os.getcwd(); os.chdir(base_dir) for root,dirs,files in os.walk(src_dir): for _file in files: filename = os.path.join(root,_file) arcname = filename zf.write(filename, arcname) zf.close() os.chdir(current_dir) |
現在、pythonを勉強中でちょっとしたWEBアプリを作っていたのですが、ダウンロード処理を書かないといけなくなったので、モジュールを作成してみました。よかったらどうぞ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # -*- coding: utf-8 -*- import sys import os def download(req,filepath): req.content_type = 'application/octet-stream' req.headers_out['Content-Type'] = 'application/octet-stream' req.headers_out['Content-Disposition'] = "attachment; filename=\"" + os.path.basename(filepath) req.headers_out['Content-Length'] = str(os.path.getsize(filepath)) req.headers_out['Expires'] = '0' req.headers_out['Cache-Control'] = 'must-revalidate, post-check=0,pre-check=0' req.headers_out['Pragma'] = 'private' if sys.platform == "win32": import msvcrt msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) return req.sendfile(filepath) |
Tags: mod_python, Python, ユーティリティ