Подтвердить что ты не робот

Как сделать Django slugify правильной работой с строками Unicode?

Что можно сделать, чтобы фильтр slugify не удалял буквенные символы без ASCII? (Я использую Django 1.0.2)

cnprog.com содержит китайские символы в URL-адресах, поэтому я просмотрел их код. Они не используют slugify в шаблонах, вместо этого они вызывают этот метод в модели Question для получения постоянных ссылок

def get_absolute_url(self):
    return '%s%s' % (reverse('question', args=[self.id]), self.title)

Заменяют ли они URL-адреса или нет?

4b9b3361

Ответ 1

Существует пакет python под названием unidecode, который я принял для вопроса Q & Forum, он хорошо работает для латинского основанные на алфавитах и ​​даже выглядящие разумно для греческого:

>>> import unidecode
>>> from unidecode import unidecode
>>> unidecode(u'διακριτικός')
'diakritikos'

Он делает что-то странное с азиатскими языками:

>>> unidecode(u'影師嗎')
'Ying Shi Ma '
>>> 

Это имеет смысл?

В askbot мы вычисляем такие пули:

from unidecode import unidecode
from django.template import defaultfilters
slug = defaultfilters.slugify(unidecode(input_text))

Ответ 3

Кроме того, версия slugify Django не использует флаг re.UNICODE, поэтому даже не попытается понять значение \w\s, поскольку оно относится к символам без ascii.

Эта специальная версия работает хорошо для меня:

def u_slugify(txt):
        """A custom version of slugify that retains non-ascii characters. The purpose of this
        function in the application is to make URLs more readable in a browser, so there are 
        some added heuristics to retain as much of the title meaning as possible while 
        excluding characters that are troublesome to read in URLs. For example, question marks 
        will be seen in the browser URL as %3F and are thereful unreadable. Although non-ascii
        characters will also be hex-encoded in the raw URL, most browsers will display them
        as human-readable glyphs in the address bar -- those should be kept in the slug."""
        txt = txt.strip() # remove trailing whitespace
        txt = re.sub('\s*-\s*','-', txt, re.UNICODE) # remove spaces before and after dashes
        txt = re.sub('[\s/]', '_', txt, re.UNICODE) # replace remaining spaces with underscores
        txt = re.sub('(\d):(\d)', r'\1-\2', txt, re.UNICODE) # replace colons between numbers with dashes
        txt = re.sub('"', "'", txt, re.UNICODE) # replace double quotes with single quotes
        txt = re.sub(r'[?,:[email protected]#~`+=$%^&\\*()\[\]{}<>]','',txt, re.UNICODE) # remove some characters altogether
        return txt

Обратите внимание на последнюю замену регулярных выражений. Это обходное решение проблемы с более надежным выражением r'\W', которое, как представляется, либо лишает некоторые символы не-ascii, либо неправильно их перекодирует, как показано в следующем сеансе интерпретатора python:

Python 2.5.1 (r251:54863, Jun 17 2009, 20:37:34) 
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import re
>>> # Paste in a non-ascii string (simplified Chinese), taken from http://globallives.org/wiki/152/
>>> str = '您認識對全球社區感興趣的中國攝影師嗎'
>>> str
'\xe6\x82\xa8\xe8\xaa\x8d\xe8\xad\x98\xe5\xb0\x8d\xe5\x85\xa8\xe7\x90\x83\xe7\xa4\xbe\xe5\x8d\x80\xe6\x84\x9f\xe8\x88\x88\xe8\xb6\xa3\xe7\x9a\x84\xe4\xb8\xad\xe5\x9c\x8b\xe6\x94\x9d\xe5\xbd\xb1\xe5\xb8\xab\xe5\x97\x8e'
>>> print str
您認識對全球社區感興趣的中國攝影師嗎
>>> # Substitute all non-word characters with X
>>> re_str = re.sub('\W', 'X', str, re.UNICODE)
>>> re_str
'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\xa3\xe7\x9a\x84\xe4\xb8\xad\xe5\x9c\x8b\xe6\x94\x9d\xe5\xbd\xb1\xe5\xb8\xab\xe5\x97\x8e'
>>> print re_str
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX?的中國攝影師嗎
>>> # Notice above that it retained the last 7 glyphs, ostensibly because they are word characters
>>> # And where did that question mark come from?
>>> 
>>> 
>>> # Now do the same with only the last three glyphs of the string
>>> str = '影師嗎'
>>> print str
影師嗎
>>> str
'\xe5\xbd\xb1\xe5\xb8\xab\xe5\x97\x8e'
>>> re.sub('\W','X',str,re.U)
'XXXXXXXXX'
>>> re.sub('\W','X',str)
'XXXXXXXXX'
>>> # Huh, now it seems to think those same characters are NOT word characters

Я не уверен, что проблема выше, но я предполагаю, что это связано с " тем, что классифицировано как буквенно-цифровое в базе данных свойств символов Unicode," и как это реализовано. Я слышал, что python 3.x имеет высокий приоритет в улучшении обработки юникода, поэтому это может быть исправлено уже. Или, может быть, это правильное поведение python, и я злоупотребляю unicode и/или китайским языком.

В настоящее время обход - это избегать классов символов и делать замены на основе явно определенных наборов символов.

Ответ 4

Я боюсь, что определение django slug означает ascii, хотя в документах django явно не указано это. Это источник фильтров defaultfilters для slugify... вы можете видеть, что значения преобразуются в ascii с опцией "ignore" в случае ошибок:

import unicodedata
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
return mark_safe(re.sub('[-\s]+', '-', value))

Исходя из этого, я предполагаю, что cnprog.com не использует официальную функцию slugify. Возможно, вы захотите адаптировать фрагмент django выше, если хотите другое поведение.

Сказав, что, однако, в RFC для URL-адресов указывается, что символы non-us-ascii (или, более конкретно, ничего, кроме буквенно-цифровых символов и $-_. +! * '()), должны быть закодированы с использованием % hex. Если вы посмотрите на фактический необработанный запрос GET, который посылает ваш браузер (скажем, с помощью Firebug), вы увидите, что китайские символы на самом деле закодированы перед отправкой... браузер просто заставляет его выглядеть красиво на дисплее. Я подозреваю, что именно поэтому slugify настаивает только на ascii, fwiw.

Ответ 5

Django 1.9 представил параметр allow_unicode для django.utils.text.slugify.

>>> slugify("你好 World", allow_unicode=True)
"你好-world"

Если вы используете Django <= 1.8, вы можете выбрать код из Django 1.9:

import re
import unicodedata

from django.utils import six
from django.utils.encoding import force_text
from django.utils.functional import allow_lazy
from django.utils.safestring import SafeText, mark_safe

def slugify_unicode(value):
    value = force_text(value)
    value = unicodedata.normalize('NFKC', value)
    value = re.sub('[^\w\s-]', '', value, flags=re.U).strip().lower()
    return mark_safe(re.sub('[-\s]+', '-', value, flags=re.U))
slugify_unicode = allow_lazy(slugify_unicode, six.text_type, SafeText)

Это в значительной степени решение, предложенное Jarret Hardie.

Ответ 6

Вы можете посмотреть: https://github.com/un33k/django-uuslug

Он позаботится обо всех "U" для вас. U в уникальном и U в Юникоде.

Это сделает вашу работу бесполезной.

Ответ 7

Это то, что я использую:

http://trac.django-fr.org/browser/site/trunk/djangofr/links/slughifi.py

SlugHiFi - это обертка для регулярного slugify, с той разницей, что она заменяет национальные символы своими английскими аналогами алфавита.

Итак, вместо "Ą" вы получаете "A", вместо "Ł" = > "L" и т.д.