Unicode 数据

Django 在任何地方都支持 Unicode 数据。

如果您正在编写使用非 ASCII 编码的数据或模板的应用程序,本文档将告诉您需要了解的内容。

创建数据库

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

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

  • PostgreSQL 用户,请参阅PostgreSQL 手册了解有关使用正确编码创建数据库的详细信息。

  • Oracle 用户,请参阅Oracle 手册了解有关如何设置(第 2 节)或更改(第 11 节)数据库字符集编码的详细信息。

  • 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 时,您可能会遇到第三种字符串状对象。框架的国际化功能引入了“延迟翻译”的概念——一个已被标记为已翻译但其实际翻译结果直到对象在字符串中使用才确定的字符串。此功能在直到使用字符串时才知晓翻译语言环境的情况下很有用,即使字符串最初是在代码第一次导入时创建的。

通常,您无需担心延迟翻译。只需注意,如果您检查一个对象并且它声称是一个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——非常宽松地说,一个URI可以包含 Unicode 字符。使用这些函数来引用并将 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 第 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 对象返回,所以基于字符的模型字段 (CharField、TextField、URLField 等) 在 Django 从数据库检索数据时将包含 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()。模板标签渲染和过滤器调用在模板渲染时发生,因此推迟将延迟翻译对象转换为字符串没有任何优势。在这一点上,仅使用字符串更容易操作。

文件

如果您打算允许用户上传文件,则必须确保用于运行 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.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 不会解码文件上传的数据,因为这些数据通常被视为字节集合,而不是字符串。任何自动解码都会改变字节流的含义。

返回顶部