Extra field for slugs that does the work.
If the slug value is given, the value is not recreated but correctness is ensured. If value is not given, the field regenerates the slug.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | import re
from django.db.models import fields
from django.template.defaultfilters import slugify
# -- Authors -----
# Combined from following two snippets:
# crucialfelix (_unique_slugify) http://www.djangosnippets.org/snippets/1321/
# GaretJax (AutoSlugField) http://www.djangosnippets.org/snippets/728/
#
# Improved from those by Ciantic, 2010.
def _unique_slugify(instance, value, slug_field_name='slug', queryset=None,
slug_separator='-'):
"""
Calculates a unique slug of ``value`` for an instance.
:param slug_field_name: Should be a string matching the name of the field to
store the slug in (and the field to check against for uniqueness).
:param queryset: usually doesn't need to be explicitly provided - it'll
default to using the ``.all()`` queryset from the model's default
manager.
"""
slug_field = instance._meta.get_field(slug_field_name)
slug_len = slug_field.max_length
# Sort out the initial slug. Chop its length down if we need to.
slug = slugify(value)
if slug_len:
slug = slug[:slug_len]
slug = _slug_strip(slug, slug_separator)
original_slug = slug
# Create a queryset, excluding the current instance.
if queryset is None:
queryset = instance.__class__._default_manager.all()
if instance.pk:
queryset = queryset.exclude(pk=instance.pk)
# Find a unique slug. If one matches, at '-2' to the end and try again
# (then '-3', etc).
next = 2
while not slug or queryset.filter(**{slug_field_name: slug}):
slug = original_slug
end = '-%s' % next
if slug_len and len(slug) + len(end) > slug_len:
slug = slug[:slug_len-len(end)]
slug = _slug_strip(slug, slug_separator)
slug = '%s%s' % (slug, end)
next += 1
setattr(instance, slug_field.attname, slug)
return slug
def _slug_strip(value, separator=None):
"""
Cleans up a slug by removing slug separator characters that occur at the
beginning or end of a slug.
If an alternate separator is used, it will also replace any instances of the
default '-' separator with the new separator.
"""
if separator == '-' or not separator:
re_sep = '-'
else:
re_sep = '(?:-|%s)' % re.escape(separator)
value = re.sub('%s+' % re_sep, separator, value)
return re.sub(r'^%s+|%s+$' % (re_sep, re_sep), '', value)
class AutoSlugField(fields.SlugField):
"""Auto slug field, creates unique slug for model."""
def __init__(self, prepopulate_from, *args, **kwargs):
"""Create auto slug field.
If field is unique, the uniqueness of the slug is ensured from existing
slugs by adding extra number at the end of slug.
If field has slug given, it is used instead. If you want to re-generate
the slug, just set it :const:`None` or :const:`""` so it will be re-
generated automatically.
:param prepopulate_from: Must be assigned to list of field names which
are used to prepopulate automatically.
:type prepopulate_from: sequence
"""
self.prepopulate_separator = kwargs.get("prepopulate_separator", u"-")
self.prepopulate_from = prepopulate_from
kwargs["blank"] = True
super(fields.SlugField, self).__init__(*args, **kwargs)
def pre_save(self, model_instance, add): #@UnusedVariable
"""Pre-save event"""
current_slug = getattr(model_instance, self.attname)
# Use current slug instead, if it is given.
# Assumption: There are no empty slugs.
if not (current_slug is None or current_slug == ""):
slug = current_slug
else:
slug = self.prepopulate_separator.\
join(unicode(getattr(model_instance, prepop))
for prepop in self.prepopulate_from)
if self.unique:
return _unique_slugify(model_instance, value=slug,
slug_field_name=self.attname)
else:
return slugify(slug)[:self.max_length]
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 8 months, 3 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 9 months ago
- Serializer factory with Django Rest Framework by julio 1 year, 3 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 4 months ago
- Help text hyperlinks by sa2812 1 year, 5 months ago
Comments
Haven't tested this yet, but one note:
super(fields.SlugField, self).__init__(*args, **kwargs)
Should call super of AutoSlugField:
super(AutoSlugField, self).__init__(*args, **kwargs)
#
There's another bug here: the attribute for the field isn't set for the last condition:
Changing it to:
Will ensure it is set
#
Please login first before commenting.