Django Models: How to automatically populate slug fields for URLs

Today, we're looking at how to add a slug field to a Django model that automatically populates itself when you create an object in the database.

Say you’re creating a blog and instead of having each post as https://myblog/post/1, you want https://myblog.com/post/my-first-post.

You could add SlugField to your model. However, slug fields don’t populate automatically and your URL won’t work until you go into /admin and populate the slug yourself.

What we need is to find a way to set the slug automatically when the post is created.

There are two ways to go about it:

1. Install django-autoslug

This method involves installing a package called django-autoslug and using its AutoSlugField instead of SlugField. This method is best if you need to guarantee the uniqueness of the slug.

2. Use the slugify method

This method involves adding a SlugField to the model but we override the save() method of the model so that the slug is calculated and assigned before saving to the database. This method is best if you want to minimise the number of 3rd party packages and take a native Django approach. However, this method only works if you don’t require slugs to be unique.

The Code

The gist below outlines how I auto-populated slugs on my movie model. movie.slug uses the AutoSlugField method and movie.slug2 uses the slugify method.

gist.github.com/alicecampkin/18d709ca2464ea..

Method 1: django-autoslug (preferred)

First you need to install django-autoslug.

$ pip install django-autoslug

Then import the AutoSlugField into your model.

from autoslug import AutoSlugField
slug = AutoSlugField(populate_from='title',editable=True, always_update=True)

Method 2: slugify

First, import slugify from django.utils.text:

from django.utils.text import slugify

Next, define a SlugField:

slug2 = models.SlugField(max_length=255)

Finally, override the save() method.

def save(self, **kwargs):
    self.slug2 = slugify(self.title)
    super(Movie, self).save(**kwargs)

Note for beginners:

  • The super line is telling Django to call the save() method of the parent class. This lets us add our custom code and then tell Django to do what save() would normally do. No copying and pasting code from models.Model.

  • **kwargs is short for Keyword Arguments. The double asterisk allows you pass in a dictionary and Python will turn each item of the dictionary into an argument of the function. We have to include this because save() is usually called with two keyword arguments: force_insert and using. By including them, we are preserving the original functionality.

Guaranteeing Uniqueness

If you’re using slugs to construct URLs, it is critical that each slug is unique.

Method 2 won’t guarantee a unique slug. If you set unique=True on the SlugField and then try to create two movies with the same title, then Django will raise an IntegrityError when attempting to create the second movie because of the duplicate slug.

If you use django-autoslug as per Method 1, then django-autoslug will automatically calculate a unique slug and will not prevent you from creating two movies with the same title.

Unique With User

Django has constraints unique_for_month, unique_for_date and unique_for_year. This is useful if you are constructing URLs from both slugs and dates.

How to we make our slugs unique for the user? Say our URL contains the user name and the post slug, we should allow two users to write posts with the same title.

This is where django-autoslug comes in useful. We can specify unique_with to provide more flexibility:

slug = AutoSlugField(populate_from='title', unique_with=['author__username'], always_update=True)

Problem: Django won’t let you make the migration without specifying a default.

This issue is common for both methods.

It is impossible to add a non-nullable field 'slug2' to movie without specifying a default. This is because the database needs something to populate existing rows.

If you get this message after running python manage.py makemigrations, I’ve got a nice trick to automatically populate existing rows without allowing blank values or dropping your database. You can read about it here.

Problem: Django Admin won’t let you create objects without specifying the slug

A screenshot of Django admin with errors. I attempted to create field without a slug but the form expects a value.

This happens because the form in Django admin is validated before the save() method is called.

What we can do is disable the fields in /admin.

You can do this by specifying editable=False on the SlugField or AutoSlugField, but if you do this, they will no longer be visible in /admin.

My preferred method is to edit the admin code to make the fields read only.

A screenshot from Django admin with the auto-populated slugs. The field is non-editable.

How to make fields read_only in admin

Go to admin.py where your model is registered.

Define a custom ModelAdmin class:

class MovieAdmin(admin.ModelAdmin):
    readonly_fields = ["slug", "slug2"]


admin.site.register(Movie, MovieAdmin)

Don’t forget to state which admin class you are using when registering your model.

Conclusion

Slugs can be auto-populated by using either the AutoSlugField of django-autoslug or by overriding the save() method of a model. If you are using slugs to construct URLs and need to guarantee the slug’s uniqueness, then I recommend using AutoSlugField as it will automatically correct the slug if it’s not unique.