Unicode 数据

Django 处处支持 Unicode 数据。

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

创建数据库

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

  • MySQL 用户,请参考 MySQL 手册 ,了解如何设置或更改数据库字符集编码。
  • PostgreSQL users, refer to the PostgreSQL manual for details on creating databases with the correct encoding.
  • 关于如何设置(第 2 节 )或改变(第 11 节 )数据库字符集编码,请参考 Oracle 手册
  • SQLite 用户,你不需要做什么。SQLite 一直使用 UTF-8 作为内部编码。

所有 Django 的数据库后端都会自动将字符串转换为合适的编码来与数据库对话。它们也会自动将从数据库中获取的字符串转换为字符串。你甚至不需要告诉 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 时可能会遇到。框架的国际化特性引入了“惰性翻译”的概念——一个已经被标记为翻译的字符串,但其实际的翻译结果直到该对象被用于字符串时才被确定。这个功能在以下情况下非常有用:在使用字符串之前,翻译的 locale 是未知的,即使该字符串可能是在第一次导入代码时创建的。

通常情况下,你不必担心惰性翻译的问题。只是要注意,如果你检查一个对象,并且它声称是 django.utils.functional.__proxy__ 对象,它就是一个惰性翻译。调用 str() 作为参数调用 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 处理

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

这两组函数的目的略有不同,因此必须将它们区分开来。通常,你会在 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#section-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 上多次调用它,而不会有重复引用问题的风险。

模型

因为所有的字符串都是以 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。

模板标签和过滤器

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

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

文件

If you intend to allow users to upload files, you must ensure that the environment used to run Django is configured to work with non-ASCII file names. If your environment isn't configured correctly, you'll encounter UnicodeEncodeError exceptions when saving files with file names or content that contains non-ASCII characters.

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

import sys
sys.getfilesystemencoding()

这应该输出“UTF-8”。

The LANG environment variable is responsible for setting the expected encoding on Unix platforms. Consult the documentation for your operating system and application server for the appropriate syntax and location to set this variable. See the 如何使用 Apache 和 mod_wsgi 托管 Django for examples.

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

export LANG="en_US.UTF-8"

表单提交

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