How to create a unique slug in Django

Viewed 32809

I am trying to create a unique slug in Django so that I can access a post via a url like this: http://www.example.com/buy-a-new-bike_Boston-MA-02111_2

The relevant models:

class ZipCode(models.Model):
    zipcode = models.CharField(max_length=5)
    city = models.CharField(max_length=64)
    statecode = models.CharField(max_length=32)

class Need(models.Model):
    title = models.CharField(max_length=50)
    us_zip = models.CharField(max_length=5)
    slug = ?????

    def get_city():
        zip = ZipCode.objects.get(zipcode=self.us_zip)
        city = "%s, %s %s" % (zip.city, zip.statecode, zip.zipcode)
        return city

A sample ZipCode record:

  • zipcode = "02111"
  • city = "Boston"
  • statecode = "MA"

A sample Need record:

  • title = "buy a new bike"
  • us_zip = "02111"
  • slug = "buy-a-new-bike_Boston-MA-02111_2" (desired)

Any tips as to how to create this unique slug? Its composition is:

  • Need.title + "_" + Need.get_city() + "_" + an optional incrementing integer to make it unique. All spaces should be replaced with "-".

NOTE: My desired slug above assumes that the slug "buy-a-new-bike_Boston-MA-02111" already exists, which is what it has the "_2" appended to it to make it unique.

I've tried django-extensions, but it seems that it can only take a field or tuple of fields to construct the unique slug. I need to pass in the get_city() function as well as the "_" connector between the title and city. Anyone solved this and willing to share?

Thank you!

UPDATE

I'm already using django-extensions for its UUIDField, so it would be nice if it could also be usable for its AutoSlugField!

12 Answers

Here are a couple functions that I use. You pass in the model instance and the desired title into unique_slugify which will add the slug if it doesn't exist, otherwise it will continue trying to append a 4 digit random string until it finds a unique one.

import string
from django.utils.crypto import get_random_string

def unique_slugify(instance, slug):
    model = instance.__class__
    unique_slug = slug
    while model.objects.filter(slug=unique_slug).exists():
        unique_slug = slug + get_random_string(length=4)
    return unique_slug

I usually use it by overriding the model save method.

class YourModel(models.Model):
    slug = models.SlugField()
    title = models.CharField()

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = unique_slugify(self, slugify(self.title))
        super().save(*args, **kwargs)

This is the simple and small code i am using for generating unique slug, you only need one field to create your unique slug field

from random import randint

def save(self, *args, **kwargs):
    if Post.objects.filter(title=self.title).exists():
        extra = str(randint(1, 10000))
        self.slug = slugify(self.title) + "-" + extra
    else:
        self.slug = slugify(self.title)
    super(Post, self).save(*args, **kwargs)

I hope you like this.

Hi can you tried this function

class Training(models.Model):
    title = models.CharField(max_length=250)
    text = models.TextField()
    created_date = models.DateTimeField(
    auto_now_add=True, editable=False, )
    slug = models.SlugField(unique=True, editable=False, max_length=250)

    def __unicode__(self):
       return self.title

    def get_unique_slug(id,title,obj):
      slug = slugify(title.replace('ı', 'i'))
      unique_slug = slug
      counter = 1
      while obj.filter(slug=unique_slug).exists():
         if(obj.filter(slug=unique_slug).values('id')[0]['id']==id):
             break
         unique_slug = '{}-{}'.format(slug, counter)
         counter += 1
      return unique_slug.  

    def save(self, *args, **kwargs):
       self.slug =self.get_unique_slug(self.id,self.title,Training.objects)
       return super(Training, self).save(*args, **kwargs)

from django.utils.text import slugify Helps a lot and has quite clear Concepts. Here one example on How to auto-generate slug by using from django.utils.text import slugify

utils.py

from django.utils.text import slugify
import random
import string

# Random string generator
def random_string_generator(size=10, chars=string.ascii_lowercase + string.digits):
    return ''.join(random.choice(chars) for _ in range(size))

# Unique Slug Generator 
def unique_slug_generator(instance, new_slug=None):
    """
    It assumes your instance has a model with a slug field and a title character (char) field.
    """
    if new_slug is not None:
        slug = new_slug  
    else:
        slug = slugify(instance.title)  

    Klass = instance.__class__

    qs_exists = Klass.objects.filter(slug=slug).exists()

    if qs_exists:
        new_slug = "{slug}-{randstr}".format(slug=slug, randstr=random_string_generator(size=4))
        return unique_slug_generator(instance, new_slug=new_slug)
    return slug

models.py

from django.db.models.signals import pre_save # Signals
# import the unique_slug_generator from .utils.py 
from .utils import unique_slug_generator

class Product(models.Model):
    title  = models.CharField(max_length=120)
    # set blank to True
    slug  = models.SlugField(blank=True, unique=True)

def product_pre_save_receiver(sender, instance, *args, **kwargs):
    if not instance.slug:
        instance.slug = unique_slug_generator(instance)


pre_save.connect(product_pre_save_receiver, sender=Product)

Django documentation explains Django.utils.text import slugify to generate slug automatically. You can read more detail here

After implementing the code, while creating product, you may leave the slug field blank, which will be further aquired with auto generated slug for the product which will be unique in this case.

 def get_slug(self):
    slug = slugify(self.title.replace("ı", "i"))
    unique = slug
    number = 2

    while Model.objects.filter(slug=unique).exists():
        unique = "{}-{}".format(slug, number)
        number += 1
    return unique

Try this, worked out for me,welcome in advance:

class Parcel(models.Model):
    title = models.CharField(max_length-255)
    slug = models.SlugField(unique=True, max_length=255)
    weight = models.IntegerField()
    description = models.CharField(max_length=255)
    destination = models.CharField(max_length=255)
    origin = models.CharField(max_length=255)

    def __str__(self):
        return self.description

    def save(self, *args, **kwargs):
        if not self.slug:
            t_slug = slugify(self.title)
            startpoint = 1
            unique_slug = t_slug
            while Parcel.objects.filter(slug=unique_slug).exists():
                unique_slug = '{} {}'.format(t_slug, origin)
                origin += 1
            self.slug = unique_slug
        super().save(*args, **kwargs)
Related