Django Sitemap Tutorial

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~=4.2.0
(.venv) $ django-admin startproject django_project .

# macOS
$ python3 -m venv .venv
$ source .venv/bin/activate
(.venv) $ python3 -m pip install django~=4.2.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.

Admin Homepage

Click the "+Add" next to the Recipe section and create two new recipes.

Hummus Recipe

PBJ Recipe

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.

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

Hummus Recipe Page

PBJ Recipe Page

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 via GenericSitemap

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.

Site in Admin

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.

Set Domain Name and Display Name to localhost:8000

After saving, navigate back to http://127.0.0.1:8000/sitemap.xml and you can see the default domain name has changed.

Sitemap with localhost

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.

Join My Newsletter

Subscribe to get the latest tutorials/writings by email.

    No spam. Unsubscribe at any time.