Why a sitemap and a feed

A crawler follows links, so deep or new pages are found late. sitemap.xml is an explicit list of URLs. An RSS/Atom feed publishes the same content in a machine-readable form. Django provides all of it in django.contrib.

A crawler finds sitemap.xml through robots.txt
A crawler finds sitemap.xml through robots.txt
ToolURLAudience
Sitemap/sitemap.xmlsearch crawlers
Feed/feeds/rss/readers and aggregators
robots.txt/robots.txtall crawlers

Setup

Add django.contrib.sites, django.contrib.sitemaps and django.contrib.syndication to INSTALLED_APPS and set SITE_ID = 1. Without sites Django uses the host of the current request, but with sites the domain lives in one place.

python
# settings.py
INSTALLED_APPS = [
    # ...
    "django.contrib.sites",
    "django.contrib.sitemaps",
    "django.contrib.syndication",
    "blog",
]

SITE_ID = 1

⚠️ After migrate the default Site record is example.com. Change it to your real domain in the admin or shell: Site.objects.update(domain="example.org", name="Example").

The Sitemap class

The model needs get_absolute_url(). The Sitemap items() method returns the objects and lastmod() returns the change date.

python
# blog/models.py
from django.db import models
from django.urls import reverse

class Post(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)
    body = models.TextField()
    published = models.BooleanField(default=False)
    published_at = models.DateTimeField(null=True, blank=True)
    updated_at = models.DateTimeField(auto_now=True)

    def get_absolute_url(self):
        return reverse("blog:post-detail", kwargs={"slug": self.slug})
python
# blog/sitemaps.py
from django.contrib.sitemaps import Sitemap
from django.urls import reverse

from .models import Post

class PostSitemap(Sitemap):
    changefreq = "weekly"
    priority = 0.8
    protocol = "https"