2009-03-31 12 views
33

Was kann ich tun, um zu verhindern, dass der Filter slugify nicht-ASCII-alphanumerische Zeichen entfernt? (Ich benutze Django 1.0.2)Wie kann Django Slugify mit Unicode-Zeichenfolgen richtig funktionieren?

cnprog.com hat chinesische Zeichen in Frage URLs, so sah ich in ihrem Code. Sie sind nicht mit slugify in Vorlagen, statt sie diese Methode in Question Modell sind Aufruf Permalinks zu bekommen

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

Sind sie slugifying die URLs oder nicht?

Antwort

85

Es ist ein Python-Paket namens unidecode, die ich für die askbot Q angenommen haben & Ein Forum, es funktioniert gut für den lateinischen basierten Alphabeten und sieht auch angemessen für griechisch:

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

Es tut etwas komisch mit asiatischen Sprachen:

Macht das Sinn?

In askbot berechnen wir Schnecken wie so:

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

Dies ist wirklich eine wunderbare kleine lib. Diese Antwort sollte die akzeptierte sein. –

+0

+1, nette lib! Einfach zu bedienen. – laike9m

+0

es ist die pinyin-version der chinesischen, super nützlich! Vor allem, wenn Sie das Pinyin brauchen. –

10

Ich fürchte, Djangos Definition von Slug bedeutet Ascii, obwohl die Django-Dokumente dies nicht explizit angeben. Dies ist die Quelle des defaultfilters für die slugify ist ... Sie sehen, dass die Werte in ASCII umgewandelt werden, mit der ‚ignore‘ Option für den Fall von Fehlern:

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

Auf dieser Basis würde ich denke, dass cnprog.com keine offizielle slugify Funktion verwendet. Vielleicht möchten Sie das Django-Snippet oben anpassen, wenn Sie ein anderes Verhalten wünschen.

Allerdings, der RFC für URLs besagt, dass nicht-us-ASCII-Zeichen (oder, genauer gesagt, alles andere als die alphanumerischen und $ -_. +! * '()) Mit codiert werden sollte die% Hex-Schreibweise. Wenn Sie sich die tatsächliche Raw-GET-Anfrage ansehen, die Ihr Browser sendet (z. B. mit Firebug), werden Sie feststellen, dass die chinesischen Zeichen tatsächlich codiert sind, bevor sie gesendet werden ... der Browser sorgt dafür, dass er im Display schön aussieht. Ich vermute, das ist der Grund, warum Slugify nur auf Ascii besteht, fwiw.

+1

Beachten Sie auch OpenSEOs Antwort in Bezug auf Mozillas Unicode-Slugify. – jnns

15

Auch die Django-Version von Slugify verwendet nicht das RE.UNICODE-Flag, also würde es nicht einmal versuchen, die Bedeutung von \w\s zu verstehen, da es nicht-ASCII-Zeichen betrifft.

Diese kundenspezifische Version funktioniert gut für mich:

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 

Hinweis der letzte regex Substitution. Dies ist eine Abhilfe für ein Problem mit dem robusteren Ausdruck r'\W', die entweder einig Nicht-ASCII-Zeichen Streifen aus zu scheint oder falsch wieder kodieren sie, wie in der folgenden Python-Interpreter-Sitzung dargestellt:

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 

Ich bin unsicher, was das Problem oben ist, aber ich vermute, dass es von "" stammt, und wie das implementiert ist. Ich habe gehört, dass Python 3.x eine höhere Priorität bei der besseren Unicode-Behandlung hat, so dass dies bereits behoben sein könnte. Oder vielleicht ist es korrektes Python-Verhalten, und ich missbrauche Unicode und/oder die chinesische Sprache.

Vorerst ist es ein Workaround, Zeichenklassen zu vermeiden und Ersetzungen basierend auf explizit definierten Zeichensätzen vorzunehmen.

+1

+1 für alle diese Arbeit .. –

4

Dies ist, was ich benutze:

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

SlugHiFi ein Wrapper für regelmäßigen slugify ist, mit einem Unterschied, dass es nationale Zeichen ersetzt mit ihren Pendants im englischen Alphabet.

Also anstelle von "Ą" erhalten Sie "A" anstelle von "£" => "L", und so weiter.

+4

Obwohl ich das ein LoFi nennen würde, nicht HiFi Slug;)! –

8

1,9 Django allow_unicode Parameter django.utils.text.slugify eingeführt.

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

Wenn Sie Django verwenden < = 1.8 können Sie 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) 

die so ziemlich the solution suggested by Jarret Hardie ist.