Django での Unicode の扱い
| revision-up-to: | 8001 (1.0pre SVN) |
|---|
開発版の Django で新たに追加された機能です
Django はネイティブで Unicode データをサポートしています.データベースの設 定を正しく行っている限り,テンプレートやモデル,データベース間で Unicode 文 字列を問題なく受渡しできます.
このドキュメントでは,非 ASCII の文字コードでエンコードされたデータやテンプ レートを扱うアプリケーションを作成するときに知っておくべきことを説明します.
データベースの作成
まず,データベースが任意の文字列データを保存できるように設定してください. 通常,これは UTF-8 または UTF-16 を使うことを意味します. latin1 (iso8859-1) のような,より文字セットの制約が強いエンコーディングを使ってい ると,一部の文字をデータベースに保存できず,情報が失われるかもしれません.
- MySQL ユーザは MySQL 5.1 マニュアルの原文 10.3.2 節 ,または 和訳 9.3.2 節 を参考に,データベースの文字セットエンコーディングの設 定や変更を行ってください.
- PostgreSQL ユーザは PostgreSQL 8 マニュアルの原文 21.2.2 節 , または 和訳 20.2.2 節 を参考にして,正しいエンコーディングでデータベー スを作成してください.
- SQLite ユーザは何もする必要がありません. SQLite は常に内部のエンコーディ ングに UTF-8 を使うからです.
Django のデータベースバックエンドは,データベースにクエリを発行する際に, Unicode 文字列を自動的に適切なエンコーディングに変換します.また,データベー スから文字列データを取り出す時にも,適切なエンコーディングで Python の Unicode 文字列に変換します.変換の際に,いちいち Django にデータベースの使っ ているエンコーディングを指定する必要はなく,透過的に操作できます.
詳しくは,後述の「データベース API」を参照してください.
一般的な文字列の操作
データベースの照合やテンプレートのレンダリングなど, Django 内で文字列を使 う場面では, Unicode 文字列と UTF-8 でエンコードされた (string 型の) 文字列 の 2 通りを選択できます.
注意 バイト文字列 (string 型) 自体にはエンコーディングに関する情報は付随して いません.そのため, Django は全てのバイト文字列を UTF-8 でエンコードさ れているとみなします.
UTF-8 以外の文字セットを使ってエンコードされた文字列を Django に渡すと, 内部のどこかでまずいことが起きるでしょう.その結果, Django はたいてい UnicodeDecodeError をどこかで送出するはずです.
ASCII は UTF-8 のサブセットに相当するので,コード内で ASCII のデータしか扱 わないのなら,通常の文字列を使って,自由に受渡ししてかまいません.
DEFAULT_CHARSET 設定を 'utf-8' にすれば,バイト文字列の変換に他の文 字セットを使えるかも,なんて考えるのはやめましょう! DEFAULT_CHARSET は, テンプレートのレンダリング結果 (とメール送信) の文字列のエンコーディングに しか使われません. Django は常に内部のバイト文字列のエンコーディングに UTF-8 を使います.その理由は, DEFAULT_CHARSET というものは,アプリケー ションの開発者が自由にいじってよいものではなく,アプリケーションをインストー ルして使うユーザが自由に設定して使うためのものだからです.ユーザが DEFAULT_CHARSET をどんな値に設定しても,あなたのコードは正しく動かねば なりません.そのため, DEFAULT_CHARSET に依存したコードを書いてはならな いのです.
ほとんどの場合, Django は文字列を扱う際に,まず Unicode 文字列に変換してか ら処理を行います.従って,一般的なルールとして,バイト文字列を渡すときには, 戻り値が Unicode 文字列となって戻ってくると想定しておいてください.
翻訳された文字列
Django を使っていると, Unicode 文字列とバイト文字列の他に,文字列ライクな 第三の型,すなわち「翻訳された文字列」を目にするはずです.Django フレームワー クの国際化機能には,文字列を翻訳用としてマークはするけれども,実際の翻訳結 果はその文字列を使うときまで決定しないという,「遅延翻訳 (lazy translation)」 の概念があります.この機能は,文字列を実際に使用するまでに翻訳ロケールがはっ きり決まらない一方で,コードを import する時にはもとの文字列を生成しておき たい場合に便利です.
通常は,遅延翻訳文字列について心配する必要はありません.ただ,このオブジェ クトを評価すると,遅延翻訳を表す django.utils.functional.__proxy__ オブ ジェクトになっているということを覚えておいてください.また, unicode() の引数に遅延翻訳オブジェクトを指定して呼び出すと,現在のロケールでの翻訳結 果を Unicode 文字列で返すということも覚えておいてください.
遅延翻訳オブジェクトの詳細は, 国際化のドキュメント を参照してください.
便利なユーティリティ関数
文字列の操作は何度も何度も必要になるので, Django では Unicode 文字列型やバ イト文字列型オブジェクトの操作を少しだけ簡単にするユーティリティ関数を提供 しています.
変換用の関数
django.utils.encoding モジュールには, Unicode 文字列とバイト文字列との 間で相互に変換を行うための関数が入っています.
smart_unicode(s, encoding='utf-8', string_only=False, errors='strict') は,入力 s を Unicode 文字列に変換します. encoding パラメタ には入力文字列のエンコーディングを指定します.(例えば, Django はフォー ムから入力された UTF-8 でエンコードされていないデータを内部的に処理す る際にこの関数を使っています.) strings_only パラメタが True の場合、数値型、ブール型、 None といった値を s に渡しても文字列 に変換しません (もとの型のままで値を返します) 。 errors はエラー 処理時の動作を指定するためのパラメタで,Python の unicode() 関数 で使われているのと同じ値を受け取ります.
__unicode__ メソッドを実装しているオブジェクトを smart_unicode() に渡すと, __unicode__ メソッドを使って変換を 行います.
force_unicode(s, encoding='utf-8', string_only=False, errors='strict') は,ほとんどの場合 smart_unicode() と同じように動作します.ただし, 第一引数が 遅延翻訳オブジェクト の場合には動作が異なります. smart_unicode() は遅延翻訳オブジェクトをそのままにしておきますが, force_unicode() を使うと,(その場で翻訳を行って) Unicode 文字列を 返します.通常は smart_unicode() を使った方がよいでしょう.とはい え,テンプレートタグやフィルタは,「後で文字列になる何か」ではなく, 「文字列」を扱わねばならない ので, force_unicode() を使った方 が有意義です.
smart_str(s, encoding='utf-8', strings_only=False, errors='strict') は本質的に smart_unicode() の対極にあたるメソッドです.このメソッ ドは,第一引数をバイト文字列に変換します. strings_only パラメタ の意味は、 smart_unicode() および force_unicode() の同名のパ ラメタと同じです。これは Python の組み込み関数 str() と少し違った 仕様ですが, Django 内部のいくつかの場所で必要な機能として実装されて います.
普段は, smart_unicode() しか必要ないでしょう.入力データが Unicode か バイト文字列であるような場合には,早い段階でこのメソッドを使って Unicode 化 を行い,その後の操作ではつねに Unicode を扱うようにしてください.
URI および IRI の処理
Web フレームワークは (URI の一形式である) URL を扱えねばなりません. URL の必要条件の一つとして, URL は全て ASCII 文字セットで構成せねばなりま せん.しかし,国際化環境では, URL を IRI で構築せねばならない,ざっくりと 言えば,Unicode 文字を含んだ URI を構築せねばならない場合もあるでしょう. IRI から URI へのクオート処理や変換処理はやや厄介なので, Django は補助にな る機能をいくつか提供しています.
- django.utils.encoding.iri_to_uri() は (RFC 3987 準拠の) IRI から URI への変換を実装しています.
- django.utils.http.urlquote() および django.utils.http.urlquote_plus() は, Python の標準ライブラリで 提供している urllib.quote() および urllib.quote_plus() に手を 加えて,(データをエンコーディング前に UTF-8 文字列に変換することで)非 ASCII 文字を扱えるようにしています.
これらの関数は,それぞれのグループごとに少し違った目的があり,きちんとした 使い分けが必要です.通常,IRI や URI パスを構成する個々の部分文字列には urlquote() を使い, '&' や '%' といった予約文字が正しくエンコー ドされるようにします.その後, iri_to_uri を IRI 全体に適用して,非 ASCII 文字が正しい値にエンコードされるようにしてください.
Note
技術的には, iri_to_uri() は IRI 仕様に準拠した完全なアルゴリズムを 実装しているわけではありません.このメソッドは,(いまのところ) 国際化ド メイン名のエンコーディングを行っていないからです.
iri_to_uri() は, URL 内での使用を許されている ASCII 文字を一切変換しま せん.従って,例えば '%' のような文字を iri_to_url() に渡してもエン コーディングは起こりません.つまり,この関数に完全な URL を渡しても,クエリ 文字列部分などを壊してしまうことはありません.
以下の例を見ればわかりやすいでしょう:
>>> urlquote(u'Paris & Orléans') u'Paris%20%26%20Orl%C3%A9ans' >>> iri_to_uri(u'/favorites/François/%s' % urlquote(u'Paris & Orléans')) '/favorites/Fran%C3%A7ois/Paris%20%26%20Orl%C3%A9ans'
注意深く見れば,二つ目の例では, urlquote() が生成した URL を iri_to_uri() が誤って二重クオートしていないことがわかるでしょう.これは 非常に重要かつ有用な機能です.つまり,非 ASCII 文字列が入っているかどうかを 気にせず IRI を構築してもかまわず,最後に iri_to_uri() を呼び出しさえす ればよいということなのです.
iri_to_uri() は等冪性がある (何度適用しても同じ結果になる) ので,以下の ような関係が常に成立します:
iri_to_uri(iri_to_uri(some_string)) = iri_to_uri(some_string)
従って, iri_to_uri() は同じ IRI に対して二重クオート問題を気にせず何度 でも呼び出せます.
モデル
データベースから返される文字列は全て Unicode 文字列です。文字ベースのモデル フィールド (CharField, TextField, URLField など) の値をデータベースから取り 出すと,データが ASCII バイト文字列の範疇に収まるかどうかに関わらず,常に Unicode 型の値になります.
バイト文字列を渡してモデルを作成したり,フィールドに値を入れたりしてもよく, その場合には Django が必要に応じてデータを Unicode 型に変換します.
__str__() と __unicode__() のどちらを使うべきか
デフォルトで Unicode を使うようにした結果,モデルのデータを出力するときに 注意せねばならない点が一つ増えました.
具体的には,モデルに __str__() メソッドを定義する代わりに __unicode__() メソッドを実装するよう推奨します. __unicode__() メソッ ドの中では,モデルのフィールド値を使って好きな値を作成でき,その値がバイト 文字列として適切に表現されるかを気にせず返してかまいません (たとえ __str__() に Unicode オブジェクトを返させるように実装したとしても, Python の仕様によって, __str__() は 常に バイト文字列を返します).
もちろん,必要であれば,モデルに __str__() メソッドを定義してもかまいま せんが,明確な理由がないかぎりそうするべきではありません. Django の モデルクラスのベースクラスでは,自動的にモデルに __str__() メソッドを追 加するようになっていて,この __str__() 実装は内部で __unicode__() を呼び出し,その結果を UTF-8 で返します.つまり, __unicode__() メソッ ドだけを定義しておけば, Django が自動的にバイト文字列への型強制を処理して くれるのです.
get_absolute_url() に注意
URL に含めてよい文字は ASCII 文字だけです.従って,非 ASCII 文字を含むよう なデータから URL を構築する場合, URL として適切な値になるようにエンコード する必要があります. django.db.models.permalink() デコレータはこの処理 を自動的に行います.
URL を手動で生成する場合 (permalink() デコレータを 使わない 場合), 作り手が自分でエンコーディングに気を配らねばなりません.この場合, 前述の iri_to_uri() および urlquote() 関数を使っ てください.例えば:
from django.utils.encoding import iri_to_uri
from django.utils.http import urlquote
def get_absolute_url(self):
url = u'/person/%s/?x=0&y=0' % urlquote(self.location)
return iri_to_uri(url)
この関数は, self.location に "Jack visited Paris & Orléans" のような文 字列が入っていてもただしくエンコードされた URL を返します. (実際,上の例で は,最初の行のクオート処理で非 ASCII 文字が落ちるので,厳密には iri_to_uri() の呼び出しは不要です)
データベース API
filter() メソッドなどのデータベース API の引数には, Unicode 文字列と, UTF-8 でエンコードされたバイト文字列のどちらを渡してもかまいません.以下の 二つのクエリセットは全く同じです:
qs = People.objects.filter(name__contains=u'Å') qs = People.objects.filter(name__contains='\xc3\85') # UTF-8 encoding of Å
テンプレート
テンプレートの手動生成には, Unicode とバイト文字列のどちらでも使えます:
from django.template import Template
t1 = Template('バイト文字列のテンプレートだよ.')
t2 = Template(u'Unicode のテンプレートだよ.')
とはいえ,ほとんどのケースではファイルシステムからテンプレートを読み出して いることでしょう.そして,ここにはちょっと難しい問題があります.というのも, ファイルシステム上のファイルが常に UTF-8 でエンコードされているとは限らない からです.ファイルシステム上のテンプレートファイルが UTF-8 でエンコードされ ていない場合は, FILE_CHARSET を該当ファイルのエンコードに設定してくだ さい. Django はテンプレートファイルを読み出すときに,設定値に基づいてテン プレートの内容をデコードして Unicode オブジェクトに変換します (FILE_CHARSET のデフォルト値は 'utf-8' です.)
レンダリング済みのテンプレートのエンコーディングは DEFAULT_CHARSET 設定 で制御されています.この設定のデフォルト値は UTF-8 です.
テンプレートタグとフィルタ
テンプレートタグやフィルタを定義する場合,以下の 2 点に注意してください:
- テンプレートタグの render() メソッドやテンプレートフィルタの戻 り値には,常に Unicode 文字列を使ってください.
- smart_unicode() よりも force_unicode() を使うようにしてくださ い.タグのレンダリングやフィルタの呼び出しはテンプレートのレンダリン グ中に行われるので,遅延翻訳オブジェクトから文字列への変換を遅らせる 意味がないからです.また,この時点では, Unicode 文字列だけを扱うよう にした方が簡単です.
メール
Django のメール送信フレームワーク (django.core.mail) は,透過的に Unicode をサポートしており,メッセージ本体やヘッダに Unicode データを指定で きます.ただし,メールアドレスには ASCII 文字しか使わないといったように,一 般のメールの仕様は守るよう気を配るべきでしょう.
以下のコード例では,メールアドレス以外が非 ASCII 文字列であるようなメールを 送信しています:
from django.core.mail import EmailMessage subject = u'My visit to Sør-Trøndelag' sender = u'Arnbjörg Ráðormsdóttir <arnbjorg@example.com>' recipients = ['Fred <fred@example.com'] body = u'...' EmailMessage(subject, body, sender, recipients).send()
フォームから提出されたデータ
HTML フォームから提出されたデータの扱いはいささか厄介な問題です.提出データ にエンコーディング情報が入っている保証がないため,結局フレームワーク側でデー タのエンコーディングを推定することになるからです.
Django はフォームデータのデコードに「遅延的」アプローチを取っています. HttpRequest オブジェクト内のデータは,データにアクセスする瞬間にのみデ コードされます.実際には,ほとんどのデータはまったくデコードされず, HttpRequest.GET や HttpRequest.POST データ構造の一部がデコードされ ることになるでしょう.これら二つの辞書は,そのメンバを常に Unicode データと して返します.対して, HttpRequest の他の属性やメソッドの値はクライアン トから送信されたそのままの値になります.
デフォルトでは,データのエンコーディングは DEFAULT_CHARSET 設定値である と想定しています.特定のフォームに対してエンコーディングを変えたければ, 以下のように HttpRequest インスタンスの encoding 属性を変更します:
def some_view(request):
# (何かの理由で) データが必ず KOI8-R でエンコードされている場合
request.encoding = 'koi8-r'
...
request.GET や request.POST にアクセスした後に encoding を変更 してもかまいません.変更すると,それ以後のアクセス時に新しいエンコーディン グを使ってデータをデコードします.
ほとんどの開発者は,フォームのエンコーディングについて心配することはないで しょう.とはいえ,エンコーディングを制御できないような古いシステムを相手に するアプリケーションを開発する際には,この機能は有用なはずです.
Django はアップロードされたファイルの内容をデコードしません.通常,ファイル 上のデータは文字列ではなく単なるバイト列とみなすべきだからです.自動的にデ コードを行って,バイトストリームの意味が変わってしまうのはまずいですよね.