slugify.py 2.5 KB

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