Unicode数据

Django本地支持任何地方的Unicode数据。 提供数据库可以以某种方式存储数据,您可以安全地将字符串传递给模板,模型和数据库。

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

创建数据库

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

  • MySQL用户,请参阅MySQL手册了解如何设置或更改数据库字符集编码的详细信息。
  • PostgreSQL users, refer to the PostgreSQL manual (section 22.3.2 in PostgreSQL 9) for details on creating databases with the correct encoding.
  • 关于如何设置(section 2)或alter(section 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时可能遇到的第三种字符串类型的对象。 框架的国际化功能引入了“懒惰翻译”的概念 - 一个已标记为已翻译的字符串,但其实际翻译结果直到对象用于字符串才被确定。 即使字符串可能最初是在第一次导入代码时创建的,但在转换语言环境为未知之前,此功能仍非常有用。

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

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

有用的实用功能

由于一些字符串操作一次又一次出现,Django提供了一些有用的函数,可以使字符串和字符串对象更容易处理。

转换函数

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

  • smart_text(s, encoding ='utf-8', strings_only = False, errors ='strict' / t4>将其输入转换为一个字符串。 encoding参数指定输入编码。 (例如,Django在处理表单输入数据时内部使用它,这可能不是UTF-8编码。) 如果设置为True,则strings_only参数将导致Python数字,布尔值和None未被转换为字符串(它们保持其原始类型)。 errors参数采用Python的str()函数接受的任何值来进行错误处理。
  • force_text(s, encoding='utf-8', strings_only=False, errors='strict') is identical to smart_text() in almost all cases. 不同之处在于第一个参数是一个lazy translation实例。 smart_text()保留惰性转换时,force_text()强制这些对象转换为一个字符串(导致转换发生)。 通常情况下,您需要使用smart_text() 然而,在模板标签和过滤器中,force_text()非常有用,绝对必须有一个字符串可以使用,而不仅仅是可以转换为字符串的东西。
  • smart_bytes(s, encoding ='utf-8', strings_only = False, errors ='strict' / t4>基本上与smart_text()相反。 它强制第一个参数为一个字节串。 strings_only参数具有与smart_text()force_text()相同的行为。 这与Python的内建str()函数的语义略有不同,但在Django内部的一些地方需要区别。

通常情况下,你只需要使用force_text() 尽可能早地调用任何可能是字符串或字符串的输入数据,从此,可以将结果视为一个字符串。

URI和IRI处理

Web框架必须处理URL(这是一种IRI)。 URL的一个要求是它们只使用ASCII字符进行编码。 However, in an international environment, you might need to construct a URL from an IRI – very loosely speaking, a URI that can contain Unicode characters. 使用这些函数来引用和将IRI转换为URI:

这两组函数的目的略有不同,重要的是保持直线。 通常,您可以在IRI或URI路径的各个部分使用quote(),以便任何保留字符(如'&'或'%')都被正确编码。 Then, you apply iri_to_uri() to the full IRI and it converts any non-ASCII characters to the correct encoded values.

注意

Technically, it isn’t correct to say that iri_to_uri() implements the full algorithm in the IRI specification. 它还没有执行算法的国际域名编码部分。

The iri_to_uri() function will not change ASCII characters that are otherwise permitted in a URL. 因此,例如,字符'%'在传递给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()时没有双引号。 这是一个非常重要和有用的功能。 It means that you can construct your IRI without worrying about whether it contains non-ASCII characters and then, right at the end, call iri_to_uri() on the result.

类似地,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上多次调用它,而不用担心双引号问题。

模型¶ T0>

因为所有的字符串都是以str对象的形式从数据库返回的,所以当Django从数据库中检索数据时,基于字符的模型字段(CharField,TextField,URLField等)将包含Unicode值。 This is always the case, even if the data could fit into an ASCII bytestring.

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

照顾get_absolute_url()

网址只能包含ASCII字符。 如果您要从可能为非ASCII的数据构建URL,请注意以适合URL的方式对结果进行编码。 reverse()函数会自动为您处理。

如果您正在使用reverse()函数手动构建URL(即not),则需要自己处理编码。 在这种情况下,使用上面中记录的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。 (In fact, the iri_to_uri() call isn’t strictly necessary in the above example, because all the non-ASCII characters would have been removed in quoting in the first line.)

数据库API

您可以将字符串或UTF-8字节作为参数传递给数据库API中的filter()方法等。 以下两个查询集是相同的:

qs = People.objects.filter(name__contains='Å')
qs = People.objects.filter(name__contains=b'\xc3\x85') # UTF-8 encoding of Å

模板¶ T0>

手动创建模板时可以使用字符串或UTF-8字节串:

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

但常见的情况是从文件系统中读取模板,这会造成一些小问题:并非所有文件系统都将其数据编码为UTF-8。 如果您的模板文件未以UTF-8编码存储,请将FILE_CHARSET设置为磁盘上文件的编码。 当Django读入一个模板文件时,它将把这个编码的数据转换成Unicode。 FILE_CHARSET默认设置为'utf-8')。

DEFAULT_CHARSET设置控制着色模板的编码。 这是默认设置为UTF-8。

模板标签和过滤器

在编写自己的模板标签和过滤器时要记住一些技巧:

  • 始终从模板标记的render()方法和模板过滤器返回字符串。
  • 在这些地方使用force_text()优先于smart_text() 标记渲染和过滤器调用是在模板被渲染的时候发生的,因此推迟惰性转换对象到字符串的转换是没有好处的。 在这一点上单纯地使用字符串更容易。

文件¶ T0>

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

对UTF-8文件名的文件系统支持各不相同,可能取决于环境。 通过运行以下命令在交互式Python shell中检查当前的配置:

import sys
sys.getfilesystemencoding()

这应该输出“UTF-8”。

The LANG environment variable is responsible for setting the expected encoding on Unix platforms. 请查阅适用于您的操作系统和应用程序服务器的文档,以获取适当的语法和位置以设置此变量。

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

export LANG="en_US.UTF-8"

表单提交

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

Django采用“懒惰”的方式来解码表单数据。 The data in an HttpRequest object is only decoded when you access it. 实际上,大部分数据根本就没有被解码。 只有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不会对文件上传的数据进行解码,因为这些数据通常被视为字节集合,而不是字符串。 那里的任何自动解码都会改变字节流的含义。