2009-03-31 8 views
33

Cosa posso fare per evitare che il filtro slugify rimuova caratteri alfanumerici non ASCII? (Sto usando Django 1.0.2)Come fare in modo che Django slugify funzioni correttamente con le stringhe Unicode?

cnprog.com ha caratteri cinesi in questione URL, quindi ho guardato nel loro codice. Essi non stanno usando slugify nei modelli, invece stanno chiamando questo metodo in Question modello per ottenere permalink

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

Sono slugifying gli URL o no?

risposta

85

Esiste un pacchetto python chiamato unidecode che ho adottato per l'askbot Q & Un forum, funziona bene per gli alfabeti latino-based e addirittura sembra ragionevole per il greco:

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

Si fa qualcosa strano con lingue asiatiche:

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

Ha senso?

In askbot calcoliamo lumache in questo modo:

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

Questa è davvero una piccola lib meravigliosa. Questa risposta dovrebbe essere accettata. –

+0

+1, bella lib! Facile da usare. – laike9m

+0

è la versione pinyin del cinese, super utile! Soprattutto se hai bisogno del pinyin. –

10

Ho paura che la definizione di django di slug significhi ascii, sebbene i documenti di django non lo dichiarino esplicitamente. Questa è la fonte dei defaultfilters per la slugify ... si può vedere che i valori vengono convertiti in ascii, con l'opzione 'ignorare' in caso di errori:

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

Sulla base di questo, mi piacerebbe Immagino che cnprog.com non stia usando una funzione ufficiale slugify. Potresti voler adattare lo snippet di Django sopra se vuoi un comportamento diverso.

Detto questo, tuttavia, RFC per URL indica che i caratteri non-us-ascii (o, più specificamente, qualsiasi cosa diversa da alfanumerici e $ -_. +! * '()) Devono essere codificati usando la notazione esadecimale%. Se si guarda alla reale richiesta GET grezza che il tuo browser invia (ad esempio, usando Firebug), vedrai che i caratteri cinesi sono in effetti codificati prima di essere inviati ... il browser lo fa apparire piuttosto carino sul display. Sospetto che questo sia il motivo per cui Slugify insiste solo su ascii, per esempio.

+1

Nota anche la risposta di OpenSEO relativa a unicode-slugify di Mozilla. – jnns

15

Inoltre, la versione Django di slugify non utilizza il flag re.UNICODE, quindi non tenterebbe nemmeno di comprendere il significato di \w\s in quanto riguarda caratteri non ascii.

versione Questa usanza sta lavorando bene per me:

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 l'ultima sostituzione regex. Si tratta di una soluzione a un problema con l'espressione più robusto r'\W', che sembra sia nudo fuori alcuni caratteri non ASCII o non correttamente ricodificarli, come illustrato nella seguente sessione di interprete 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 

Sono non so quale sia il problema sopra, ma suppongo che derivi da "whatever is classified as alphanumeric in the Unicode character properties database" e da come è implementato. Ho sentito che python 3.x ha un'alta priorità sulla migliore gestione unicode, quindi potrebbe essere già risolto. O forse è corretto il comportamento di Python, e sto usando male unicode e/o la lingua cinese.

Per ora, una soluzione è evitare le classi di caratteri e apportare sostituzioni basate su set di caratteri esplicitamente definiti.

+1

+1 per tutto ciò che funziona .. –

4

Questo è quello che io uso:

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

SlugHiFi è un wrapper per slugify regolare, con una differenza che sostituisce caratteri nazionali con le loro controparti in alfabeto inglese.

Quindi anziché "Ą" si ottiene "A", anziché "Ł" => "L" e così via.

+4

Anche se lo chiamerei un LoFi, non un hifi HiFi;)! –

8

Django 1.9 introdotto allow_unicode parametro django.utils.text.slugify.

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

Se si utilizza Django < = 1.8, è possibile 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) 

che è praticamente the solution suggested by Jarret Hardie.