Данные Юникода

Django поддерживает данные Unicode везде.

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

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

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

  • Пользователи MySQL, обратитесь к разделу MySQL manual для получения подробной информации о том, как установить или изменить кодировку набора символов базы данных.
  • Пользователи PostgreSQL, обратитесь к PostgreSQL manual (раздел 22.3.2 в PostgreSQL 9) за подробностями о создании баз данных с правильной кодировкой.
  • Пользователи Oracle, обратитесь к разделу Oracle manual для получения подробной информации о том, как установить (section 2) или изменить (section 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() с ленивым переводом в качестве аргумента сгенерирует строку в текущей локали.

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

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

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

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

Модуль django.utils.encoding содержит несколько функций, которые удобно использовать для преобразования строк и байтстрингов в обратном направлении.

  • smart_str(s, encoding='utf-8', strings_only=False, errors='strict') преобразует входные данные в строку. Параметр encoding задает кодировку входных данных. (Например, Django использует этот параметр при обработке входных данных формы, которые могут быть не в кодировке UTF-8). Параметр strings_only, если он имеет значение True, приведет к тому, что числа, булевы и None из Python не будут преобразованы в строку (они сохранят свои исходные типы). Параметр errors принимает любое из значений, которые принимаются функцией Python str() для обработки ошибок.
  • force_str(s, encoding='utf-8', strings_only=False, errors='strict') идентичен smart_str() почти во всех случаях. Разница заключается в том, что первым аргументом является экземпляр lazy translation. В то время как 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. Однако в международной среде вам может понадобиться построить URL из IRI - очень условно говоря, URI, который может содержать символы Unicode. Используйте эти функции для цитирования и преобразования IRI в URI:

Эти две группы функций имеют немного разное назначение, и важно соблюдать их последовательность. Обычно вы используете quote() для отдельных частей IRI или пути URI, чтобы все зарезервированные символы, такие как „&“ или „%“, были правильно закодированы. Затем вы применяете iri_to_uri() к полному IRI, и он преобразует все не-ASCII символы в правильные кодированные значения.

Примечание

Технически, неправильно говорить, что 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'

Если вы посмотрите внимательно, то увидите, что часть, которая была сгенерирована quote() во втором примере, не была заключена в двойные кавычки при передаче в iri_to_uri(). Это очень важная и полезная особенность. Это означает, что вы можете сконструировать свой IRI, не заботясь о том, содержит ли он символы, отличные от ASCII, а затем, в самом конце, вызвать iri_to_uri() для результата.

Аналогично, Django предоставляет django.utils.encoding.uri_to_iri(), который реализует преобразование из URI в IRI согласно 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'

В первом примере символы 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 и т.д.), будут содержать значения Unicode, когда Django получит данные из базы данных. Это всегда так, даже если данные могут поместиться в ASCII байтстринг.

Вы можете передавать байтовые строки при создании модели или заполнении поля, и Django будет преобразовывать их в строки, когда это потребуется.

Забота в get_absolute_url()

URL-адреса могут содержать только символы ASCII. Если вы создаете URL из фрагментов данных, которые могут быть не ASCII, будьте внимательны, чтобы закодировать результаты таким образом, чтобы они подходили для 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, измените настройку TEMPLATES. Встроенный бэкенд django предоставляет опцию „file_charset“` для изменения кодировки, используемой для чтения файлов с диска.

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

Теги и фильтры шаблонов

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

  • Всегда возвращайте строки из метода render() тега шаблона и из фильтров шаблона.
  • Используйте force_str() в этих местах вместо smart_str(). Отрисовка тегов и вызовы фильтров происходят во время отрисовки шаблона, поэтому нет никакого преимущества в откладывании преобразования объектов ленивого перевода в строки. В этот момент проще работать исключительно со строками.

Файлы

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

Поддержка файловой системой имен файлов в формате 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.POST возвращают данные именно в том виде, в котором они были переданы клиентом.

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

Вернуться на верх