Django Unique Slug Generator

For my blog posts, I wanted to use a slug based on the post's title as its URL. The post's slug needs to be unique so that Django knows which object to return.

What I wanted to happen was, if the title of a post happens to be the same as a previous post, an incrementing number will be added to the end of the new post's slug. For example, if there are two posts named, "Hello, World!", the first post would receive the slug, "hello-world", the second post would receive the slug, "hello-world-2".

 

In my blog app directory, I created a utils.py file. This is where my unique slug function resides. At the top of models.py get_unique_slug is imported and get_unique_slug is called in the save method.

models.py

from django.db import models
from django.urls import reverse
from django.utils.text import slugify
from blog.utils import get_unique_slug


class Post(models.Model):
    title = models.CharField(max_length=50)
    body = models.TextField()
    slug = models.SlugField(blank=True)

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        get_unique_slug(self)
        super(Post, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('post-detail', kwargs={'slug': self.slug})

 

The get_unique_slug function is called from models.py. It first creates a slug from self.title using slugify. A variable called i is set to 1, this will increment up with as many posts that have the same title. The process_slug(self, i) function is then called.

First, the counter variable i is incremented by 1 to equal 2. The blog posts are then imported and a query is run to find any posts that have the same slug. If no other slugs are found, the function ends and the slug is saved as is. If a post is found with a matching slug, the current slug will be created with slugify(self.title) and adding the value of i to the end of it. process_slug(self, i) is called again, and so on.

When a post is edited, the query will match the primary key of the object that was found with the primary key of the current object being edited and end the execution leaving the slug as it was.

utils.py

from django.utils.text import slugify


def process_slug(self, i):
    i += 1
    from blog.models import Post
    slug_filter = Post.objects.filter(slug__exact=self.slug)
    if len(slug_filter) == 1:
        if not slug_filter[0].pk == self.pk:
            self.slug = f"{slugify(self.title)}-{i}"
            process_slug(self, i)


def get_unique_slug(self):
    if not self.slug:
        self.slug = slugify(self.title)
        i = 1
        process_slug(self, i)