slugify.py 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. import re
  2. from django.template.defaultfilters import slugify
  3. def unique_slugify(instance, value, slug_field_name='slug', queryset=None,
  4. slug_separator='-'):
  5. """
  6. Calculates and stores a unique slug of ``value`` for an instance.
  7. ``slug_field_name`` should be a string matching the name of the field to
  8. store the slug in (and the field to check against for uniqueness).
  9. ``queryset`` usually doesn't need to be explicitly provided - it'll default
  10. to using the ``.all()`` queryset from the model's default manager.
  11. """
  12. slug_field = instance._meta.get_field(slug_field_name)
  13. slug = getattr(instance, slug_field.attname)
  14. slug_len = slug_field.max_length
  15. # Sort out the initial slug, limiting its length if necessary.
  16. slug = slugify(value)
  17. if slug_len:
  18. slug = slug[:slug_len]
  19. slug = _slug_strip(slug, slug_separator)
  20. original_slug = slug
  21. # Create the queryset if one wasn't explicitly provided and exclude the
  22. # current instance from the queryset.
  23. if queryset is None:
  24. queryset = instance.__class__._default_manager.all()
  25. if instance.pk:
  26. queryset = queryset.exclude(pk=instance.pk)
  27. # Find a unique slug. If one matches, at '-2' to the end and try again
  28. # (then '-3', etc).
  29. next = 2
  30. while not slug or queryset.filter(**{slug_field_name: slug}):
  31. slug = original_slug
  32. end = '%s%s' % (slug_separator, next)
  33. if slug_len and len(slug) + len(end) > slug_len:
  34. slug = slug[:slug_len-len(end)]
  35. slug = _slug_strip(slug, slug_separator)
  36. slug = '%s%s' % (slug, end)
  37. next += 1
  38. setattr(instance, slug_field.attname, slug)
  39. def _slug_strip(value, separator='-'):
  40. """
  41. Cleans up a slug by removing slug separator characters that occur at the
  42. beginning or end of a slug.
  43. If an alternate separator is used, it will also replace any instances of
  44. the default '-' separator with the new separator.
  45. """
  46. separator = separator or ''
  47. if separator == '-' or not separator:
  48. re_sep = '-'
  49. else:
  50. re_sep = '(?:-|%s)' % re.escape(separator)
  51. # Remove multiple instances and if an alternate separator is provided,
  52. # replace the default '-' separator.
  53. if separator != re_sep:
  54. value = re.sub('%s+' % re_sep, separator, value)
  55. # Remove separator from the beginning and end of the slug.
  56. if separator:
  57. if separator != '-':
  58. re_sep = re.escape(separator)
  59. value = re.sub(r'^%s+|%s+$' % (re_sep, re_sep), '', value)
  60. return value