Skip to content

Instantly share code, notes, and snippets.

@lgellert
Last active January 2, 2020 06:43
Show Gist options
  • Save lgellert/33f413584c61f6ddb68b23833e834b68 to your computer and use it in GitHub Desktop.
Save lgellert/33f413584c61f6ddb68b23833e834b68 to your computer and use it in GitHub Desktop.
Django 1.8x + compatible automatic slug creator abstract model
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models
from django.db import IntegrityError
from django.template.defaultfilters import slugify
class AutoSlugifyOnSaveModel(models.Model):
"""
Models that inherit from this class get an auto filled slug property based on the models name property.
Correctly handles duplicate values (slugs are unique), and truncates slug if value too long.
The following attributes can be overridden on a per model basis:
* value_field_name - the value to slugify, default 'name'
* slug_field_name - the field to store the slugified value in, default 'slug'
* max_interations - how many iterations to search for an open slug before raising IntegrityError, default 1000
* slug_separator - the character to put in place of spaces and other non url friendly characters, default '-'
"""
def save(self, *args, **kwargs):
pk_field_name = self._meta.pk.name
value_field_name = getattr(self, 'value_field_name', 'name')
slug_field_name = getattr(self, 'slug_field_name', 'slug')
max_interations = getattr(self, 'slug_max_iterations', 1000)
slug_separator = getattr(self, 'slug_separator', '-')
# fields, query set, other setup variables
slug_field = self._meta.get_field(slug_field_name)
slug_len = slug_field.max_length
queryset = self.__class__.objects.all()
# if the pk of the record is set, exclude it from the slug search
current_pk = getattr(self, pk_field_name)
if current_pk:
queryset = queryset.exclude(**{pk_field_name: current_pk})
# setup the original slug, and make sure it is within the allowed length
slug = slugify(getattr(self, value_field_name))
if slug_len:
slug = slug[:slug_len]
original_slug = slug
# iterate until a unique slug is found, or max_iterations
counter = 2
while queryset.filter(**{slug_field_name: slug}).count() > 0 and counter < max_interations:
slug = original_slug
suffix = '%s%s' % (slug_separator, counter)
if slug_len and len(slug) + len(suffix) > slug_len:
slug = slug[:slug_len-len(suffix)]
slug = '%s%s' % (slug, suffix)
counter += 1
if counter == max_interations:
raise IntegrityError('Unable to locate unique slug')
setattr(self, slug_field.attname, slug)
super(AutoSlugifyOnSaveModel, self).save(*args, **kwargs)
class Meta:
abstract = True
# example model
class Article(AutoSlugifyOnSaveModel):
id = models.AutoField(primary_key=True, editable=False)
name = models.CharField(max_length=255)
# slug gets set automatically based on the name
slug = models.CharField(unique=True, max_length=100)
# example model with overrides
class LegacyArticle(AutoSlugifyOnSaveModel):
value_field_name = 'title'
slug_field_name = 'unique_title'
slug_separator = '' # strip all non-url friendly characters, don't replace with -
id = models.AutoField(primary_key=True, editable=False)
title = models.CharField(max_length=255)
# unique_title gets set automatically based on the title (like a slug)
unique_title = models.CharField(unique=True, max_length=100)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment