Данные Unicode

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

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

Все бекенды баз данных в Django автоматически конвертируют Unicode строки в соответствующую кодировку для передачи в базу данных. Они также автоматически конвертируют строки, полученные из базы данных в строки Python Unicode. Вам даже не нужно говорить Django, какую кодировку использует ваша база данных: это прозрачно обрабатывается.

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

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

Всякий раз когда вы используете строки в Django – к примеру, в поиске по базе данных, рендеринге шаблонов или где-нибудь еще – вы имеете два варианта для кодирования таких строк. Вы можете использовать строки Unicode или же вы можете использовать обыкновенные строки (иногда называющиеся “байтовыми строками”), которые кодируются с использованием UTF-8.

В Python 3 логика была изменена, теперь обыкновенные строки являются Unicode, и когда вы хотите создать байтовую строку, вы должны использовать строку начинающуюся с ‘b’. Как мы сами делаем в Django, начиная с версии 1.5, мы рекомендуем и вам - импортировать unicode_literals из библиотеки __future__ в вашем коде. И тогда, когда вы захотите специально создать байтовую строку, добавьте префикс ‘b’.

Наследие Python 2:

my_string = "This is a bytestring"
my_unicode = u"This is an Unicode string"

Python 2 с литералами Unicode или Python 3:

from __future__ import unicode_literals

my_string = b"This is a bytestring"
my_unicode = "This is an Unicode string"

Смотрите также Портирование на Python 3.

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

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

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

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

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

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

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

Немного в стороне от строк Unicode и байтовых строк, есть еще третий тип строковых объектов, с которыми можно столкнуться, используя Django. Особенности интернационализации фреймворка вводят понятие “ленивого перевода” – это строка которая отмечена как переведенная, но чей фактический перевод не определен пока объект используется в строке. Эта особенность полезна в случаях, когда перевод локали не известен пока строка не будет использована, даже при том что строка могла быть первоначально создана когда код был первый раз импортирован.

Обычно, вы не должны беспокоиться о ленивых переводах. Только знайте, что если вы проверяете объект и он утверждает, что он django.utils.functional.__proxy__ - это ленивый перевод. Вызов unicode() с ленивым переводом в качестве аргумента сгенерирует строку Unicode в текущей локали.

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

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

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

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

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

  • smart_text(s, encoding='utf-8', strings_only=False, errors='strict') преобразовывает входную строку в Unicode. Параметр encoding устанавливает входную кодировку. (Так Django использует это внутри при обработке форм входных данных, которые могут быть закодированы не в UTF-8.) Если параметр strings_only установлен в True, то результат для чисел, булевых переменных и None не будет преобразован в строку (они сохранят свои начальные типы). Параметр errors может принимать любые значение, которые передаются в Python функцию unicode() для обработки ею ошибок.

    Если вы передаете в smart_text() объект, который имеет метод __unicode__, то этот метод будет использован для преобразования.

  • force_text(s, encoding='utf-8', strings_only=False, errors='strict') идентична smart_text() в большинстве случаев. Отличие лишь в том случае, когда первый аргумент является инстансом ленивого перевода. В то время как smart_text() хранит ленивые переводы, force_text() форсирует преобразование этих объектов в строки Unicode (в результате чего происходит перевод). Обычно вам будет нужно использовать 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.

Как правило, вам будет нужно использовать только smart_text(). Вызывайте это как можно раньше для любых входных данных, которые могут быть Unicode или байтовой строкой, и после этого вы можете работать с результатом, который всегда будет Unicode.

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

Веб фреймворки должны взаимодействовать с URL’ами (которые являются разновидностью IRI). Одно из требований URL’ов - это то, что они должны быть закодированы с использованием только ASCII символов. Тем не менее, в международном окружении, вы можете захотеть обрабатывать URL как IRI – если говорить очень просто, то URI могут содержать Unicode символы.Экранирование и преобразование IRI в URI может быть непростым, поэтому Django представляет некоторую помощь для этого.

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

Примечание

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

Функция iri_to_uri() не будет изменять ASCII символы, которые в противном случае допускаются в URL. Так, например, символ ‘%’ дополнительно не кодируется когда передается в iri_to_uri(). Это значит вы можете передавать полный URL в эту функцию и это не испортит строку запроса или что-либо подобное ей.

Пример поможет прояснить это:

>>> urlquote('Paris & Orléans')
'Paris%20%26%20Orl%C3%A9ans'
>>> iri_to_uri('/favorites/François/%s' % urlquote('Paris & Orléans'))
'/favorites/Fran%C3%A7ois/Paris%20%26%20Orl%C3%A9ans'

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

Также Django предоставляет функцию django.utils.encoding.uri_to_iri(), которая конвертирует URI в IRI, как этого требует спецификация RFC 3987#section-3.2. Она пытается декодировать возможные UTF-8 символы.

Примеры:

>>> uri_to_iri('/%E2%99%A5%E2%99%A5/?utf8=%E2%9C%93')
'/♥♥/?utf8=✓'
>>> uri_to_iri('%A9helloworld')
'%A9helloworld'

В первом примере 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)

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

Модели

По причине того, что все строки возвращаются из базы данных как Unicode строки, строковые поля модели (CharField, TextField, URLField и т.д.) будут содержать Unicode значения, когда Django достает данные из базы данных. Это происходит всегда , даже если данные содержат только байтовые строки ASCII.

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

Выбор между __str__() и __unicode__()

Примечание

Если вы используете Python 3, можете пропустить эту секцию, потому что вы всегда будете создавать __str__() вместо __unicode__(). Если вы хотите обеспечить совместимость с Python 2, вы можете декорировать ваш класс модели при помощи python_2_unicode_compatible().

Одно из последствий использования Unicode по умолчанию - это то что вы должны проявлять некоторую заботу при распечатке данных из модели.

В частности, вместо получения __str__() метода вашей модели, мы рекомендовали вам реализовать __unicode__() метод. В __unicode__() методе, вы можете довольно безопасно вернуть значения всех ваших полей без необходимости беспокойства о том будут ли они байтовыми строками или нет. (Путь Python работает, результат __str__() всегда байтовая строка, даже если вы нечаянно попробуете вернуть Unicode объект).

Вы можете все же создать метод __str__() в вашей модели, если хотите, конечно, но вам не нужно этого делать, пока вы не имеете достаточной причины. Базовый класс Model в Django автоматически предоставляет реализацию __str__(), которая вызывает __unicode__() и кодирует результат в UTF-8. Это значит, что обычно вам достаточно реализовать метод __unicode__() и дать Django принудительно преобразовать в байтовую строку когда это потребуется.

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

URL’ы могут содержать только ASCII символы. Если вы собираете URL из частей, которые могут содержать не-ASCII, будьте осторожны с кодированием результатов, способом подходящим для URL. Функция reverse() обрабатывает это для вас автоматически.

Если вы собираете URL вручную (то есть не используя функцию reverse() ), вам будет нужно заботиться о кодировании самостоятельно. В этом случае, используйте функции iri_to_uri() и urlquote() которые были описаны выше. Например:

from django.utils.encoding import iri_to_uri
from django.utils.http import urlquote

def get_absolute_url(self):
    url = '/person/%s/?x=0&y=0' % urlquote(self.location)
    return iri_to_uri(url)

Функция возвращает корректный кодированный URL, даже если self.location это что то подобное “Jack visited Paris & Orléans”. (По факту, вызов iri_to_uri() не строго необходим в примере выше, потому что все не-ASCII символы были бы удалены при экранировании в первой строке.)

API базы данных

Вы можете передавать или Unicode строку или UTF-8 байтовую строку в качестве аргументов в методы filter() и им подобные в API баз данных. Следующие два QuerySets одинаковы:

from __future__ import unicode_literals

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

Шаблоны

Вы можете использовать Unicode или байтовые строки когда создаете шаблон вручную.

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

Но в общем случае шаблоны читаются из файловой системы и это создает незначительную сложность: не все файловые системы хранят данные в UTF-8. Если ваши файлы с шаблонами хранятся не в UTF-8, установите параметр FILE_CHARSET в значение кодировки ваших файлов на диске. Когда Django читает файл шаблона, он будет конвертировать данные из указанной кодировки в Unicode.(FILE_CHARSET установлено в 'utf-8' по умолчанию.)

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

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

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

  • Всегда возвращайте Unicode строки из render() метода шаблонных тегов и из шаблонных фильтров.

  • Использование force_text() предпочтительнее smart_text() в этих местах. Вызовы рендеринга тега и фильтра происходят так же как для отрендереннного шаблона, поэтому нет выгоды в отложенном преобразовании объектов ленивого перевода в строки. Это легче для работы исключительно с Unicode строками в этот момент.

Файлы

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

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

import sys
sys.getfilesystemencoding()

Должно вывести “UTF-8”.

Переменная окружения LANG отвечает за кодировку по умолчанию в Unix. Обратитесь к документации вашей операционной системы и сервера, чтобы узнать о настройке этого значения.

При разработке вы можете указать настройку в ~.bashrc:

export LANG="en_US.UTF-8"

Email

Email фреймворк Django (в django.core.mail) прозрачно поддерживает Unicode. Вы можете использовать Unicode данные в теле сообщения и в любых заголовках. Однако, вы все еще обязаны соблюдать требования email спецификаций, так, например, email адрес должен использовать только ASCII символы.

Следующий пример кода демонстрирует, что все, исключая лишь email, адрес может быть не-ASCII:

from __future__ import unicode_literals
from django.core.mail import EmailMessage

subject = 'My visit to Sør-Trøndelag'
sender = 'Arnbjörg Ráðormsdóttir <arnbjorg@example.com>'
recipients = ['Fred <fred@example.com']
body = '...'
msg = EmailMessage(subject, body, sender, recipients)
msg.attach("Une pièce jointe.pdf", "%PDF-1.4.%...", mimetype="application/pdf")
msg.send()

Отправка формы

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