Last active
January 2, 2020 06:43
-
-
Save lgellert/33f413584c61f6ddb68b23833e834b68 to your computer and use it in GitHub Desktop.
Django 1.8x + compatible automatic slug creator abstract model
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- 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