Django Sitemap Tutorial
Updated
Table of Contents
Django has a built-in sitemap framework that allows developers to generate sitemaps for a project dynamically. A sitemap is an XML file that informs search engines of your website's pages, their relevance, and how frequently they are updated. Your website will become more visible on search engines if you use a sitemap because it helps their crawlers index your entire website.
A sitemap typically exists in a single location, such as /sitemap.xml
. For example, the sitemap for this website is located at https://learndjango.com/sitemap.xml.
Before we can generate a sitemap, we need a basic Django website! Let's create a recipes
app for this purpose. I will largely give the commands in this tutorial with little explanation. If you need clarification on this part, I cover Django basics at length and in detail in my book, Django for Beginners.
If you are impatient and want to jump straight to the Sitemap section of this tutorial, scroll down now.
Initial Set Up
On the command line, navigate to the desktop directory, create a new directory called recipes
, and navigate into it.
# Windows
$ cd onedrive\desktop\code
$ mkdir recipes
$ cd recipes
# macOS
$ cd ~/desktop/code
$ mkdir recipes
$ cd recipes
Now set up and activate a virtual environment, install Django, and create a new project called django_project
.
# Windows
$ python -m venv .venv
$ .venv\Scripts\Activate.ps1
(.venv) $ python -m pip install django~=5.0.0
(.venv) $ django-admin startproject django_project .
# macOS
$ python3 -m venv .venv
$ source .venv/bin/activate
(.venv) $ python3 -m pip install django~=5.0.0
(.venv) $ django-admin startproject django_project .
Recipes App
We'll call our recipes app.... recipes
. Create it with the startapp
command.
(.venv) $ python manage.py startapp recipes
Update the django_project/settings.py
file to register it.
# django_project/settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"recipes", # new
]
And run the first migrate
command to initialize the database.
(.venv) $ python manage.py migrate
Model
Our model will contain a title
, description
, and updated_at
field for each recipe. We'll also add str
method and get_absolute_url
methods.
# recipes/models.py
from django.db import models
from django.urls import reverse
class Recipe(models.Model):
title = models.CharField(max_length=50)
description = models.TextField()
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("recipes_detail", args=[str(self.id)])
Now create a migrations file for this change and add it to our database via the migrate
command.
(.venv) $ python manage.py makemigrations recipes
(.venv) $ python manage.py migrate
Admin
The Django admin is a convenient way to play around with our data, so create a superuser
account to log in.
(.venv) $ python manage.py createsuperuser
But--and I always forget this step myself--we also need to update recipes/admin.py
to display our new app in the admin.
# recipes/admin.py
from django.contrib import admin
from .models import Recipe
class RecipeAdmin(admin.ModelAdmin):
list_display = ("title", "description",)
admin.site.register(Recipe, RecipeAdmin)
Start the local server with the command python manage.py runserver
and log in at http://127.0.0.1:8000/admin with your superuser account.
Click the "+Add" next to the Recipe
section and create two new recipes.
Awesome. We have content, but our URL paths, views, and templates still need to be added.
URLs
There are two URL files to update: our top-level one at django_project/urls.py
and then one within the recipes
app that must be created. In your text editor, create the recipes/urls.py
file now and add the following code:
# recipes/urls.py
from django.urls import path
from .views import RecipesListView, RecipesDetailView
urlpatterns = [
path("<int:pk>", RecipesDetailView.as_view(), name="recipes_detail"),
path("", RecipesListView.as_view(), name="recipes_list"),
]
Then update django_project/urls.py
.
# django_project/urls.py
from django.contrib import admin
from django.urls import path, include # new
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("recipes.urls")), # new
]
Views
We will use the built-in Generic Class-Based Views for list and detail pages.
# recipes/views.py
from django.views.generic import ListView, DetailView
from .models import Recipe
class RecipesListView(ListView):
model = Recipe
template_name = "recipes_list.html"
class RecipesDetailView(DetailView):
model = Recipe
template_name = "recipes_detail.html"
Templates
Finally, a corresponding template for each is needed as well. Create a new templates
folder within the recipes
app and add two blank HTML files.
recipes/templates/recipes_list.html
recipes/templates/recipes_detail.html
The recipes_list.html
page displays all recipes.
<!-- recipes/templates/recipes_list.html -->
<h1>Recipes</h1>
{% for recipe in object_list %}
<ul>
<li><a href="{{ recipe.get_absolute_url }}">{{ recipe.title }}</a></li>
</ul>
{% endfor %}
And recipes_detail.html
displays an individual recipe.
<!-- recipes/templates/recipes_detail.html -->
<div>
<h2>{{ object.title }}</h2>
<p>{{ object.description }}</p>
</div>
That's it! We now have three pages in our basic app:
- homepage with all recipes
- a detail page for each of our two recipes in the database
What we want is a sitemap now!
Sitemap
The sitemap framework depends on Django's sites framework, which allows you to use a single Django project to power multiple websites and differentiate between them.
Add the sites
framework and the sitemaps
app to our project's INSTALLED_APPS
setting. The SITE_ID must also be set; since we have only one site in our project, we can use 1
.
# django_project/settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.sites", # new
"django.contrib.sitemaps", # new
"recipes",
]
SITE_ID = 1 # new
The sites
framework requires an update to our database, so run migrate
at this time.
(.venv) $ python manage.py migrate
GenericSitemap
There are multiple ways to create a sitemap, but we will start with the simplest, which is to use GenericSitemap, a shortcut convenience class for common sitemap usage. It allows us to create a sitemap entirely within a urls.py
file. Since we have only one app, we will add it to the project-level django_project/urls.py
file, but you could also add it to an app if desired.
Here is what the updated file looks like:
# django_project/urls.py
from django.contrib import admin
from django.contrib.sitemaps import GenericSitemap # new
from django.contrib.sitemaps.views import sitemap # new
from django.urls import path, include
from recipes.models import Recipe # new
# new dict below...
info_dict = {
"queryset": Recipe.objects.all(),
"date_field": "updated_at",
}
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("recipes.urls")),
# new path below...
path(
"sitemap.xml",
sitemap,
{"sitemaps": {"recipes": GenericSitemap(info_dict)}},
),
]
At the top of the file, import GenericSitemap
and sitemap
. We must also import the Recipe
model from the recipes
app. Then create a dictionary--named info_dict
here--that contains a queryset entry. In this case, we are getting all objects, but filters can be used depending on your use case. We also add the optional key of date_field
, used for the lastmod
attribute in the generated sitemap. Here we are setting it to the updated_at
field from the Recipe
model.
Then create a new URL path that the pattern to "sitemap.xml" and uses the sitemap
view from Django. The sitemaps
dictionary is passed in with the name "recipes". It is possible to define multiple sitemaps with this pattern.
If you start up the local server with python manage.py runserver
you can now navigate to http://127.0.0.1:8000/sitemap.xml
in your web browser to see the sitemap created for our two recipes.
Sitemap.py
A more granular option for generating sitemaps is to create a dedicated sitemap.py
file, which can exist within either an app or the project-level directory. This approach provides complete control and access to the sitemap's long list of available methods.
For this demo, we will create a project-level django_project/sitemaps.py
file. At the top of the file, import the Sitemap
class and our Recipe
model. Then create a new class called RecipesSitemap
that subclasses Sitemap
. The only required method is items, which returns a queryset of objects. We will set lastmod
to our model's updated_at
field value to demonstrate how additional attributes can be added.
# django_project/sitemaps.py
from django.contrib.sitemaps import Sitemap
from recipes.models import Recipe
class RecipesSitemap(Sitemap):
def items(self):
return Recipe.objects.all()
def lastmod(self, obj):
return obj.updated_at
Now update the django_project/urls.py
file to set the URL path for this sitemap. We no longer need to import GenericSitemap
, create a dictionary like info_dict
, or import the Recipe
model as we did before. Instead, import RecipesSitemap
and set the URL path to "sitemap.xml". The sitemaps
dictionary is then passed into the sitemap
view.
# django_project/urls.py
from django.contrib import admin
from django.contrib.sitemaps.views import sitemap # new
from django.urls import path, include
from .sitemaps import RecipesSitemap # new
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("recipes.urls")),
# new sitemap url below...
path(
"sitemap.xml",
sitemap,
{"sitemaps": {"recipes": RecipesSitemap}},
),
]
If you refresh your web browser, the results are the same as before!
example.com
You may have noticed that the domain used for the sitemap is example.com
. That comes from the Sites
framework we stored in our database, but it is simple enough to change. Go into the admin at http://127.0.0.1:8000/admin/
and you'll see the section for "Sites." Click on the "Sites" link in blue; on the same line as the "+ Add" and "Change" links.
Then click on example.com
under "DOMAIN NAME," which will let us change the domain name and the display name. Set them both to "localhost:8000" for use in our local environment. In production, you will set the display name to your website's domain to generate absolute URLs.
After saving, navigate back to http://127.0.0.1:8000/sitemap.xml
and you can see the default domain name has changed.
Next Steps
For more information on sitemaps, check out https://www.sitemaps.org/protocol.html. If you have static pages on your site--for example, an About page--it's possible to create a sitemap just for static pages. And for sites with over 50,000 URLs, be aware that a sitemap index, which creates multiple sitemap files, is appropriate.