Unicode 数据¶
Django 在任何地方都支持 Unicode 数据。
如果你编写的应用程序使用了非 ASCII 编码的数据或模板,本文档将告诉你需要了解的内容。
创建数据库¶
请确保你的数据库配置为能够存储任意字符串数据。通常,这意味着将其编码设置为 UTF-8 或 UTF-16。如果你使用更具限制性的编码(例如 latin1 (iso8859-1)),你将无法在数据库中存储某些字符,并且信息会丢失。
MySQL 用户,请参考 MySQL 手册了解有关如何设置或更改数据库字符集编码的详细信息。
PostgreSQL 用户,请参考 PostgreSQL 手册了解有关以正确编码创建数据库的详细信息。
Oracle 用户,请参考 Oracle 手册了解有关如何设置(第 2 节)或更改(第 11 节)数据库字符集编码的详细信息。
SQLite 用户,无需进行任何操作。SQLite 在内部编码中始终使用 UTF-8。
所有 Django 数据库后端都会自动将字符串转换为与数据库通信所需的适当编码。它们还会自动将从数据库检索到的字符串转换为(Python)字符串。你甚至不需要告诉 Django 你的数据库使用什么编码:这些都是透明处理的。
更多信息,请参阅下方的“数据库 API”一节。
常规字符串处理¶
每当你在 Django 中使用字符串时(例如,在数据库查询、模板渲染或其他任何地方),你有两种选择来对这些字符串进行编码。你可以使用普通字符串或字节串(以 'b' 开头)。
警告
字节串本身不包含任何关于其编码的信息。因此,我们必须做出假设,Django 假设所有字节串都是 UTF-8 编码的。
如果你向 Django 传递了一个以其他格式编码的字符串,事情会以有趣的方式出错。通常,Django 会在某个时刻引发 UnicodeDecodeError。
如果你的代码只使用 ASCII 数据,那么使用普通字符串并随意传递它们是安全的,因为 ASCII 是 UTF-8 的子集。
不要以为如果你的 DEFAULT_CHARSET 设置为 'utf-8' 以外的值,你就可以在字节串中使用该其他编码!DEFAULT_CHARSET 仅适用于作为模板渲染(和电子邮件)结果生成的字符串。Django 始终假设内部字节串采用 UTF-8 编码。这样做的原因是 DEFAULT_CHARSET 设置实际上不受你(应用程序开发者)控制。它是由安装和使用你应用程序的人控制的——如果那个人选择了不同的设置,你的代码必须仍然能正常工作。因此,它不能依赖于该设置。
在大多数情况下,当 Django 处理字符串时,它会先将它们转换为字符串再进行其他操作。因此,作为一般规则,如果你传入的是字节串,请准备好接收一个字符串结果。
翻译字符串¶
除了字符串和字节串之外,在使用 Django 时你可能会遇到第三种类似字符串的对象。该框架的国际化功能引入了“惰性翻译”(lazy translation)的概念——一个被标记为已翻译但直到对象在字符串中使用时才确定其实际翻译结果的字符串。此功能在翻译区域设置在字符串使用前未知的情况下非常有用,即使该字符串最初可能是在代码首次导入时创建的。
通常,你不需要担心惰性翻译。只需注意,如果你检查一个对象,它声称是一个 django.utils.functional.__proxy__ 对象,那么它就是一个惰性翻译。以惰性翻译作为参数调用 str() 将生成当前区域设置下的字符串。
有关惰性翻译对象的更多详细信息,请参阅 国际化 文档。
实用工具函数¶
由于某些字符串操作经常出现,Django 提供了一些实用的函数,使处理字符串和字节串对象变得更容易。
转换函数¶
django.utils.encoding 模块包含一些用于在字符串和字节串之间进行转换的便捷函数。
smart_str(s, encoding='utf-8', strings_only=False, errors='strict')将其输入转换为字符串。encoding参数指定输入编码。(例如,Django 在处理表单输入数据时会在内部使用此函数,这些数据可能不是 UTF-8 编码的。)如果strings_only参数设置为 True,则 Python 数字、布尔值和None将不会被转换为字符串(它们保持其原始类型)。errors参数接受 Python 的str()函数错误处理所接受的任何值。force_str(s, encoding='utf-8', strings_only=False, errors='strict')在几乎所有情况下都与smart_str()相同。区别在于第一个参数是 惰性翻译 实例时。虽然smart_str()会保留惰性翻译,但force_str()会强制将这些对象转换为字符串(从而触发翻译)。通常,你会想使用smart_str()。但是,force_str()在模板标签和过滤器中非常有用,因为它们绝对必须使用字符串来工作,而不仅仅是可以使用可以转换为字符串的对象。smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict')本质上是smart_str()的反面。它强制将第一个参数转换为字节串。strings_only参数的行为与smart_str()和force_str()的行为相同。这与 Python 内置的str()函数在语义上略有不同,但在 Django 内部的某些地方需要这种差异。
通常,你只需要使用 force_str()。尽早对任何可能是字符串或字节串的输入数据调用它,从那时起,你可以将结果始终视为字符串。
URI 和 IRI 处理¶
Web 框架必须处理 URL(它是 IRI 的一种)。URL 的一个要求是它们必须仅使用 ASCII 字符编码。然而,在国际化环境中,你可能需要从 IRI 构建 URL——非常笼统地说,是一个可以包含 Unicode 字符的 URI。使用这些函数来引用(quoting)和将 IRI 转换为 URI
django.utils.encoding.iri_to_uri()函数实现了 RFC 3987 第 3.1 节 所要求的从 IRI 到 URI 的转换。来自 Python 标准库的
urllib.parse.quote()和urllib.parse.quote_plus()函数。
这两组函数有略微不同的目的,将它们区分开来很重要。通常,你会对 IRI 或 URI 路径的各个部分使用 quote(),以便正确编码诸如“&”或“%”之类的保留字符。然后,将 iri_to_uri() 应用于完整的 IRI,它会将所有非 ASCII 字符转换为正确的编码值。
注意
从技术上讲,说 iri_to_uri() 实现了 IRI 规范中的完整算法是不正确的。它(目前)没有执行算法中的国际化域名编码部分。
iri_to_uri() 函数不会更改 URL 中允许的其他 ASCII 字符。因此,例如,当传递给 iri_to_uri() 时,字符 '%' 不会被进一步编码。这意味着你可以将完整的 URL 传递给此函数,它不会破坏查询字符串或类似内容。
一个例子可能有助于澄清问题
>>> from urllib.parse import quote
>>> from django.utils.encoding import iri_to_uri
>>> quote("Paris & Orléans")
'Paris%20%26%20Orl%C3%A9ans'
>>> iri_to_uri("/favorites/François/%s" % quote("Paris & Orléans"))
'/favorites/Fran%C3%A7ois/Paris%20%26%20Orl%C3%A9ans'
如果你仔细观察,你会发现第二个例子中由 quote() 生成的部分在传递给 iri_to_uri() 时没有被二次引用。这是一个非常重要且有用的功能。这意味着你可以构建 IRI,而不必担心它是否包含非 ASCII 字符,然后在最后对结果调用 iri_to_uri()。
同样,Django 提供了 django.utils.encoding.uri_to_iri(),它根据 RFC 3987 第 3.2 节 实现了从 URI 到 IRI 的转换。
一个演示示例
>>> from django.utils.encoding import uri_to_iri
>>> uri_to_iri("/%E2%99%A5%E2%99%A5/?utf8=%E2%9C%93")
'/♥♥/?utf8=✓'
>>> uri_to_iri("%A9hello%3Fworld")
'%A9hello%3Fworld'
在第一个示例中,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 上多次调用它,而不会面临双重转义的问题。
模型(Models)¶
由于所有字符串都是从数据库作为 str 对象返回的,因此当 Django 从数据库检索数据时,基于字符的模型字段(CharField、TextField、URLField 等)将包含 Unicode 值。即使数据适合 ASCII 字节串,情况也始终如此。
你可以在创建模型或填充字段时传入字节串,Django 会在需要时将其转换为字符串。
在 get_absolute_url() 中要小心¶
URL 只能包含 ASCII 字符。如果你正在从可能包含非 ASCII 的数据片段构建 URL,请务必以适合 URL 的方式对结果进行编码。reverse() 函数会自动为你处理这个问题。
如果你是手动构建 URL(即不使用 reverse() 函数),则需要自己处理编码。在这种情况下,请使用上面记录的 iri_to_uri() 和 quote() 函数。例如
from urllib.parse import quote
from django.utils.encoding import iri_to_uri
def get_absolute_url(self):
url = "/person/%s/?x=0&y=0" % quote(self.location)
return iri_to_uri(url)
即使 self.location 类似于“Jack visited Paris & Orléans”,此函数也会返回正确编码的 URL。(事实上,在上面的例子中 iri_to_uri() 调用并不是严格必需的,因为所有非 ASCII 字符在第一行的引用中都会被删除。)
模板¶
手动创建模板时使用字符串
from django.template import Template
t2 = Template("This is a string template.")
但常见的情况是从文件系统读取模板。如果你的模板文件不是以 UTF-8 编码存储的,请调整 TEMPLATES 设置。内置的 django 后端提供了 'file_charset' 选项,用于更改从磁盘读取文件时使用的编码。
DEFAULT_CHARSET 设置控制渲染模板的编码。默认为 UTF-8。
文件¶
如果你打算允许用户上传文件,则必须确保用于运行 Django 的环境已配置为可以使用非 ASCII 文件名。如果你的环境配置不正确,在保存包含非 ASCII 字符的文件名或内容的文件时,将会遇到 UnicodeEncodeError 异常。
文件系统对 UTF-8 文件名的支持各不相同,可能取决于环境。通过运行以下命令在交互式 Python shell 中检查你当前的配置
import sys
sys.getfilesystemencoding()
这应该输出“UTF-8”。
LANG 环境变量负责在 Unix 平台上设置预期的编码。请查阅你的操作系统和应用服务器的文档,了解设置此变量的正确语法和位置。有关示例,请参阅 如何将 Django 与 Apache 和 mod_wsgi 一起使用。
在开发环境中,你可能需要在 ~.bashrc 中添加类似于以下的设置
export LANG="en_US.UTF-8"
表单提交¶
HTML 表单提交是一个棘手的领域。无法保证提交会包含编码信息,这意味着框架可能不得不猜测提交数据的编码。
Django 对表单数据的解码采用“惰性”方法。HttpRequest 对象中的数据仅在你访问时才会被解码。事实上,大部分数据根本没有被解码。只有 HttpRequest.GET 和 HttpRequest.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.GET 或 request.POST 之后更改编码,随后的所有访问都将使用新编码。
大多数开发者不需要担心更改表单编码,但这对于与你无法控制其编码的遗留系统通信的应用程序来说是一个有用的功能。
Django 不会对文件上传的数据进行解码,因为这些数据通常被视为字节集合,而不是字符串。那里的任何自动解码都会改变字节流的含义。