2009-03-31 17 views
33

¿Qué puedo hacer para evitar que el filtro slugify elimine los caracteres alfanuméricos no ASCII? (Estoy usando Django 1.0.2)¿Cómo hacer que Django slugify funcione correctamente con cadenas Unicode?

cnprog.com tiene caracteres chinos en las URL de las preguntas, por lo que miré en su código. Ellos no están utilizando slugify en las plantillas, en lugar que están llamando a este método en Question modelo para obtener enlaces permanentes

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

¿Son slugifying las direcciones URL o no?

Respuesta

85

Hay un paquete python llamado unidecode que he adoptado para la askbot Q & Un foro, que funciona bien para los alfabetos basados ​​en el latín e incluso parece razonable para griego:

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

Se hace algo raro con idiomas asiáticos:

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

¿Tiene esto sentido?

En askbot calculamos babosas de este modo:

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

Esto es realmente una maravillosa pequeña lib. Esta respuesta debe ser la aceptada. –

+0

+1, ¡buena lib! Fácil de usar. – laike9m

+0

es la versión pinyin del chino, ¡súper útil! Especialmente si necesitas el pinyin. –

10

Me temo que la definición de django de slug significa ascii, aunque los documentos de django no lo indican explícitamente. Esta es la fuente de los defaultfilters para el slugify ... se puede ver que los valores se convierten en formato ASCII, con la opción 'ignorar' en caso de errores:

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)) 

Con base en esto, me gustaría Supongo que cnprog.com no está utilizando una función oficial slugify. Es posible que desee adaptar el fragmento de django anterior si desea un comportamiento diferente.

Dicho esto, sin embargo, el RFC para URL indica que los caracteres no estadounidenses (o, más específicamente, cualquier cosa que no sean los alfanuméricos y $ -_. +! * '()) Deberían codificarse usando la notación% hexadecimal Si nos fijamos en la solicitud GET en bruto real que envía su navegador (digamos, usando Firebug), verá que los caracteres chinos están de hecho codificados antes de ser enviados ... el navegador simplemente lo hace lucir bonito en la pantalla. Sospecho que esta es la razón por la cual slugify solo insiste en ascii, fwiw.

+1

También tenga en cuenta la respuesta de OpenSEO con respecto a unicode-slugify de Mozilla. – jnns

15

Además, la versión de Django de slugify no utiliza el indicador re.UNICODE, por lo que ni siquiera intentaría comprender el significado de \w\s en lo que respecta a caracteres que no son ascii.

versión Esta costumbre está funcionando bien para mí:

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 

Nota la última sustitución de expresiones regulares. Se trata de una solución a un problema con la más robusta expresión r'\W', que parece bien despojar a cabo algunos caracteres no ASCII o incorrectamente volver a codificarlos, como se ilustra en la siguiente sesión de Python intérprete:

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 

soy No estoy seguro de cuál es el problema anterior, pero supongo que proviene de "whatever is classified as alphanumeric in the Unicode character properties database" y de cómo se implementa. He oído que Python 3.x tiene una alta prioridad en el mejor manejo de Unicode, por lo que esto ya puede solucionarse. O tal vez es el comportamiento correcto de Python, y estoy haciendo un uso indebido de Unicode y/o del idioma chino.

Por el momento, una solución temporal es evitar clases de caracteres, y hacer sustituciones basadas en conjuntos de caracteres explícitamente definidos.

+1

+1 por todo lo que funciona ... –

4

Esto es lo que yo uso:

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

SlugHiFi es un contenedor de slugify regular, con una diferencia que reemplaza caracteres nacionales con sus contrapartes del alfabeto inglés.

De modo que en lugar de "Ą" obtiene "A", en lugar de "Ł" => "L", y así sucesivamente.

+4

Aunque yo llamaría a eso una LoFi, no una babosa HiFi;)! –

8

Django 1.9 introducido allow_unicode parámetro para django.utils.text.slugify.

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

Si utiliza Django < = 1.8, puede pick up the code from 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) 

que es prácticamente the solution suggested by Jarret Hardie.

Cuestiones relacionadas