Auto-generate unique slugs in Django
Create unique, URL-friendly slugs on save.
from django.db import models
from django.utils.text import slugify
class Article(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True, blank=True)
def save(self, *args, **kwargs):
if not self.slug:
base = slugify(self.title)[:180] or "item"
slug, i = base, 2
while Article.objects.filter(slug=slug).exclude(pk=self.pk).exists():
slug = f"{base}-{i}"; i += 1
self.slug = slug
super().save(*args, **kwargs)
Why bother with slugs
A slug is the readable part of a URL: /blog/merge-excel-files/ instead of /blog/?id=47. People trust readable links more, they're easier to share, and search engines use the words in them.
How this works
When an Article is saved for the first time and has no slug, slugify turns the title into a URL-friendly version: lowercase, spaces become hyphens, symbols are dropped. So "Top 10 Excel Tips!" becomes top-10-excel-tips.
The while loop handles duplicates. If top-10-excel-tips is taken, it tries top-10-excel-tips-2, then -3, and so on. .exclude(pk=self.pk) makes sure an article doesn't count itself as a duplicate when it's saved again.
The [:180] cut keeps room for the -2 suffix, so the slug never overflows the field.
Things to know
- Slugs don't change when the title changes. That's intentional. If the slug changed, every link anyone had shared would break. If you really must change one, set up a redirect from the old URL.
- Titles in Hindi or other scripts come out empty from
slugify, because by default it only keeps English letters. That's what theor "item"fallback is for. To keep the original characters, useslugify(self.title, allow_unicode=True). - Under heavy traffic, two saves at the exact same moment could pick the same slug. The
unique=Trueon the field means the database will reject the second one rather than allow a duplicate. For a normal blog or catalogue, that's more than enough protection.