Как заставить Django slugify правильно работать со строками Unicode? - PullRequest
36 голосов
/ 31 марта 2009

Что можно сделать, чтобы фильтр 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 или нет?

Ответы [ 8 ]

92 голосов
/ 27 октября 2010

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

>>> 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))
23 голосов
/ 12 августа 2011

Команда веб-сайта Mozilla работает над реализацией: https://github.com/mozilla/unicode-slugify пример кода на http://davedash.com/2011/03/24/how-we-slug-at-mozilla/

15 голосов
/ 26 октября 2010

Кроме того, версия 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'[?,:!@#~`+=$%^&\\*()\[\]{}<>]','',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, и я неправильно использую юникод и / или китайский язык.

На данный момент обходной путь - избегать классов символов и делать замены на основе явно определенных наборов символов.

10 голосов
/ 23 марта 2016

С Джанго> = 1,9 , django.utils.text.slugify имеет параметр allow_unicode:

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

Если вы используете Django <= 1.8 (что не следует делать с апреля 2018 года), вы можете <a href="https://github.com/django/django/blob/1.9/django/utils/text.py#L413-L427" rel="nofollow noreferrer"> забрать код из Django 1.9 .

10 голосов
/ 31 марта 2009

Боюсь, определение слизняка в django означает ascii, хотя в django docs это явно не указано. Это источник 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-адресов действительно утверждает, что символы, отличные от us-ascii (или, более конкретно, все, кроме буквенно-цифровых символов и $ -_. +! * '()), Должны кодироваться с использованием % шестнадцатеричное обозначение. Если вы посмотрите на фактический необработанный GET-запрос, который отправляет ваш браузер (скажем, с помощью Firebug), вы увидите, что китайские символы на самом деле кодируются перед отправкой ... браузер просто выглядит красиво на дисплее. Я подозреваю, что именно поэтому slugify настаивает только на ascii, fwiw.

7 голосов
/ 31 мая 2011

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

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

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

4 голосов
/ 06 января 2011

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

http://trac.django -fr.org / браузер / сайт / багажник / djangofr / ссылки / slughifi.py

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

Таким образом, вместо «Ą» вы получаете «A», вместо «Ł» => «L» и так далее.

1 голос
/ 07 февраля 2019

Я заинтересован в разрешении только символов ASCII в слаге, поэтому я попытался сравнить некоторые доступные инструменты для той же строки:

  • Slugify Unicode :

    In [5]: %timeit slugify('Παίζω τρέχω %^&*@# και γ%^(λώ la fd/o', only_ascii=True)
    37.8 µs ± 86.7 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    
    'paizo-trekho-kai-glo-la-fdo'
    
  • Джанго Ууслуг :

    In [3]: %timeit slugify('Παίζω τρέχω %^&*@# και γ%^(λώ la fd/o')
    35.3 µs ± 303 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    
    'paizo-trekho-kai-g-lo-la-fd-o'
    
  • Удивительный Slugify :

    In [3]: %timeit slugify('Παίζω τρέχω %^&*@# και γ%^(λώ la fd/o')
    47.1 µs ± 1.94 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    
    'Paizo-trekho-kai-g-lo-la-fd-o'
    
  • Python Slugify :

    In [3]: %timeit slugify('Παίζω τρέχω %^&*@# και γ%^(λώ la fd/o')
    24.6 µs ± 122 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    
    'paizo-trekho-kai-g-lo-la-fd-o'
    
  • django.utils.text.slugify с Unidecode :

    In [15]: %timeit slugify(unidecode('Παίζω τρέχω %^&*@# και γ%^(λώ la fd/o'))
    36.5 µs ± 89.7 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    
    'paizo-trekho-kai-glo-la-fdo'
    
...