Данные Unicode

Django supports Unicode data everywhere.

Этот документ расскажет вам, что вы должны знать, если вы пишете приложения, которые используют данные или шаблоны, закодированные, в что-то отличное от ASCII.

Создание базы данных

Убедитесь, что ваша база данных настроена с учетом возможности хранить произвольные строковые данные. Обычно, это значит хранение в кодировке UTF-8 или UTF-16. Если вы используете более строгие кодировки – к примеру latin1 (iso8859-1) – вы не сможете хранить определенные символы, и информация будет утеряна.

  • Пользователи MySQL могут обратиться к MySQL manual за подробностями, как установить или изменить кодировку базы данных.
  • Пользователи PostgreSQL могут обратиться к PostgreSQL manual (раздел 22.3.2 in PostgreSQL 9) за подробностями по созданию баз данных с правильной кодировкой.
  • Пользователи Oracle могут обратиться к Oracle manual за подробностями, как установить (section 2) или изменить (section 11) кодировку базы данных.
  • Пользователи SQLite могут ничего не делать. SQLite всегда использует UTF-8 в качестве внутренней кодировки.

All of Django’s database backends automatically convert strings into the appropriate encoding for talking to the database. They also automatically convert strings retrieved from the database into strings. You don’t even need to tell Django what encoding your database uses: that is handled transparently.

Подробнее смотрите раздел «API базы данных» ниже.

Общая обработка строк

Whenever you use strings with Django – e.g., in database lookups, template rendering or anywhere else – you have two choices for encoding those strings. You can use normal strings or bytestrings (starting with a „b“).

Предупреждение

Байтовые строки не могут содержать информацию о своих кодировках. По этой причине, мы должны делать предположение, и Django предполагает, что все байтовые строки будут в кодировке UTF-8.

Если передать строку в Django, которая была закодирована в каком-то другом формате, все пойдет не так. Как правило Django выбрасывает исключение UnicodeDecodeError в этом месте.

Если ваш код использует только данные ASCII, это позволяет использовать обыкновенные строки, они будут работать корректно, так как ASCII является подмножеством UTF-8.

Не обманывайте себя, думая что если ваш параметр DEFAULT_CHARSET установлен в какую-то отличную от 'utf-8' кодировку, то вы можете использовать другую кодировку в ваших байтовых строках. Параметр DEFAULT_CHARSET применяется только к строкам, сгенерированным в результате рендеринга шаблона (или email). Django всегда будет ожидать UTF-8 для внутренних байтовых строк. Причина этого в том, что параметр DEFAULT_CHARSET фактически находится не под вашим контролем (если вы разработчик приложения). Это будет под контролем, того, кто инсталирует и использует ваше приложение – и даже если будет выбрана другая кодировка, то ваш код обязан продолжать работать. Следовательно это не может зависеть от такого параметра.

In most cases when Django is dealing with strings, it will convert them to strings before doing anything else. So, as a general rule, if you pass in a bytestring, be prepared to receive a string back in the result.

Переведенные строки

Aside from strings and bytestrings, there’s a third type of string-like object you may encounter when using Django. The framework’s internationalization features introduce the concept of a «lazy translation» – a string that has been marked as translated but whose actual translation result isn’t determined until the object is used in a string. This feature is useful in cases where the translation locale is unknown until the string is used, even though the string might have originally been created when the code was first imported.

Normally, you won’t have to worry about lazy translations. Just be aware that if you examine an object and it claims to be a django.utils.functional.__proxy__ object, it is a lazy translation. Calling str() with the lazy translation as the argument will generate a string in the current locale.

Для более подробной информации об объектах ленивого перевода вы можете обратиться к документации Интернационализация.

Полезные утилитарные функции.

Because some string operations come up again and again, Django ships with a few useful functions that should make working with string and bytestring objects a bit easier.

Функции преобразования

The django.utils.encoding module contains a few functions that are handy for converting back and forth between strings and bytestrings.

  • smart_str(s, encoding='utf-8', strings_only=False, errors='strict') converts its input to a string. The encoding parameter specifies the input encoding. (For example, Django uses this internally when processing form input data, which might not be UTF-8 encoded.) The strings_only parameter, if set to True, will result in Python numbers, booleans and None not being converted to a string (they keep their original types). The errors parameter takes any of the values that are accepted by Python’s str() function for its error handling.
  • force_str(s, encoding='utf-8', strings_only=False, errors='strict') is identical to smart_str() in almost all cases. The difference is when the first argument is a lazy translation instance. While smart_str() preserves lazy translations, force_str() forces those objects to a string (causing the translation to occur). Normally, you’ll want to use smart_str(). However, force_str() is useful in template tags and filters that absolutely must have a string to work with, not just something that can be converted to a string.
  • smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict') is essentially the opposite of smart_str(). It forces the first argument to a bytestring. The strings_only parameter has the same behavior as for smart_str() and force_str(). This is slightly different semantics from Python’s builtin str() function, but the difference is needed in a few places within Django’s internals.

Normally, you’ll only need to use force_str(). Call it as early as possible on any input data that might be either a string or a bytestring, and from then on, you can treat the result as always being a string.

Обработка URI и IRI

Web frameworks have to deal with URLs (which are a type of IRI). One requirement of URLs is that they are encoded using only ASCII characters. 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. Use these functions for quoting and converting an IRI to a URI:

These two groups of functions have slightly different purposes, and it’s important to keep them straight. Normally, you would use quote() on the individual portions of the IRI or URI path so that any reserved characters such as „&“ or „%“ are correctly encoded. Then, you apply iri_to_uri() to the full IRI and it converts any non-ASCII characters to the correct encoded values.

Примечание

Формально, не корректно говорить, что функция iri_to_uri() реализует полный алгоритм из спецификации IRI. Она (пока) не реализует часть алгоритма для кодирования международных доменных имен.

Функция iri_to_uri() не будет изменять ASCII символы, которые в противном случае допускаются в 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'

If you look carefully, you can see that the portion that was generated by quote() in the second example was not double-quoted when passed to iri_to_uri(). This is a very important and useful feature. 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.

Similarly, Django provides django.utils.encoding.uri_to_iri() which implements the conversion from URI to IRI as per RFC 3987#section-3.2.

Примеры:

>>> 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'

In the first example, the UTF-8 characters are unquoted. In the second, the percent-encodings remain unchanged because they lie outside the valid UTF-8 range or represent a reserved character.

Функции 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)

Поэтому вы можете безопасно вызывать функцию множество раз для одного и того же IRI/URI без риска получить проблему двойного экранирования.

Модели

Because all strings are returned from the database as str objects, model fields that are character based (CharField, TextField, URLField, etc.) will contain Unicode values when Django retrieves data from the database. This is always the case, even if the data could fit into an ASCII bytestring.

You can pass in bytestrings when creating a model or populating a field, and Django will convert it to strings when it needs to.

Будьте осторожны с get_absolute_url()

URLs can only contain ASCII characters. If you’re constructing a URL from pieces of data that might be non-ASCII, be careful to encode the results in a way that is suitable for a URL. The reverse() function handles this for you automatically.

If you’re constructing a URL manually (i.e., not using the reverse() function), you’ll need to take care of the encoding yourself. In this case, use the iri_to_uri() and quote() functions that were documented above. For example:

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 это что то подобное «Jack visited Paris & Orléans». (По факту, вызов iri_to_uri() не строго необходим в примере выше, потому что все не-ASCII символы были бы удалены при экранировании в первой строке.)

Шаблоны

Use strings when creating templates manually:

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

But the common case is to read templates from the filesystem. If your template files are not stored with a UTF-8 encoding, adjust the TEMPLATES setting. The built-in django backend provides the 'file_charset' option to change the encoding used to read files from disk.

Параметр DEFAULT_CHARSET управляет кодировкой сгенерированных шаблонов. Он установлен в UTF-8 по умолчанию.

Шаблонные теги и фильтры

Несколько подсказок, которые следует помнить когда вы пишите свои теги и фильтры:

  • Always return strings from a template tag’s render() method and from template filters.
  • Use force_str() in preference to smart_str() in these places. Tag rendering and filter calls occur as the template is being rendered, so there is no advantage to postponing the conversion of lazy translation objects into strings. It’s easier to work solely with strings at that point.

Файлы

Если вы позволяете пользователям загружать файлы, убедитесь, что окружение настроено для работы с названиями файлов, которые содержат не ASCII символы. Иначе вы получите ошибку UnicodeEncodeError при сохранении таких файлов.

Поддержка файловой системой UTF-8 в названиях файлов зависит от окружения. Проверьте текущие настройки в консоли Python, выполнив:

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 используется как предполагающаяся кодировка для данных формы. Если вам нужно изменить это для части форм, вы можете установить атрибут encoding для экземпляра HttpRequest. Например:

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 не декодирует данные загруженных файлов, потому что их данные обычно обрабатывают как набор байтов, а не как строки. Любое автоматическое декодирование здесь могло бы изменить значение потока байт.