Django slugs - Easily explained
I wrote about my job board the other day, one of the 2 experiments I am trying to get off the ground. The idea is to build a business without having a large audience. For SEO the URL is very important and you don’t want to mess them up. From Google’s documents you want to use words in URLs. URLs with words that are relevant to your site's content and structure are friendlier for visitors navigating your site
. And also, you want to avoid using lengthy URLs with unnecessary parameters and session IDs
. So, for example, https://datainternships.co/internships/data-scientist-italy-ferrari
is a much more SEO-friendly choice than https://datainternships.co/internships/italy/12281726
, where we use some random id to identify the job opening.
But how can you write SEO-friendly URLs in Django? That’s where slugs
come into play. Let’s look at an example on how to implement slugs in Django.
The model
Let’s take an example of a recipe website. Even better, international recipes. You have categories for vegetarian, meat, pasta dishes and so on. You probably want to have /italian-vegetarian-dishes
or /french-meat-dishes
as your URL.
You probably have a model that looks something like this
class Recipe(models.Model):
type = models.CharField(default="")
nationality = models.CharField(default="")
recipe = models.CharField(default="")
You have the ingredients to create a slug, but you do not have the slug yet. So first of all, you need to add the slug to your database. Add an attribute recipe_slug to the class Recipe
.
class Recipe(models.Model):
...
recipe_slug = models.SlugField(null=True, blank=True, unique=True, max_length=100)
The view
Now that the field is there we need a way to automatically populate it when we add a new recipe to our database. For this, you just need to add a function to the class Recipe
. It will look like this
class Recipe(models.Model):
...
def save(self, *args, **kwargs):
self.recipe_slug = slugify(self.nationality + "-" + self.type)
super().save(*args, **kwargs)
That’s it. Now every time you add a new recipe to the database, the slug field will get populated.
Now, we need to tell Django to use the slug just created in the URL. Let’s just create the corresponding row in urls.py
.
path("recipe/<str:recipe_slug>/", RecipeDetailView.as_view(), name="recipe_detail")
Where RecipeDetailView
is the view where you show your recipe. It now probably looks something like this.
class RecipeDetailView(DetailView):
model = RecipeDetail
template_name = "recipe/recipe_detail.html"
We have just the Model and the template name. Here we have the template under recipe/recipe_detail.html
, but it can be anywhere. To make the slugs work, we need to add slug_field
and slug_url_kwarg
to properly parse them.
class RecipeDetailView(DetailView):
model = RecipeDetail
slug_field = "recipe_slug"
slug_url_kwarg = "recipe_slug"
template_name = "recipe/recipe_detail.html"
Now that you have a link to the recipe, you can use the slug to link directly to the RecipeDetailView
in the usual way, just remember to use recipe_slug=recipe.recipe_slug %}
in your href
.
Once you run a migration with python manage.py makemigrations
and python manage.py migrate
, each time you insert a new recipe in the database a slug will be automatically created.
Add slugs for previous data
Something that I didn’t notice when I inserted slugs is that SLUGS WILL WORK ONLY FOR NEW DATA. As it modifies the database schema, it will only work from the moment you have included them. For old data, you need to find a way to populate your database with the expected input. In our case, it could be something that mimics the slugify
function. We could run an SQL statement like this:
update recipe set recipe_slug=lower(concat(nationality, '-', type, '-dishes'));
This will create the same pattern as our slugify
function above. For example, if the nationality is chinese
, and the type is dumpling
, the field recipe_slug
will bechinese-dumpling-dishes
.