Далее следует более-менее подробное описание проблемы и фикса. Если о проблеме вы в курсе и вас интересует только решение — смело листайте в конец поста.
TL/DR;
В django есть функция для генерации т.н. slug’ов. Перевести одним словом этот термин достаточно сложно, но, кто имеет хоть какой-то опыт работы с django должен знать что это такое. Если раскрыть суть — часть ЧПУ (человеко-понятного URL), которая относится к заголовку сущности. Например, slug для этого поста в терминах django было бы таким: «django-fix-non-latin-slugify» (если я не поменял URL записи после написания этих строк).
Исторически сложилось так, что slug может быть сгенерирован средствами django несколькими способами: с помощью JS в админке (через prepopulated_fields), либо, в самом python-коде с использованием функции slugify (можно ещё в шаблоне с помощью стандартного фильтра slugify, но это то же самое по сути, в отличие от генерации на frontend’е). А всегда, когда не соблюдается принцип DRY — есть вероятность получить разную логику, где она, по идее, должна быть одинакова. Так и вышло, frontend и backend генерируют slug по разному, и если вы пользуетесь последним способом, то это может быть критично — при генерации slug’а на backend’е все не латинские символы будут выкинуты из результата!
Досадная ситуация. Ещё более досадно то, что пофиксить не так уж и сложно, ведь python идёт «в комплекте с батарейками», готовые решения есть — использовать библиотеку unidecode. Но, в django есть политика использовать как можно меньше жёстких внешних зависимостей, поэтому, всё что они сильно захотят использовать у себя «под капотом» — включают в код самого проекта. Так, например, было с библиотекой simplejson, можно найти ещё несколько сторонних пакетов которые живут внутри django. Но, к сожалению (или к счастью), unidecode весит достаточно много и разработчики django не хотят включать его код в свой проект именно по этой причине.
Длинный путь
Ну что ж, благо, это open source, всё в наших руках. Форкать django для правки такой мелочи мы не будем, а воспользуемся monkey-патчингом. Может, это и не очень хорошая техника, но, как мне кажется, это именно тот случай, когда её можно применить. Листинг кода будет совсем короткий:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | from django import conf from django.utils import encoding try: # Django 1.5 have some permutations. from django.utils import text as src_pkg from django.utils.text import slugify as dj_slugify except ImportError: from django.template import defaultfilters as src_pkg from django.template.defaultfilters import slugify as dj_slugify import unidecode def slugify(value): value = encoding.smart_unicode(value) return dj_slugify(encoding.smart_unicode(unidecode.unidecode(value))) if getattr(conf.settings, 'PATCH_SLUGIFY', True): if not getattr(src_pkg.slugify, 'patched', False): src_pkg.slugify = slugify src_pkg.slugify.patched = True |
Этот код позволяет как заменить исходную версию slugify, так и просто использовать свою. Если не хотите патчить — поставьте в настройках флаг:
PATCH_SLUGIFY = False
Короткий путь
Если вы не хотите копи-пастить этот костыль, то можете просто установить его с помощью PIP:
1 | pip install -e git+http://github.com/simplylizz/django-slugify.git#egg=django-slugify==1.0.1 |
И добавить в INSTALLED_APPS приложение «slugify».
Изначально код был протестирован на django 1.4, после выхода 1.5 был подправлен под неё. Есть небольшой шанс, что поддержка ветки 1.4 поломалась — я не проверял. Если у кого будут проблемы — дайте знать.
Возможно, скоро выложу этот пакет на PyPi — я не сторонник захламления этой репы, но проблеме уже года 3 как, судя по соответствующему тикету в django.
Работает шикарно, спасибо.