# Django 3.2.11.dev 文档

# Unicode 数据

Django 处处支持 Unicode 数据。

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

## 创建数据库

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

- MySQL 用户，请参考 MySQL 手册 ，了解如何设置或更改数据库字符集编码。
- PostgreSQL 用户，请参考 PostgreSQL 手册 （PostgreSQL 9 中的 22.3.2 节），了解以正确编码创建数据库的细节。
- 关于如何设置（第 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。

- `django.utils.encoding.iri_to_uri()` 函数，它实现了 RFC 3987#section-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#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()`。标签渲染和过滤器调用是在模板渲染时发生的，所以推迟将懒惰翻译对象转换为字符串没有好处。这时只用字符串工作更容易。

## 文件

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