Quickstart

Run these commands to setup a base project:

python3 -m venv venv
source venv/bin/activate
pip install django
django-admin startproject mysite .
python manage.py startapp blog
python manage.py migrate
python manage.py runserver

Edit the mysite/urls.py file and make the following changes:

from django.contrib import admin
from django.urls import path

# START
from blog.models import Post
import blog.views

from django.contrib.sitemaps.views import sitemap
from django.contrib.sitemaps import GenericSitemap

sitemaps = {
    'blog': GenericSitemap({
        'queryset': Post.objects.all(),
        'date_field': 'updated',
    }, priority=0.9),
}
# END

urlpatterns = [
    # START
    path('blog/<slug:slug>/', blog.views.detail, name='blog_detail'),
    path('sitemap.xml', sitemap,
         {'sitemaps': sitemaps},
         name='django.contrib.sitemaps.views.sitemap'),
    # END
    path('admin/', admin.site.urls),
]

Edit the project settings.py file and make the following changes:

INSTALLED_APPS = [
    'blog.apps.BlogConfig', # < here
    ...
    'django.contrib.staticfiles',
    'django.contrib.sites', # < here
    'django.contrib.sitemaps', # < here
  ]

SITE_ID = 1 # < here

Edit the blog/views.py file and add a new view function called detail to it:

from django.shortcuts import render, get_object_or_404

from blog.models import Post


def detail(request, slug=None):
    post = get_object_or_404(Post, slug=slug)

    return render(request,
                  'blog/detail.html',
                  {'post': post})

Add a new template file called detail.html in the blog/templates/blog/ directory:

<h1>{{ post }}</h1>

Edit the blog app models.py file and add a new class called Post to it:

from django.db import models
from django.urls import reverse
from django.utils.text import slugify


class Post(models.Model):
    title = models.CharField(default='',
                             unique=True,
                             max_length=255)
    slug = models.SlugField(default='',
                            blank=True,
                            max_length=255)
    updated = models.DateTimeField(auto_now=True,
                                   null=True)

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        self.slug = slugify(self.title)
        super().save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('blog_detail',
                       args=[str(self.slug)])

Run migrations:

python manage.py makemigrations
python manage.py migrate

Create some posts:

python manage.py shell
>>> from blog.models import Post
>>> import uuid
>>> for x in range(0,50):
...     post = Post(title='title-%d' % uuid.uuid4())
...     post.save()
...

Visit /sitemap.xml to see the result:

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <url>
        <loc>http://example.com/blog/title-91.../</loc>
        <lastmod>2020-01-05</lastmod>
        <priority>0.9</priority>
    </url>
    <url>
        <loc>http://example.com/blog/title-50.../</loc>
        <lastmod>2020-01-05</lastmod>
        <priority>0.9</priority>
    </url>
    <url>
        <loc>http://example.com/blog/title-20.../</loc>
        <lastmod>2020-01-05</lastmod>
        <priority>0.9</priority>
    </url>
</urlset>

Static sitemaps

Create a new file called sitemaps.py in the blog app directory. Add a new class called StaticViewSitemap to it:

from django.contrib.sitemaps import Sitemap
from django.urls import reverse

class StaticViewSitemap(Sitemap):

    def items(self):
        return ['about']

    def location(self, item):
        return reverse(item)

Edit the mysite/urls.py file and make the following changes to it:

from django.contrib import admin
from django.urls import path

from blog.models import Post
import blog.views
from django.contrib.sitemaps.views import sitemap
from django.contrib.sitemaps import GenericSitemap

# START
from blog.sitemaps import StaticViewSitemap
# END

sitemaps = {
    'blog': GenericSitemap({
        'queryset': Post.objects.all(),
        'date_field': 'updated',
    }, priority=0.9),
    # START
    'static': StaticViewSitemap,
    # END
}

urlpatterns = [
    path('blog/<slug:slug>/', blog.views.detail, name='blog_detail'),
    # START
    path('about/', blog.views.about, name='about'),
    # END
    path('sitemap.xml', sitemap,
         {'sitemaps': sitemaps},
         name='django.contrib.sitemaps.views.sitemap'),
    path('admin/', admin.site.urls),
]

Add a new template file called about.html in the blog/templates/blog/ directory:

<h1>About</h1>

Edit the blog app views.py file and add a new view function called about to it:

def about(request):
    return render(request,
                  'blog/about.html')

Visit /sitemap.xml to see the result:

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    ...
    <url>
        <loc>http://example.com/about/</loc>
    </url>
</urlset>

These are the files we edited or created in this tutorial:

.
├── blog
│   ├── models.py
│   ├── sitemaps.py
│   ├── templates
│   │   └── blog
│   │       ├── about.html
│   │       └── detail.html
│   └── views.py
├── mysite
│   ├── settings.py
│   ├── urls.py