Django Sitemap Tutorial
A vital part of modern SEO (Search Engine Optimization) is to have a sitemap, an XML file that tells a search engine how often a page is updated and how important it is in relation to other pages on the site. Fortunately, Django comes with a built-in sitemap framework to help in this task.
Generally, a sitemap exists at a single location as a sitemap.xml
file such as https://learndjango.com/sitemap.xml. But for sites with over 50,000 URLs, a sitemap index, which creates multiple sitemap files, is appropriate.
Recipe app
Before we can generate a sitemap, we need a basic Django website! Let's create a recipes
app for this purpose. I'm going to largely give the commands in this section without much explanation. If you find this part confusing, I cover Django basics at length and in detail in my book, Django for Beginners.
On the command line, install Django, create a new project called config
, and a new app called recipe
.
$ cd ~/Desktop $ mkdir recipes && cd recipes $ pipenv install django==3.0.3 $ pipenv shell (recipes) $ django-admin.py startproject config . (recipes) $ python manage.py startapp recipes
Then within the config/settings.py
file add our new recipes
app.
# config/settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'recipes', # new ]
Run the first migrate
command to initialize the database.
(recipes) $ python manage.py migrate
Model
Our model will contain a title
and description
for each recipe. We'll also add str
and get_absolute_url
methods as well.
# 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() def __str__(self): return self.title def get_absolute_url(self): return reverse('recipe_detail', args=[str(self.id)])
Now create a migrations file for this change and add it to our database via the migrate
command.
(recipes) $ python manage.py makemigrations recipes (recipes) $ 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.
(recipes) $ python manage.py createsuperuser
But--and I always forget this step myself--we also need to update recipe/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 up 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 on the "+Add" next to the Recipe
section and create two new recipes.
Awesome. Now we have content. What's missing is our URL paths, views, and templates.
URLs
There are two URL files to update: our top-level one at config/urls.py
and then one within the recipes
app that must be created. Let's create that new one now.
(recipes) $ touch recipes/urls.py
Now update config/urls.py
.
# config/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 ]
And then recipes/urls.py
.
# recipes/urls.py from django.urls import path from .views import RecipeListView, RecipeDetailView urlpatterns = [ path('<int:pk>', RecipeDetailView.as_view(), name='recipe_detail'), path('', RecipeListView.as_view(), name='recipe_list'), ]
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 RecipeListView(ListView): model = Recipe template_name = 'recipe_list.html' class RecipeDetailView(DetailView): model = Recipe template_name = 'recipe_detail.html'
Templates
Finally, a corresponding template for each is needed as well. First, create the blank HTML files.
(recipes) $ mkdir recipe/templates (recipes) $ touch recipe/templates/recipe_list.html (recipes) $ touch reciple/templates/recipe_detail.html
The recipe_list.html
page displays all recipes.
<!-- recipe_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 recipe_detail.html
displays an individual recipe.
<!-- recipe_detail.html --> <div> <h2>{{ object.title }}</h2> <p>{{ object.body }}</p> </div>
That's it! We now have 3 pages in our basic app: - homepage with all recipes - a detail page for each of our 2 recipes in the database
What we want is a sitemap now!
Installation
To add a sitemap, we must install the sitemap app to our project as well as the sites framework which it relies upon.
# config/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.apps.RecipesConfig', ] SITE_ID = 1 # new
The sites
framework requires an update to our database so run migrate
at this time.
(recipes) $ python manage.py migrate
There are several ways to implement sitemaps, but the simplest approach is to use GenericSitemap, a convenience class that covers common sitemap usage. Here's how to add it to our config/urls.py
file.
# config/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 info_dict = { 'queryset': Recipe.objects.all(), } urlpatterns = [ path('admin/', admin.site.urls), path('', include('recipes.urls')), path('sitemap.xml', sitemap, # new {'sitemaps': {'blog': GenericSitemap(info_dict, priority=0.6)}}, name='django.contrib.sitemaps.views.sitemap'), ]
Make sure the server is running and visit http://127.0.0.1:8000/sitemap.xml.
There's our sitemap! But there's still a number of tweaks we could make to improve things:
- switch away from
example.com
as the name of our site (comes fromsites
framework, go into Admin) - add sitemaps for other posts, maybe a blog?
- auto-ping Google when there's a change...
Also notice that it's using example.com
as the name of our site. This comes from the sites
framework. If you go into the Django admin, there's a new section called Sites
where you can change the name.
It's also possible to change the priority and changefreq. And while you might notice that the current version uses http
rather than https
, if your production site uses https
then that will be reflected in the sitemap. Or you can change the protocol entirely, if so desired.