Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Versioned slug model
from django.contrib import admin
from .models import MainThing, MainThingSlug
class SlugInline(admin.StackedInline):
model = MainThingSlug
fields = ('slug',)
can_delete = False
class MainThingAdmin(admin.ModelAdmin):
inlines = (SlugInline,)
admin.site.register(MainThing, MainThingAdmin)
from django.db import models, transaction
from django.db.models import Q
class MainThing(models.Model):
title = models.CharField('The title', max_length=1023)
def __str__(self):
return self.title
class RelatedManager(models.Manager):
def __init__(self, *field_names):
super(RelatedManager, self).__init__()
self._field_names = field_names
def get_queryset(self):
return (
super(RelatedManager, self).get_queryset()
.select_related(*self._field_names))
class MainThingSlug(models.Model):
objects = RelatedManager('thing')
slug = models.SlugField(unique=True)
thing = models.OneToOneField(
MainThing, null=True, on_delete=models.SET_NULL)
superseeded_by = models.ForeignKey('self', null=True)
def _get_replacable_pk(self):
candidates = (
MainThingSlug.objects
.filter(slug=self.slug, thing__isnull=True)
.values_list('pk'))
return None if len(candidates) == 0 else candidates[0][0]
@transaction.atomic
def save(self, *args, **kwargs):
old_pk = self.pk
# update the "old" db row, as a new one will be created
MainThingSlug.objects.filter(pk=old_pk).update(thing=None)
# Allow old slugs to be reused if they are not in use
# otherwise create new row
self.pk = self._get_replacable_pk()
kwargs['force_update' if self.pk else 'force_insert'] = True
self.superseeded_by = None
return_value = super(MainThingSlug, self).save(*args, **kwargs)
# set the superseeded_by column to the new primary key
if old_pk is not None:
(MainThingSlug.objects
.filter(Q(superseeded_by=old_pk) | Q(pk=old_pk))
.update(superseeded_by=self.pk))
return return_value
def validate_unique(self, exclude=None):
# if the selected slugs exists, but is not used, allow to reuse it and
# skip unique validation for the slug field.
if (self._get_replacable_pk() is not None):
exclude = list(exclude) if exclude else []
exclude.append('slug')
return super(MainThingSlug, self).validate_unique(exclude)
def __str__(self):
return self.slug
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.