Django Models: How to automatically populate slug fields for URLs
Table of contents
- 1. Install django-autoslug
- 2. Use the slugify method
- The Code
- Method 1: django-autoslug (preferred)
- Method 2: slugify
- Guaranteeing Uniqueness
- Problem: Django won’t let you make the migration without specifying a default.
- Problem: Django Admin won’t let you create objects without specifying the slug
- Conclusion
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 thesave()
method of the parent class. This lets us add our custom code and then tell Django to do whatsave()
would normally do. No copying and pasting code frommodels.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 becausesave()
is usually called with two keyword arguments:force_insert
andusing
. 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
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.
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.