Commit 4b1bc750875f2de02f0c6702ae925e5b9f0c492a

Authored by Jailson Dias
1 parent f34a20a8

Biblioteca para gerar o slug automaticamente já no model

autoslug/__init__.py 0 → 100644
@@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
  1 +# coding: utf-8
  2 +#
  3 +# Copyright (c) 2008—2015 Andy Mikhailenko
  4 +#
  5 +# This file is part of django-autoslug.
  6 +#
  7 +# django-autoslug is free software under terms of the GNU Lesser
  8 +# General Public License version 3 (LGPLv3) as published by the Free
  9 +# Software Foundation. See the file README for copying conditions.
  10 +#
  11 +from autoslug.fields import AutoSlugField
  12 +
  13 +
  14 +__version__ = '1.9.3'
  15 +__all__ = ['AutoSlugField']
autoslug/fields.py 0 → 100644
@@ -0,0 +1,343 @@ @@ -0,0 +1,343 @@
  1 +# coding: utf-8
  2 +#
  3 +# Copyright (c) 2008—2015 Andy Mikhailenko
  4 +#
  5 +# This file is part of django-autoslug.
  6 +#
  7 +# django-autoslug is free software under terms of the GNU Lesser
  8 +# General Public License version 3 (LGPLv3) as published by the Free
  9 +# Software Foundation. See the file README for copying conditions.
  10 +#
  11 +
  12 +# django
  13 +from django.conf import settings
  14 +from django.db.models.fields import SlugField
  15 +from django.db.models.signals import post_save
  16 +
  17 +# 3rd-party
  18 +try:
  19 + from south.modelsinspector import introspector
  20 +except ImportError:
  21 + introspector = lambda self: [], {}
  22 +
  23 +try:
  24 + from modeltranslation import utils as modeltranslation_utils
  25 +except ImportError:
  26 + modeltranslation_utils = None
  27 +
  28 +# this app
  29 +from autoslug.settings import slugify, autoslug_modeltranslation_enable
  30 +from autoslug import utils
  31 +
  32 +__all__ = ['AutoSlugField']
  33 +
  34 +SLUG_INDEX_SEPARATOR = '-' # the "-" in "foo-2"
  35 +
  36 +try: # pragma: nocover
  37 + # Python 2.x
  38 + basestring
  39 +except NameError: # pragma: nocover
  40 + # Python 3.x
  41 + basestring = str
  42 +
  43 +
  44 +class AutoSlugField(SlugField):
  45 + """
  46 + AutoSlugField is an extended SlugField able to automatically resolve name
  47 + clashes.
  48 +
  49 + AutoSlugField can also perform the following tasks on save:
  50 +
  51 + - populate itself from another field (using `populate_from`),
  52 + - use custom `slugify` function (using `slugify` or :doc:`settings`), and
  53 + - preserve uniqueness of the value (using `unique` or `unique_with`).
  54 +
  55 + None of the tasks is mandatory, i.e. you can have auto-populated non-unique
  56 + fields, manually entered unique ones (absolutely unique or within a given
  57 + date) or both.
  58 +
  59 + Uniqueness is preserved by checking if the slug is unique with given constraints
  60 + (`unique_with`) or globally (`unique`) and adding a number to the slug to make
  61 + it unique.
  62 +
  63 + :param always_update: boolean: if True, the slug is updated each time the
  64 + model instance is saved. Use with care because `cool URIs don't
  65 + change`_ (and the slug is usually a part of object's URI). Note that
  66 + even if the field is editable, any manual changes will be lost when
  67 + this option is activated.
  68 + :param populate_from: string or callable: if string is given, it is considered
  69 + as the name of attribute from which to fill the slug. If callable is given,
  70 + it should accept `instance` parameter and return a value to fill the slug
  71 + with.
  72 + :param sep: string: if defined, overrides default separator for automatically
  73 + incremented slug index (i.e. the "-" in "foo-2").
  74 + :param slugify: callable: if defined, overrides `AUTOSLUG_SLUGIFY_FUNCTION`
  75 + defined in :doc:`settings`.
  76 + :param unique: boolean: ensure total slug uniqueness (unless more precise
  77 + `unique_with` is defined).
  78 + :param unique_with: string or tuple of strings: name or names of attributes
  79 + to check for "partial uniqueness", i.e. there will not be two objects
  80 + with identical slugs if these objects share the same values of given
  81 + attributes. For instance, ``unique_with='pub_date'`` tells AutoSlugField
  82 + to enforce slug uniqueness of all items published on given date. The
  83 + slug, however, may reappear on another date. If more than one field is
  84 + given, e.g. ``unique_with=('pub_date', 'author')``, then the same slug may
  85 + reappear within a day or within some author's articles but never within
  86 + a day for the same author. Foreign keys are also supported, i.e. not only
  87 + `unique_with='author'` will do, but also `unique_with='author__name'`.
  88 +
  89 + .. _cool URIs don't change: http://w3.org/Provider/Style/URI.html
  90 +
  91 + .. note:: always place any slug attribute *after* attributes referenced
  92 + by it (i.e. those from which you wish to `populate_from` or check
  93 + `unique_with`). The reasoning is that autosaved dates and other such
  94 + fields must be already processed before using them in the AutoSlugField.
  95 +
  96 + Example usage:
  97 +
  98 + .. code-block:: python
  99 +
  100 + from django.db import models
  101 + from autoslug import AutoSlugField
  102 +
  103 + class Article(models.Model):
  104 + '''An article with title, date and slug. The slug is not totally
  105 + unique but there will be no two articles with the same slug within
  106 + any month.
  107 + '''
  108 + title = models.CharField(max_length=200)
  109 + pub_date = models.DateField(auto_now_add=True)
  110 + slug = AutoSlugField(populate_from='title', unique_with='pub_date__month')
  111 +
  112 +
  113 + More options:
  114 +
  115 + .. code-block:: python
  116 +
  117 + # slugify but allow non-unique slugs
  118 + slug = AutoSlugField()
  119 +
  120 + # globally unique, silently fix on conflict ("foo" --> "foo-1".."foo-n")
  121 + slug = AutoSlugField(unique=True)
  122 +
  123 + # autoslugify value from attribute named "title"; editable defaults to False
  124 + slug = AutoSlugField(populate_from='title')
  125 +
  126 + # same as above but force editable=True
  127 + slug = AutoSlugField(populate_from='title', editable=True)
  128 +
  129 + # ensure that slug is unique with given date (not globally)
  130 + slug = AutoSlugField(unique_with='pub_date')
  131 +
  132 + # ensure that slug is unique with given date AND category
  133 + slug = AutoSlugField(unique_with=('pub_date','category'))
  134 +
  135 + # ensure that slug in unique with an external object
  136 + # assuming that author=ForeignKey(Author)
  137 + slug = AutoSlugField(unique_with='author')
  138 +
  139 + # ensure that slug in unique with a subset of external objects (by lookups)
  140 + # assuming that author=ForeignKey(Author)
  141 + slug = AutoSlugField(unique_with='author__name')
  142 +
  143 + # mix above-mentioned behaviour bits
  144 + slug = AutoSlugField(populate_from='title', unique_with='pub_date')
  145 +
  146 + # minimum date granularity is shifted from day to month
  147 + slug = AutoSlugField(populate_from='title', unique_with='pub_date__month')
  148 +
  149 + # autoslugify value from a dynamic attribute (i.e. a method)
  150 + slug = AutoSlugField(populate_from='get_full_name')
  151 +
  152 + # autoslugify value from a custom callable
  153 + # (ex. usage: user profile models)
  154 + slug = AutoSlugField(populate_from=lambda instance: instance.user.get_full_name())
  155 +
  156 + # specify model manager for looking up slugs shared by subclasses
  157 +
  158 + class Article(models.Model):
  159 + '''An article with title, date and slug. The slug is not totally
  160 + unique but there will be no two articles with the same slug within
  161 + any month.
  162 + '''
  163 + objects = models.Manager()
  164 + title = models.CharField(max_length=200)
  165 + slug = AutoSlugField(populate_from='title', unique_with='pub_date__month', manager=objects)
  166 +
  167 + class NewsArticle(Article):
  168 + pass
  169 +
  170 + # autoslugify value using custom `slugify` function
  171 + from autoslug.settings import slugify as default_slugify
  172 + def custom_slugify(value):
  173 + return default_slugify(value).replace('-', '_')
  174 + slug = AutoSlugField(slugify=custom_slugify)
  175 +
  176 + """
  177 + def __init__(self, *args, **kwargs):
  178 + kwargs['max_length'] = kwargs.get('max_length', 50)
  179 +
  180 + # autopopulated slug is not editable unless told so
  181 + self.populate_from = kwargs.pop('populate_from', None)
  182 + if self.populate_from:
  183 + kwargs.setdefault('editable', False)
  184 +
  185 + # unique_with value can be string or tuple
  186 + self.unique_with = kwargs.pop('unique_with', ())
  187 + if isinstance(self.unique_with, basestring):
  188 + self.unique_with = (self.unique_with,)
  189 +
  190 + self.slugify = kwargs.pop('slugify', slugify)
  191 + assert hasattr(self.slugify, '__call__')
  192 +
  193 + self.index_sep = kwargs.pop('sep', SLUG_INDEX_SEPARATOR)
  194 +
  195 + if self.unique_with:
  196 + # we will do "manual" granular check below
  197 + kwargs['unique'] = False
  198 +
  199 + # Set db_index=True unless it's been set manually.
  200 + if 'db_index' not in kwargs:
  201 + kwargs['db_index'] = True
  202 +
  203 + # A boolean instructing the field to accept Unicode letters in
  204 + # addition to ASCII letters. Defaults to False.
  205 + self.allow_unicode = kwargs.pop('allow_unicode', False)
  206 +
  207 + # When using model inheritence, set manager to search for matching
  208 + # slug values
  209 + self.manager = kwargs.pop('manager', None)
  210 +
  211 + self.always_update = kwargs.pop('always_update', False)
  212 + super(SlugField, self).__init__(*args, **kwargs)
  213 +
  214 + def deconstruct(self):
  215 + name, path, args, kwargs = super(AutoSlugField, self).deconstruct()
  216 +
  217 + if self.max_length == 50:
  218 + kwargs.pop('max_length', None)
  219 +
  220 + if self.populate_from is not None:
  221 + kwargs['populate_from'] = self.populate_from
  222 + if self.editable is not False:
  223 + kwargs['editable'] = self.editable
  224 +
  225 + if self.unique_with != ():
  226 + kwargs['unique_with'] = self.unique_with
  227 + kwargs.pop('unique', None)
  228 +
  229 + if self.slugify != slugify:
  230 + kwargs['slugify'] = self.slugify
  231 +
  232 + if self.index_sep != SLUG_INDEX_SEPARATOR:
  233 + kwargs['sep'] = self.index_sep
  234 +
  235 + kwargs.pop('db_index', None)
  236 +
  237 + if self.manager is not None:
  238 + kwargs['manager'] = self.manager
  239 +
  240 + if self.always_update:
  241 + kwargs['always_update'] = self.always_update
  242 +
  243 + return name, path, args, kwargs
  244 +
  245 + def pre_save(self, instance, add):
  246 +
  247 + # get currently entered slug
  248 + value = self.value_from_object(instance)
  249 +
  250 + manager = self.manager
  251 +
  252 + # autopopulate
  253 + if self.always_update or (self.populate_from and not value):
  254 + value = utils.get_prepopulated_value(self, instance)
  255 +
  256 + # pragma: nocover
  257 + if __debug__ and not value and not self.blank:
  258 + print('Failed to populate slug %s.%s from %s' % \
  259 + (instance._meta.object_name, self.name, self.populate_from))
  260 +
  261 + if value:
  262 + slug = self.slugify(value)
  263 + else:
  264 + slug = None
  265 +
  266 + if not self.blank:
  267 + slug = instance._meta.model_name
  268 + elif not self.null:
  269 + slug = ''
  270 +
  271 + if not self.blank:
  272 + assert slug, 'slug is defined before trying to ensure uniqueness'
  273 +
  274 + if slug:
  275 + slug = utils.crop_slug(self, slug)
  276 +
  277 + # ensure the slug is unique (if required)
  278 + if self.unique or self.unique_with:
  279 + slug = utils.generate_unique_slug(self, instance, slug, manager)
  280 +
  281 + assert slug, 'value is filled before saving'
  282 +
  283 + # make the updated slug available as instance attribute
  284 + setattr(instance, self.name, slug)
  285 +
  286 + # modeltranslation support
  287 + if 'modeltranslation' in settings.INSTALLED_APPS \
  288 + and not hasattr(self.populate_from, '__call__') \
  289 + and autoslug_modeltranslation_enable:
  290 + post_save.connect(modeltranslation_update_slugs, sender=type(instance))
  291 +
  292 + return slug
  293 +
  294 +
  295 + def south_field_triple(self):
  296 + "Returns a suitable description of this field for South."
  297 + args, kwargs = introspector(self)
  298 + kwargs.update({
  299 + 'populate_from': 'None' if callable(self.populate_from) else repr(self.populate_from),
  300 + 'unique_with': repr(self.unique_with)
  301 + })
  302 + return ('autoslug.fields.AutoSlugField', args, kwargs)
  303 +
  304 +
  305 +def modeltranslation_update_slugs(sender, **kwargs):
  306 + # https://bitbucket.org/neithere/django-autoslug/pull-request/11/modeltranslation-support-fix-issue-19/
  307 + # http://django-modeltranslation.readthedocs.org
  308 + #
  309 + # TODO: tests
  310 + #
  311 + if not modeltranslation_utils:
  312 + return
  313 +
  314 + instance = kwargs['instance']
  315 + slugs = {}
  316 +
  317 + for field in instance._meta.fields:
  318 + if type(field) != AutoSlugField:
  319 + continue
  320 + if not field.populate_from:
  321 + continue
  322 + for lang in settings.LANGUAGES:
  323 + lang_code, _ = lang
  324 + lang_code = lang_code.replace('-', '_')
  325 +
  326 + populate_from_localized = modeltranslation_utils.build_localized_fieldname(field.populate_from, lang_code)
  327 + field_name_localized = modeltranslation_utils.build_localized_fieldname(field.name, lang_code)
  328 +
  329 + # The source field or the slug field itself may not be registered
  330 + # with translator
  331 + if not hasattr(instance, populate_from_localized):
  332 + continue
  333 + if not hasattr(instance, field_name_localized):
  334 + continue
  335 +
  336 + populate_from_value = getattr(instance, populate_from_localized)
  337 + field_value = getattr(instance, field_name_localized)
  338 +
  339 + if not field_value or field.always_update:
  340 + slug = field.slugify(populate_from_value)
  341 + slugs[field_name_localized] = slug
  342 +
  343 + sender.objects.filter(pk=instance.pk).update(**slugs)
autoslug/models.py 0 → 100644
autoslug/settings.py 0 → 100644
@@ -0,0 +1,68 @@ @@ -0,0 +1,68 @@
  1 +# coding: utf-8
  2 +#
  3 +# Copyright (c) 2008—2015 Andy Mikhailenko
  4 +#
  5 +# This file is part of django-autoslug.
  6 +#
  7 +# django-autoslug is free software under terms of the GNU Lesser
  8 +# General Public License version 3 (LGPLv3) as published by the Free
  9 +# Software Foundation. See the file README for copying conditions.
  10 +#
  11 +"""
  12 +Django settings that affect django-autoslug:
  13 +
  14 +`AUTOSLUG_SLUGIFY_FUNCTION`
  15 + Allows to define a custom slugifying function.
  16 +
  17 + The function can be repsesented as string or callable, e.g.::
  18 +
  19 + # custom function, path as string:
  20 + AUTOSLUG_SLUGIFY_FUNCTION = 'some_app.slugify_func'
  21 +
  22 + # custom function, callable:
  23 + AUTOSLUG_SLUGIFY_FUNCTION = some_app.slugify_func
  24 +
  25 + # custom function, defined inline:
  26 + AUTOSLUG_SLUGIFY_FUNCTION = lambda slug: 'can i haz %s?' % slug
  27 +
  28 + If no value is given, default value is used.
  29 +
  30 + Default value is one of these depending on availability in given order:
  31 +
  32 + * `unidecode.unidecode()` if Unidecode_ is available;
  33 + * `pytils.translit.slugify()` if pytils_ is available;
  34 + * `django.template.defaultfilters.slugify()` bundled with Django.
  35 +
  36 + django-autoslug also ships a couple of slugify functions that use
  37 + the translitcodec_ Python library, e.g.::
  38 +
  39 + # using as many characters as needed to make a natural replacement
  40 + AUTOSLUG_SLUGIFY_FUNCTION = 'autoslug.utils.translit_long'
  41 +
  42 + # using the minimum number of characters to make a replacement
  43 + AUTOSLUG_SLUGIFY_FUNCTION = 'autoslug.utils.translit_short'
  44 +
  45 + # only performing single character replacements
  46 + AUTOSLUG_SLUGIFY_FUNCTION = 'autoslug.utils.translit_one'
  47 +
  48 +.. _Unidecode: http://pypi.python.org/pypi/Unidecode
  49 +.. _pytils: http://pypi.python.org/pypi/pytils
  50 +.. _translitcodec: http://pypi.python.org/pypi/translitcodec
  51 +
  52 +`AUTOSLUG_MODELTRANSLATION_ENABLE`
  53 + Django-autoslug support of modeltranslation_ is still experimental.
  54 + If you wish to enable it, please set this option to `True` in your project
  55 + settings. Default is `False`.
  56 +
  57 +.. _modeltranslation: http://django-modeltranslation.readthedocs.org
  58 +
  59 +"""
  60 +from django.conf import settings
  61 +from django.core.urlresolvers import get_callable
  62 +
  63 +# use custom slugifying function if any
  64 +slugify_function_path = getattr(settings, 'AUTOSLUG_SLUGIFY_FUNCTION', 'autoslug.utils.slugify')
  65 +slugify = get_callable(slugify_function_path)
  66 +
  67 +# enable/disable modeltranslation support
  68 +autoslug_modeltranslation_enable = getattr(settings, 'AUTOSLUG_MODELTRANSLATION_ENABLE', False)
autoslug/utils.py 0 → 100644
@@ -0,0 +1,199 @@ @@ -0,0 +1,199 @@
  1 +# coding: utf-8
  2 +#
  3 +# Copyright (c) 2008—2015 Andy Mikhailenko
  4 +#
  5 +# This file is part of django-autoslug.
  6 +#
  7 +# django-autoslug is free software under terms of the GNU Lesser
  8 +# General Public License version 3 (LGPLv3) as published by the Free
  9 +# Software Foundation. See the file README for copying conditions.
  10 +#
  11 +
  12 +# django
  13 +from django.core.exceptions import ImproperlyConfigured
  14 +from django.db.models import ForeignKey
  15 +from django.db.models.fields import FieldDoesNotExist, DateField
  16 +from django.template.defaultfilters import slugify as django_slugify
  17 +
  18 +try:
  19 + # i18n-friendly approach
  20 + from unidecode import unidecode
  21 +except ImportError:
  22 + try:
  23 + # Cyrillic transliteration (primarily Russian)
  24 + from pytils.translit import slugify
  25 + except ImportError:
  26 + # fall back to Django's default method
  27 + slugify = django_slugify
  28 +else:
  29 + # Use Django's default method over decoded string
  30 + def slugify(value):
  31 + return django_slugify(unidecode(value))
  32 +
  33 +
  34 +def get_prepopulated_value(field, instance):
  35 + """
  36 + Returns preliminary value based on `populate_from`.
  37 + """
  38 + if hasattr(field.populate_from, '__call__'):
  39 + # AutoSlugField(populate_from=lambda instance: ...)
  40 + return field.populate_from(instance)
  41 + else:
  42 + # AutoSlugField(populate_from='foo')
  43 + attr = getattr(instance, field.populate_from)
  44 + return callable(attr) and attr() or attr
  45 +
  46 +
  47 +def generate_unique_slug(field, instance, slug, manager):
  48 + """
  49 + Generates unique slug by adding a number to given value until no model
  50 + instance can be found with such slug. If ``unique_with`` (a tuple of field
  51 + names) was specified for the field, all these fields are included together
  52 + in the query when looking for a "rival" model instance.
  53 + """
  54 +
  55 + original_slug = slug = crop_slug(field, slug)
  56 +
  57 + default_lookups = tuple(get_uniqueness_lookups(field, instance, field.unique_with))
  58 +
  59 + index = 1
  60 +
  61 + if not manager:
  62 + manager = field.model._default_manager
  63 +
  64 +
  65 + # keep changing the slug until it is unique
  66 + while True:
  67 + # find instances with same slug
  68 + lookups = dict(default_lookups, **{field.name: slug})
  69 + rivals = manager.filter(**lookups)
  70 + if instance.pk:
  71 + rivals = rivals.exclude(pk=instance.pk)
  72 +
  73 + if not rivals:
  74 + # the slug is unique, no model uses it
  75 + return slug
  76 +
  77 + # the slug is not unique; change once more
  78 + index += 1
  79 +
  80 + # ensure the resulting string is not too long
  81 + tail_length = len(field.index_sep) + len(str(index))
  82 + combined_length = len(original_slug) + tail_length
  83 + if field.max_length < combined_length:
  84 + original_slug = original_slug[:field.max_length - tail_length]
  85 +
  86 + # re-generate the slug
  87 + data = dict(slug=original_slug, sep=field.index_sep, index=index)
  88 + slug = '%(slug)s%(sep)s%(index)d' % data
  89 +
  90 + # ...next iteration...
  91 +
  92 +
  93 +def get_uniqueness_lookups(field, instance, unique_with):
  94 + """
  95 + Returns a dict'able tuple of lookups to ensure uniqueness of a slug.
  96 + """
  97 + for original_lookup_name in unique_with:
  98 + if '__' in original_lookup_name:
  99 + field_name, inner_lookup = original_lookup_name.split('__', 1)
  100 + else:
  101 + field_name, inner_lookup = original_lookup_name, None
  102 +
  103 + try:
  104 + other_field = instance._meta.get_field(field_name)
  105 + except FieldDoesNotExist:
  106 + raise ValueError('Could not find attribute %s.%s referenced'
  107 + ' by %s.%s (see constraint `unique_with`)'
  108 + % (instance._meta.object_name, field_name,
  109 + instance._meta.object_name, field.name))
  110 +
  111 + if field == other_field:
  112 + raise ValueError('Attribute %s.%s references itself in `unique_with`.'
  113 + ' Please use "unique=True" for this case.'
  114 + % (instance._meta.object_name, field_name))
  115 +
  116 + value = getattr(instance, field_name)
  117 + if not value:
  118 + if other_field.blank:
  119 + field_object, model, direct, m2m = instance._meta.get_field_by_name(field_name)
  120 + if isinstance(field_object, ForeignKey):
  121 + lookup = '%s__isnull' % field_name
  122 + yield lookup, True
  123 + break
  124 + raise ValueError('Could not check uniqueness of %s.%s with'
  125 + ' respect to %s.%s because the latter is empty.'
  126 + ' Please ensure that "%s" is declared *after*'
  127 + ' all fields listed in unique_with.'
  128 + % (instance._meta.object_name, field.name,
  129 + instance._meta.object_name, field_name,
  130 + field.name))
  131 + if isinstance(other_field, DateField): # DateTimeField is a DateField subclass
  132 + inner_lookup = inner_lookup or 'day'
  133 +
  134 + if '__' in inner_lookup:
  135 + raise ValueError('The `unique_with` constraint in %s.%s'
  136 + ' is set to "%s", but AutoSlugField only'
  137 + ' accepts one level of nesting for dates'
  138 + ' (e.g. "date__month").'
  139 + % (instance._meta.object_name, field.name,
  140 + original_lookup_name))
  141 +
  142 + parts = ['year', 'month', 'day']
  143 + try:
  144 + granularity = parts.index(inner_lookup) + 1
  145 + except ValueError:
  146 + raise ValueError('expected one of %s, got "%s" in "%s"'
  147 + % (parts, inner_lookup, original_lookup_name))
  148 + else:
  149 + for part in parts[:granularity]:
  150 + lookup = '%s__%s' % (field_name, part)
  151 + yield lookup, getattr(value, part)
  152 + else:
  153 + # TODO: this part should be documented as it involves recursion
  154 + if inner_lookup:
  155 + if not hasattr(value, '_meta'):
  156 + raise ValueError('Could not resolve lookup "%s" in `unique_with` of %s.%s'
  157 + % (original_lookup_name, instance._meta.object_name, field.name))
  158 + for inner_name, inner_value in get_uniqueness_lookups(field, value, [inner_lookup]):
  159 + yield original_lookup_name, inner_value
  160 + else:
  161 + yield field_name, value
  162 +
  163 +
  164 +def crop_slug(field, slug):
  165 + if field.max_length < len(slug):
  166 + return slug[:field.max_length]
  167 + return slug
  168 +
  169 +
  170 +try:
  171 + import translitcodec
  172 +except ImportError:
  173 + pass
  174 +else:
  175 + import re
  176 + PUNCT_RE = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+')
  177 +
  178 + def translitcodec_slugify(codec):
  179 + def _slugify(value, delim='-', encoding=''):
  180 + """
  181 + Generates an ASCII-only slug.
  182 +
  183 + Borrowed from http://flask.pocoo.org/snippets/5/
  184 + """
  185 + if encoding:
  186 + encoder = "%s/%s" % (codec, encoding)
  187 + else:
  188 + encoder = codec
  189 + result = []
  190 + for word in PUNCT_RE.split(value.lower()):
  191 + word = word.encode(encoder)
  192 + if word:
  193 + result.append(word)
  194 + return unicode(delim.join(result))
  195 + return _slugify
  196 +
  197 + translit_long = translitcodec_slugify("translit/long")
  198 + translit_short = translitcodec_slugify("translit/short")
  199 + translit_one = translitcodec_slugify("translit/one")