from django.db import IntegrityError from django.template.defaultfilters import slugify class AutoSlugField (SlugField): """ A SlugField that automatically populates itself at save-time from the value of another field. Accepts argument populate_from, which should be the name of a single field which the AutoSlugField will populate from (default = 'name'). By default, also sets unique=True, db_index=True, and editable=False. Accepts additional argument, overwrite_on_save. If True, will re-populate on every save, overwriting any existing value. If False, will not touch existing value and will only populate if slug field is empty. Default is False. """ def __init__ (self, populate_from='name', overwrite_on_save=False, *args, **kwargs): kwargs.setdefault('unique', True) kwargs.setdefault('db_index', True) kwargs.setdefault('editable', False) self._save_populate = populate_from self._overwrite_on_save = overwrite_on_save super(AutoSlugField, self).__init__(*args, **kwargs) def _populate_slug(self, model_instance): value = getattr(model_instance, self.attname, None) prepop = getattr(model_instance, self._save_populate, None) if (prepop is not None) and (not value or self._overwrite_on_save): value = slugify(prepop) setattr(model_instance, self.attname, value) return value def contribute_to_class (self, cls, name): # apparently in inheritance cases, contribute_to_class is called more # than once, so we have to be careful not to overwrite the original # save method. if not hasattr(cls, '_orig_save'): cls._orig_save = cls.save def _new_save (self_, *args, **kwargs): counter = 1 orig_slug = self._populate_slug(self_) slug_len = len(orig_slug) if slug_len > self.max_length: orig_slug = orig_slug[:self.max_length] slug_len = self.max_length setattr(self_, name, orig_slug) while True: try: self_._orig_save(*args, **kwargs) break except IntegrityError, e: # check to be sure a slug fight caused the IntegrityError s_e = str(e) if name in s_e and 'unique' in s_e: counter += 1 max_len = self.max_length - (len(str(counter)) + 1) if slug_len > max_len: orig_slug = orig_slug[:max_len] setattr(self_, name, "%s-%s" % (orig_slug, counter)) else: raise cls.save = _new_save super(AutoSlugField, self).contribute_to_class(cls, name)