comments 2

Auto generating unique slug in Django: a generic approach

More than a year ago I wrote the post: Auto generating unique slug in Django. There we considered a model named Article, which had title and slug fields. We had overridden the save method of that model to automatically save the unique slug which had been generated by our written _get_unique_slug model method. But what will happen if we want to auto generate unique slug for tens or even hundreds of models? Do the same for each of the models? No way! So, in this post, we will know how we can achieve that through a generic approach. To continue reading this post, you don’t necessarily need to read that previous post, but recommended.

The Basics

What is slug? Consider the URL of this post: https://fazle.me/auto-generating-unique-slug-django-generic-approach/ . Here the bold part of the URL is called slug.

Suppose we have tens of models including the Author:

Based on the name filed, slug filed should be generated. If the name of our first Author is ‘John Doe’, the slug should be ‘john-doe’. If the name of our second Author is also ‘John Doe’, the slug should be ‘john-doe-1’. And if the name of our third Author is ‘John Doe’ again, the slug should be ‘john-doe-2’. And so on.

The Solution

Create a file named utils.py in the app directory and write this code:

As the docstring says, the get_unique_slug takes a django model instance for the first argument, sluggable field name such as ‘name’ (based on which the slug will be generated) of that model as string for the second argument and slug field name such as ‘slug’ of the model as string for the third argument. And it returns a unique slug as string.

Let’s break down the code a bit. At line 10, slugify (django.utils.text.slugify) takes a string like ‘John Doe’ as argument and returns a string like ‘john-doe’. And we are accessing the sluggable field of the model instance using getattr. At line 13, we are getting the model class from the model instance using __class__.

Take a closer look at line 15-19. Until we get a unique slug, we append the current slug string with an extension number starting from 1; such as ‘john-doe-1’, ‘john-doe-2’…. For accessing the model manager (usually objects) we are using _default_manager. Line 15 might look a bit tricky, in short, **{slug_field_name: unique_slug} works like something similar to slug='john-doe'. To understand this clearly, we need to know about how **kwargs works in python and how django model unpacks it. As this is beyond the scope of this post, I’m not discussing it here.

Let’s update our models.py:

Here we are overriding the save method, if the slug is already not set, we are setting it using the get_unique_slug function.

Boom! all done, enjoy!

Spin up your first SSD cloud server on DigitalOcean with $10 free credit!

2 Comments

  1. Harro

    So when you insert the 100th John Doe Record you are doing 100 exists queries to generate the slug.

    Wouldn’t something like this be better:


    field_name_startswith = '{}__startswith'.format(slug_field_name)
    field_name_ordering = '-{}'.format(slug_field_name)
    result = ModelClass._default_manager.filter(
    **{field_name_startswith: unique_slug}
    ).values_list(slug_field_name, flat=True).order_by(field_name_ordering).first()

    Then you can do a split on ‘-‘ and if it’s there set the extension to that. That would save a lot of database hits.

    • Hey Harro,

      Thank you very much for giving such nice feedback. I have considered this earlier. But suppose we have an Author ‘John Doe’ with the slug ‘john-doe’ and another Author ‘John Doe Dev’ with the slug ‘john-doe-dev’. What will happen to the slug if we want to create another Author with name ‘John Doe’ with your mentioned approach?

      In a real project I would just append the ‘id’ at the end of the slugify generated string if that is not unique, to save the database hits.

Leave a Reply

Your email address will not be published. Required fields are marked *