Skip to main content

Unicode数据

Django本地支持Unicode数据无处不在。提供您的数据库可以以某种方式存储数据,您可以安全地传递Unicode字符串到模板,模型和数据库。

本文档告诉您需要知道的是,您是在编写使用数据的应用程序还是使用ASCII以外的编码的模板。

创建数据库

确保您的数据库配置为能够存储任意字符串数据。通常,这意味着给它一个UTF-8或UTF-16的编码。如果使用更严格的编码 - 例如latin1(iso8859-1) - 您将无法在数据库中存储某些字符,并且信息将丢失。

  • MySQL用户,有关如何设置或更改数据库字符集编码的详细信息,请参阅 MySQL manual

  • PostgreSQL用户,有关使用正确的编码创建数据库的详细信息,请参考 PostgreSQL manual (PostgreSQL 9中的第22.3.2节)。

  • Oracle用户,有关如何设置(section 2)或alter(section 11)数据库字符集编码的详细信息,请参阅 Oracle manual

  • SQLite用户,没有什么你需要做的。 SQLite总是使用UTF-8进行内部编码。

所有Django的数据库后端都自动将Unicode字符串转换为与数据库通信的适当编码。它们还自动将从数据库检索的字符串转换为Python Unicode字符串。你甚至不需要告诉Django你的数据库使用什么编码:这是透明地处理。

有关更多信息,请参阅下面的“数据库API”部分。

一般字符串处理

每当你使用Django的字符串 - 例如,在数据库查找,模板渲染或其他地方 - 你有两个选择编码这些字符串。您可以使用Unicode字符串,也可以使用使用UTF-8编码的普通字符串(有时称为“bytestrings”)。

在Python 3中,逻辑是相反的,这是正常的字符串是Unicode,当你想特别创建一个bytestring,你必须在字符串前面加一个’b’。正如我们在1.5版本的Django代码中所做的,我们建议您从代码中的__future__库导入 unicode_literals。然后,当您特定要创建一个bytestring字面量时,在字符串前加上“b”。

Python 2的遗产:

my_string = "This is a bytestring"
my_unicode = u"This is an Unicode string"

Python 2与unicode文字或Python 3:

from __future__ import unicode_literals

my_string = b"This is a bytestring"
my_unicode = "This is an Unicode string"

参见 Python 3兼容性

警告

一个字节不包含关于它的编码的任何信息。因此,我们必须做一个假设,Django假设所有的bytestrings都是UTF-8。

如果你传递一个字符串到Django已经编码的其他格式,事情会出错的有趣的方式。通常,Django会在某个时候产生 UnicodeDecodeError

如果你的代码只使用ASCII数据,可以安全地使用你的普通字符串,随意传递它们,因为ASCII是UTF-8的子集。

不要被愚弄以为,如果你的 DEFAULT_CHARSET 设置被设置为非 'utf-8',你可以使用其他编码在你的bytestrings! DEFAULT_CHARSET 仅适用于作为模板呈现(和电子邮件)的结果生成的字符串。 Django将总是假定内部bytestrings的UTF-8编码。原因是 DEFAULT_CHARSET 设置实际上不在您的控制之下(如果您是应用程序开发人员)。它是由安装和使用您的应用程序的人的控制 - 如果该人选择不同的设置,您的代码必须仍然继续工作。 Ergo,它不能依赖那个设置。

在大多数情况下,当Django处理字符串时,它会将它们转换为Unicode字符串,然后做其他事情。所以,作为一般规则,如果你传递一个字节,准备在结果中接收一个Unicode字符串。

翻译字符串

除了Unicode字符串和bytestrings,有使用Django时可能遇到的第三种类型的字符串对象。框架的国际化特征引入了“延迟转换”的概念 - 一个字符串,标记为已翻译,但实际的翻译结果直到该对象在字符串中使用才被确定。在使用字符串之前翻译语言环境未知的情况下,此功能非常有用,即使字符串最初可能是在首次导入代码时创建的。

通常,你不必担心延迟翻译。只要注意,如果你检查一个对象,它声称是一个 django.utils.functional.__proxy__ 对象,它是一个延迟的翻译。使用延迟转换作为参数调用 unicode() 将在当前语言环境中生成Unicode字符串。

有关延迟转换对象的更多详细信息,请参阅 国际化 文档。

有用的实用功能

因为一些字符串操作一次又一次出现,Django附带了一些有用的函数,应该使用Unicode和bytestring对象更容易一些。

转换函数

django.utils.encoding 模块包含几个函数,可以方便地在Unicode和字节之间来回转换。

  • smart_text(s, encoding='utf-8', strings_only=False, errors='strict') 将其输入转换为Unicode字符串。 encoding 参数指定输入编码。 (例如,Django在处理表单输入数据时使用它,这可能不是UTF-8编码的)。如果设置为True,strings_only 参数将导致Python数字,布尔值和 None 不转换为字符串保持其原始类型)。 errors 参数接受Python的 unicode() 函数接受的任何值,用于其错误处理。

    如果您通过 smart_text() 具有 __unicode__ 方法的对象,它将使用该方法进行转换。

  • 在几乎所有情况下,force_text(s, encoding='utf-8', strings_only=False, errors='strict')smart_text() 相同。区别在于第一个参数是 延迟翻译 实例。虽然 smart_text() 保留延迟翻译,force_text() 强制这些对象到Unicode字符串(导致翻译发生)。通常,你想使用 smart_text()。但是,force_text() 在模板标签和过滤器中非常有用,因为绝对 must 有一个字符串来处理,而不仅仅是可以转换为字符串的东西。

  • smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict') 本质上与 smart_text() 相反。它强制第一个参数为一个字节。 strings_only 参数具有与 smart_text()force_text() 相同的行为。这与Python的内置 str() 函数略有不同,但是在Django的内部结构中的几个地方需要区别。

通常,您只需要使用 smart_text()。尽可能早地对任何可能是Unicode或bytestring的输入数据进行调用,从那时起,您可以将结果视为始终是Unicode。

URI和IRI处理

Web框架必须处理URL(这是一种 IRI)。 URL的一个要求是它们仅使用ASCII字符编码。但是,在国际环境中,您可能需要从 IRI (非常宽松地构建一个可以包含Unicode字符的 URI)构造URL。引用和转换IRI到URI可能有点棘手,因此Django提供了一些帮助。

这两组功能的目的略有不同,重要的是保持它们的直线。通常,您将在IRI或URI路径的各个部分使用 urlquote(),以便正确编码任何保留字符,例如’&’或’%’。然后,将 iri_to_uri() 应用于完整IRI,并将任何非ASCII字符转换为正确的编码值。

注解

从技术上讲,iri_to_uri() 在IRI规范中实现了完整的算法是不正确的。它还没有执行算法的国际域名编码部分。

iri_to_uri() 函数不会更改URL中允许的ASCII字符。因此,例如,当传递给 iri_to_uri() 时,字符“%”不进一步编码。这意味着你可以传递一个完整的URL到这个函数,它不会弄乱查询字符串或类似的东西。

一个例子可能会在这里澄清一下:

>>> urlquote('Paris & Orléans')
'Paris%20%26%20Orl%C3%A9ans'
>>> iri_to_uri('/favorites/François/%s' % urlquote('Paris & Orléans'))
'/favorites/Fran%C3%A7ois/Paris%20%26%20Orl%C3%A9ans'

如果仔细观察,可以看到第二个示例中由 urlquote() 生成的部分在传递给 iri_to_uri() 时没有双引号。这是一个非常重要和有用的功能。这意味着你可以构造你的IRI而不用担心它是否包含非ASCII字符,然后,在结束时,在结果上调用 iri_to_uri()

类似地,Django提供了 django.utils.encoding.uri_to_iri(),它实现了根据 RFC 3987#section-3.2 从URI到IRI的转换。它对除了不表示有效UTF-8序列的那些编码之外的所有百分比编码进行解码。

一个例子来演示:

>>> uri_to_iri('/%E2%99%A5%E2%99%A5/?utf8=%E2%9C%93')
'/♥♥/?utf8=✓'
>>> uri_to_iri('%A9helloworld')
'%A9helloworld'

在第一个示例中,UTF-8字符和保留字符未引用。在第二个,百分比编码保持不变,因为它位于有效的UTF-8范围之外。

iri_to_uri()uri_to_iri() 函数都是幂等的,这意味着以下是总是正确的:

iri_to_uri(iri_to_uri(some_string)) == iri_to_uri(some_string)
uri_to_iri(uri_to_iri(some_string)) == uri_to_iri(some_string)

因此,您可以安全地在同一URI/IRI上多次调用它,而不会冒双重引用问题。

楷模

因为所有字符串都作为Unicode字符串从数据库返回,所以当Django从数据库中检索数据时,基于字符(CharField,TextField,URLField等)的模型字段将包含Unicode值。这是 always 的情况,即使数据可以适合ASCII字节表。

您可以在创建模型或填充字段时传递bytestrings,Django会在需要时将其转换为Unicode。

__str__()__unicode__() 之间选择

注解

如果你使用Python 3,你可以跳过这一节,因为你总是创建 __str__(),而不是 __unicode__()。如果你想与Python 2兼容,你可以用 python_2_unicode_compatible() 装饰你的模型类。

缺省情况下使用Unicode的一个后果是,从模型打印数据时必须小心。

特别是,不是给你的模型一个 __str__() 方法,我们建议你实现一个 __unicode__() 方法。在 __unicode__() 方法中,你可以非常安全地返回所有字段的值,而不必担心它们是否适合一个字节。 (Python的工作方式,__str__() 的结果是 always 一个bytestring,即使你不小心尝试返回一个Unicode对象)。

你仍然可以在你的模型上创建一个 __str__() 方法,当然,但你不应该需要这样做,除非你有一个很好的理由。 Django的 Model 基类自动提供一个 __str__() 实现,调用 __unicode__() 并将结果编码为UTF-8。这意味着你通常只需要实现一个 __unicode__() 方法,并让Django在需要时处理强制的字节。

照顾 get_absolute_url()

网址只能包含ASCII字符。如果要从可能是非ASCII的数据片段构造URL,请小心地以适合URL的方式对结果进行编码。 reverse() 功能自动处理这个。

如果您手动构建网址(即使用 reverse() 函数的 not),则需要自己处理编码。在这种情况下,使用记录 aboveiri_to_uri()urlquote() 函数。例如:

from django.utils.encoding import iri_to_uri
from django.utils.http import urlquote

def get_absolute_url(self):
    url = '/person/%s/?x=0&y=0' % urlquote(self.location)
    return iri_to_uri(url)

此函数返回正确编码的URL,即使 self.location 类似“杰克访问巴黎和奥尔良”。 (实际上,在上述示例中,iri_to_uri() 调用不是严格必需的,因为所有非ASCII字符都将在第一行的引号中删除。)

数据库API

您可以将Unicode字符串或UTF-8 bytestrings作为参数传递给数据库API中的 filter() 方法等。以下两个查询集是相同的:

from __future__ import unicode_literals

qs = People.objects.filter(name__contains='Å')
qs = People.objects.filter(name__contains=b'\xc3\x85') # UTF-8 encoding of Å

模板

手动创建模板时,可以使用Unicode或bytestrings:

from __future__ import unicode_literals
from django.template import Template
t1 = Template(b'This is a bytestring template.')
t2 = Template('This is a Unicode template.')

但是常见的情况是从文件系统读取模板,这产生了一个轻微的复杂性:并非所有文件系统都存储编码为UTF-8的数据。如果模板文件未以UTF-8编码存储,请将 FILE_CHARSET 设置设置为磁盘上文件的编码。当Django读取模板文件时,它会将数据从此编码转换为Unicode。 (默认情况下,FILE_CHARSET 设置为 'utf-8'。)

DEFAULT_CHARSET 设置控制着色模板的编码。默认情况下,它设置为UTF-8。

模板标签和过滤器

在编写自己的模板代码和过滤器时要记住几个提示:

  • 始终从模板标记的 render() 方法和模板过滤器返回Unicode字符串。

  • 在这些地方使用 force_text() 优先于 smart_text()。标签渲染和过滤器调用在渲染模板时发生,因此延迟将延迟翻译对象转换为字符串没有任何优势。在这一点上更容易单独使用Unicode字符串。

文件

如果您打算允许用户上传文件,则必须确保用于运行Django的环境配置为使用非ASCII文件名。如果未正确配置环境,则在使用包含非ASCII字符的文件名保存文件时,将遇到 UnicodeEncodeError 异常。

对UTF-8文件名的文件系统支持各不相同,可能取决于环境。通过运行在交互式Python shell中检查当前配置:

import sys
sys.getfilesystemencoding()

这应该输出“UTF-8”。

LANG 环境变量负责在Unix平台上设置预期的编码。请查阅操作系统和应用程序服务器的文档,了解相应的语法和位置以设置此变量。

在您的开发环境中,您可能需要向 ~.bashrc 添加一个设置,类似于::

export LANG="en_US.UTF-8"

电子邮件

Django的电子邮件框架(在 django.core.mail 中)透明地支持Unicode。您可以在邮件正文和任何标题中使用Unicode数据。但是,您仍然有义务遵守电子邮件规范的要求,因此,例如,电子邮件地址应该只使用ASCII字符。

以下代码示例演示除电子邮件地址之外的所有内容都可以是非ASCII:

from __future__ import unicode_literals
from django.core.mail import EmailMessage

subject = 'My visit to Sør-Trøndelag'
sender = 'Arnbjörg Ráðormsdóttir <arnbjorg@example.com>'
recipients = ['Fred <fred@example.com']
body = '...'
msg = EmailMessage(subject, body, sender, recipients)
msg.attach("Une pièce jointe.pdf", "%PDF-1.4.%...", mimetype="application/pdf")
msg.send()

表单提交

HTML表单提交是一个棘手的领域。不能保证提交将包括编码信息,这意味着框架可能必须猜测提交的数据的编码。

Django采用“惰性”方法来解码表单数据。 HttpRequest 对象中的数据只有在您访问它时才会被解码。事实上,大多数数据根本没有被解码。只有 HttpRequest.GETHttpRequest.POST 数据结构具有应用于它们的任何解码。这两个字段将返回其成员作为Unicode数据。 HttpRequest 的所有其他属性和方法返回的数据与客户端提交的完全相同。

默认情况下,DEFAULT_CHARSET 设置用作表单数据的假设编码。如果需要为特定表单更改此属性,可以在 HttpRequest 实例上设置 encoding 属性。例如:

def some_view(request):
    # We know that the data must be encoded as KOI8-R (for some reason).
    request.encoding = 'koi8-r'
    ...

您甚至可以在访问 request.GETrequest.POST 后更改编码,所有后续访问将使用新的编码。

大多数开发人员不必担心更改表单编码,但这对于与不能控制编码的传统系统交谈的应用程序是一个有用的功能。

Django不会解码文件上传的数据,因为该数据通常被视为字节集合,而不是字符串。任何自动解码都会改变字节流的含义。