revision-up-to: | 17812 (1.4) |
---|
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 は補助にな る機能をいくつか提供しています。
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()
の呼び出しは不要です)
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'...'
msg = EmailMessage(subject, body, sender, recipients)
msg.attach(u"Une pièce jointe.pdf", "%PDF-1.4.%...", mimetype="application/pdf")
msg.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 はアップロードされたファイルの内容をデコードしません。通常、ファイル 上のデータは文字列ではなく単なるバイト列とみなすべきだからです。自動的にデ コードを行って、バイトストリームの意味が変わってしまうのはまずいですよね。
Oct 26, 2017