Unicode 数据 — Django 文档

来自菜鸟教程
Django/docs/2.2.x/ref/unicode
跳转至:导航、​搜索

Unicode 数据

Django 处处支持 Unicode 数据。

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

创建数据库

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

  • MySQL 用户,有关如何设置或更改数据库字符集编码的详细信息,请参阅 MySQL 手册
  • PostgreSQL 用户,请参阅 PostgreSQL 手册 (PostgreSQL 9 中的第 22.3.2 节)以了解有关使用正确编码创建数据库的详细信息。
  • Oracle 用户,请参阅 Oracle 手册 以了解如何设置(第 2 节)或更改(第 11 节)数据库字符集编码的详细信息。
  • SQLite 用户,您无需执行任何操作。 SQLite 始终使用 UTF-8 进行内部编码。

Django 的所有数据库后端都会自动将字符串转换为适当的编码,以便与数据库对话。 它们还会自动将从数据库中检索到的字符串转换为字符串。 你甚至不需要告诉 Django 你的数据库使用什么编码:这是透明处理的。

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


一般字符串处理

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

警告

字节串不携带任何有关其编码的信息。 出于这个原因,我们必须做一个假设,Django 假设所有字节串都是 UTF-8。

如果你将一个字符串传递给 Django,它已经以某种其他格式编码,事情就会以有趣的方式出错。 通常,Django 会在某个时候引发 UnicodeDecodeError


如果您的代码仅使用 ASCII 数据,那么使用普通字符串并随意传递它们是安全的,因为 ASCII 是 UTF-8 的子集。

不要误以为如果你的 :setting:`DEFAULT_CHARSET` 设置不是 'utf-8' 你可以在你的字节串中使用其他编码! :setting:`DEFAULT_CHARSET` 仅适用于作为模板渲染(和电子邮件)结果生成的字符串。 Django 将始终假定内部字节串采用 UTF-8 编码。 这样做的原因是 :setting:`DEFAULT_CHARSET` 设置实际上不在您的控制之下(如果您是应用程序开发人员)。 它由安装和使用您的应用程序的人控制——如果该人选择不同的设置,您的代码仍必须继续工作。 因此,它不能依赖该设置。

在大多数情况下,当 Django 处理字符串时,它会在做任何其他事情之前将它们转换为字符串。 因此,作为一般规则,如果您传入一个字节字符串,请准备好在结果中接收一个字符串。

翻译后的字符串

除了字符串和字节串之外,在使用 Django 时,您可能会遇到第三种类型的类似字符串的对象。 该框架的国际化特性引入了“懒惰翻译”的概念——一个已被标记为已翻译但其实际翻译结果在字符串中使用该对象之前无法确定的字符串。 在使用字符串之前翻译语言环境未知的情况下,此功能很有用,即使该字符串最初可能是在首次导入代码时创建的。

通常,您不必担心懒惰的翻译。 请注意,如果您检查一个对象并且它声称是一个 django.utils.functional.__proxy__ 对象,则它是一个懒惰的翻译。 使用延迟翻译作为参数调用 str() 将在当前语言环境中生成一个字符串。

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


有用的实用工具函数

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

转换函数

django.utils.encoding 模块包含一些函数,可以方便地在字符串和字节串之间来回转换。

  • smart_text(s, encoding='utf-8', strings_only=False, errors='strict') 将其输入转换为字符串。 encoding 参数指定输入编码。 (例如,Django 在处理表单输入数据时在内部使用它,该数据可能不是 UTF-8 编码的。)strings_only 参数,如果设置为 True,将产生 Python 数字、布尔值和 [ X199X] 未转换为字符串(它们保留其原始类型)。 errors 参数采用 Python 的 str() 函数接受的任何值,用于其错误处理。
  • force_text(s, encoding='utf-8', strings_only=False, errors='strict') 几乎在所有情况下都与 smart_text() 相同。 不同之处在于第一个参数是 延迟翻译 实例。 smart_text() 保留延迟翻译,而 force_text() 强制将这些对象转换为字符串(导致翻译发生)。 通常,您需要使用 smart_text()。 然而,force_text() 在模板标签和过滤器中很有用,这些模板标签和过滤器绝对 必须 有一个字符串可以使用,而不仅仅是可以转换为字符串的东西。
  • smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict') 本质上与 smart_text() 相反。 它强制第一个参数为字节串。 strings_only 参数的行为与 smart_text()force_text() 相同。 这与 Python 的内置 str() 函数的语义略有不同,但在 Django 内部的一些地方需要不同。

通常,您只需要使用 force_text()。 尽可能早地在任何可能是字符串或字节串的输入数据上调用它,从那时起,您可以将结果视为始终是字符串。


URI 和 IRI 处理

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

这两组函数的用途略有不同,保持它们的一致性很重要。 通常,您会使用quote()在 IRI 或 URI 路径的各个部分上,以便正确编码任何保留字符,例如“&”或“%”。 然后,您将 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)

此函数返回正确编码的 URL,即使self.location类似于“杰克访问了巴黎和奥尔良”。 (事实上,在上面的例子中, iri_to_uri() 调用并不是绝对必要的,因为所有非 ASCII 字符都会在第一行的引用中被删除。)


模板

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

from django.template import Template
t2 = Template('This is a string template.')

但常见的情况是从文件系统中读取模板。 如果您的模板文件不是以 UTF-8 编码存储,请调整 :setting:`TEMPLATES` 设置。 内置的 django 后端提供了 'file_charset' 选项来更改用于从磁盘读取文件的编码。

:setting:`DEFAULT_CHARSET` 设置控制渲染模板的编码。 默认设置为 UTF-8。

模板标签和过滤器

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

  • 始终从模板标签的 render() 方法和模板过滤器中返回字符串。
  • 在这些地方使用 force_text() 优先于 smart_text()。 标签渲染和过滤器调用在模板渲染时发生,因此推迟将惰性翻译对象转换为字符串没有任何好处。 那时单独使用字符串更容易。


文件

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

文件系统对 UTF-8 文件名的支持因环境而异。 通过运行以下命令检查交互式 Python shell 中的当前配置:

import sys
sys.getfilesystemencoding()

这应该输出“UTF-8”。

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

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

export LANG="en_US.UTF-8"

表单提交

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

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

默认情况下,:setting:`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 不解码文件上传的数据,因为该数据通常被视为字节集合,而不是字符串。 那里的任何自动解码都会改变字节流的含义。