Unicode 数据

    本文档告诉你,如果你要编写使用 ASCII 以外编码的数据或模板的应用程序,你需要知道什么。

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

    • PostgreSQL 用户,请参考 (PostgreSQL 9 中的 22.3.2 节),了解以正确编码创建数据库的细节。
    • 关于如何设置(第 2 节 )或改变( )数据库字符集编码,请参考 Oracle 手册
    • SQLite 用户,你不需要做什么。SQLite 一直使用 UTF-8 作为内部编码。

    所有 Django 的数据库后端都会自动将字符串转换为合适的编码来与数据库对话。它们也会自动将从数据库中获取的字符串转换为字符串。你甚至不需要告诉 Django 你的数据库使用什么编码:这都是透明的处理。

    更多内容,请看下面的“数据库 API”一节。

    一般字符串处理

    当你在 Django 中使用字符串时——例如,在数据库查询、模板渲染或其他任何地方——你有两种选择来编码这些字符串。你可以使用普通字符串或字节字符串(以 ‘b’ 开头)。

    警告

    一个字节字符串并没有携带任何关于其编码的信息。为此,我们必须做一个假设,Django 假设所有的字节字符串都是 UTF-8 编码。

    如果你传递给 Django 的字符串是用其他格式编码的,那么事情会以有趣的方式出错。通常情况下,Django 会在某些时候引发一个 UnicodeDecodeError

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

    不要傻傻的认为如果你将 设置为 'utf-8' 以外的其他编码,你就可以在你的字节字符串中使用其他编码。DEFAULT_CHARSET 只适用于模板渲染后生成的字符串(和电子邮件)。Django 对内部的字节字符串总是采用 UTF-8 编码。原因是 的配置实际上并不在你的控制之下(如果你是应用开发者的话)。它是在安装和使用你的应用程序的人的控制之下——如果这个人选择了不同的配置,你的代码仍然必须继续工作。因此,它不能依赖该配置。

    在大多数情况下,当 Django 处理字符串时,它会在做其他事情之前将它们转换为字符串。所以,一般来说,如果你传入一个字节字符串,要准备好在结果中收到一个字符串。

    除了字符串和字节字符串之外,还有第三种类型的字符串类对象,你在使用 Django 时可能会遇到。框架的国际化特性引入了“惰性翻译”的概念——一个已经被标记为翻译的字符串,但其实际的翻译结果直到该对象被用于字符串时才被确定。这个功能在以下情况下非常有用:在使用字符串之前,翻译的 locale 是未知的,即使该字符串可能是在第一次导入代码时创建的。

    通常情况下,你不必担心惰性翻译的问题。只是要注意,如果你检查一个对象,并且它声称是 django.utils.functional.__proxy__ 对象,它就是一个惰性翻译。调用 str() 作为参数调用 str() 将生成一个当前语言环境下的字符串。

    关于惰性翻译对象的更多细节,请参考 国际化 文档。

    因为有些字符串操作会反复出现,所以 Django 提供了一些有用的函数,这些函数可以使处理字符串和字节字符串对象变得更加容易。

    转换函数

    • 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 处理

    网络框架必须处理 URL(是 IRI 的一种)。URL 的一个要求是,它们只能使用 ASCII 字符进行编码。然而,在国际环境中,您可能需要从 IRI 构建一个 URL —— 非常宽泛地讲,一个可以包含 Unicode 字符的 。使用这些函数来引用 IRI 并将其转换为 URI。

    • 函数,它实现了 所要求的从 IRI 到 URI 的转换。
    • 来自 Python 标准库的 urllib.parse.quote() 和 函数。

    这两组函数的目的略有不同,因此必须将它们区分开来。通常,你会在 IRI 或 URI 路径的个别部分使用 quote(),这样任何保留的字符如“&”或“%”都会被正确编码。然后,你将 iri_to_uri() 应用于整个 IRI,它将任何非 ASCII 字符转换为正确的编码值。

    注解

    从技术上讲,说 iri_to_uri() 实现了 IRI 规范中的全部算法是不正确的。它并不(尚未)执行算法的国际域名编码部分。

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

    举个例子可以说明这里的情况:

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

    类似的,Django 提供了 django.utils.encoding.uri_to_iri(),它按照 实现了从 URI 到 IRI 的转换。

    举例说明:

    1. >>> from django.utils.encoding import uri_to_iri
    2. >>> uri_to_iri('/%E2%99%A5%E2%99%A5/?utf8=%E2%9C%93')
    3. '/♥♥/?utf8=✓'
    4. >>> uri_to_iri('%A9hello%3Fworld')

    在第一个例子中,UTF-8 字符没有被引用。在第二个例子中,百分比编码保持不变,因为它们位于有效的 UTF-8 范围之外或代表一个保留字符。

    iri_to_uri()uri_to_iri() 函数都是幂等的,这意味着以下内容始终为真:

    所以你可以安全地在同一个 URI/IRI 上多次调用它,而不会有重复引用问题的风险。

    因为所有的字符串都是以 str 对象的形式从数据库中返回的,所以当 Django 从数据库中检索数据时,基于字符的模型字段(CharField、TextField、URLField 等)将包含 Unicode 值。这 总是 这样的情况,即使数据可以放入 ASCII 字节字符串中。

    你可以在创建模型或填充字段时传入字节字符串,Django 会在需要时将其转换为字符串。

    如果你是手动构建一个 URL(即 使用 reverse() 函数),你就需要自己进行编码。在这种情况下,请使用 上面 记载的 iri_to_uri()quote() 函数。例如:

    1. from urllib.parse import quote
    2. from django.utils.encoding import iri_to_uri
    3. def get_absolute_url(self):
    4. return iri_to_uri(url)

    即使 self.location 是类似于“Jack visited Paris & Orléans”,这个函数也会返回一个正确编码的 URL。(事实上,在上面的例子中,iri_to_uri() 的调用并不是绝对必要的,因为所有非 ASCII 字符都会在第一行的引号中被删除。)

    模板

    手动创建模板时使用字符串:

    但常见的情况是从文件系统中读取模板。如果你的模板文件没有采用 UTF-8 编码存储,请调整 配置。内置的 django 后端提供了 'file_charset' 选项,可以改变从磁盘读取文件时使用的编码。

    配置控制了渲染模板的编码。默认配置为 UTF-8。

    在编写自己的模板标签和过滤器时,要记住几个小技巧:

    • 总是从模板标签的 render() 方法和模板过滤器中返回字符串:
    • 在这些地方使用 force_str(),而不是 smart_str()。标签渲染和过滤器调用是在模板渲染时发生的,所以推迟将懒惰翻译对象转换为字符串没有好处。这时只用字符串工作更容易。

    如果你打算允许用户上传文件,你必须确保用于运行 Django 的环境被配置成可以使用非 ASCII 文件名。如果你的环境配置不正确,当保存文件名包含非 ASCII 字符的文件时,你会遇到 UnicodeEncodeError 异常。

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

    1. import sys
    2. sys.getfilesystemencoding()

    这应该输出“UTF-8”。

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

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

    表单提交

    HTML 表单提交是一个棘手的领域。无法保证提交的数据会包含编码信息,这意味着框架可能不得不猜测提交数据的编码。

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

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

    1. def some_view(request):
    2. # We know that the data must be encoded as KOI8-R (for some reason).
    3. ...

    你甚至可以在访问了 request.GETrequest.POST 之后改变编码,所有后续的访问都将使用新的编码。

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