Django v1.0 documentation

Django での Unicode の扱い

revision-up-to:8961 (1.0)
Django 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 を扱うようにしてください。

URI および IRI の処理

Web フレームワークは (IRI の一形式である) 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.GETHttpRequest.POST データ構造の一部がデコードされ ることになるでしょう。これら二つの辞書は、そのメンバを常に Unicode データと して返します。対して、 HttpRequest の他の属性やメソッドの値はクライアン トから送信されたそのままの値になります。

デフォルトでは、データのエンコーディングは DEFAULT_CHARSET 設定 値であると想定しています。特定のフォームに対してエンコーディングを変えたけ れば、以下のように HttpRequest インスタンスの encoding 属性を変更し ます:

def some_view(request):
    # (何かの理由で) データが必ず KOI8-R でエンコードされている場合
    request.encoding = 'koi8-r'
    ...

request.GETrequest.POST にアクセスした後に encoding を変更 してもかまいません。変更すると、それ以後のアクセス時に新しいエンコーディン グを使ってデータをデコードします。

ほとんどの開発者は、フォームのエンコーディングについて心配することはないで しょう。とはいえ、エンコーディングを制御できないような古いシステムを相手に するアプリケーションを開発する際には、この機能は有用なはずです。

Django はアップロードされたファイルの内容をデコードしません。通常、ファイル 上のデータは文字列ではなく単なるバイト列とみなすべきだからです。自動的にデ コードを行って、バイトストリームの意味が変わってしまうのはまずいですよね。