revision-up-to: | 8961 (1.0) |
---|
Django はネイティブで Unicode データをサポートしています。データベースの設 定を正しく行っている限り、テンプレートやモデル、データベース間で Unicode 文 字列を問題なく受渡しできます。
このドキュメントでは、非 ASCII の文字コードでエンコードされたデータやテンプ レートを扱うアプリケーションを作成するときに知っておくべきことを説明します。
まず、データベースが任意の文字列データを保存できるように設定してください。 通常、これは UTF-8 または UTF-16 を使うことを意味します。 latin1 (iso8859-1) のような、より文字セットの制約が強いエンコーディングを使ってい ると、一部の文字をデータベースに保存できず、情報が失われるかもしれません。
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 というものは、アプリケーションの開発者が自由にい じってよいものではなく、アプリケーションをインストールして使うユーザが自由 に設定して使うためのものだからです。ユーザが:setting: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 を扱うようにしてください。
Web フレームワークは (IRI の一形式である) URL を扱えねばなりません。 URL の必要条件の一つとして、 URL は全て ASCII 文字セットで構成せねばなりま せん。しかし、国際化環境では、 URL を IRI で構築せねばならない、ざっくりと 言えば、Unicode 文字を含んだ URI を構築せねばならない場合もあるでしょう。 IRI から URI へのクオート処理や変換処理はやや厄介なので、 Django は補助にな る機能をいくつか提供しています。
これらの関数は、それぞれのグループごとに少し違った目的があり、きちんとした 使い分けが必要です。通常、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 型に変換します。
デフォルトで Unicode を使うようにした結果、モデルのデータを出力するときに 注意せねばならない点が一つ増えました。
具体的には、モデルに __str__() メソッドを定義する代わりに __unicode__() メソッドを実装するよう推奨します。 __unicode__() メソッ ドの中では、モデルのフィールド値を使って好きな値を作成でき、その値がバイト 文字列として適切に表現されるかを気にせず返してかまいません (たとえ __str__() に Unicode オブジェクトを返させるように実装したとしても、 Python の仕様によって、 __str__() は 常に バイト文字列を返します)。
もちろん、必要であれば、モデルに __str__() メソッドを定義してもかまいま せんが、明確な理由がないかぎりそうするべきではありません。 Django の モデルクラスのベースクラスでは、自動的にモデルに __str__() メソッドを追 加するようになっていて、この __str__() 実装は内部で __unicode__() を呼び出し、その結果を UTF-8 で返します。つまり、 __unicode__() メソッ ドだけを定義しておけば、 Django が自動的にバイト文字列への型強制を処理して くれるのです。
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() の呼び出しは不要です)
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 点に注意してください:
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 はアップロードされたファイルの内容をデコードしません。通常、ファイル 上のデータは文字列ではなく単なるバイト列とみなすべきだからです。自動的にデ コードを行って、バイトストリームの意味が変わってしまうのはまずいですよね。
Aug 31, 2012