Django ブログサイト sitemaps.py サイトマップ生成

2020/07/04 (更新:2020/11/19)

Django フレームワークの機能を使用し、本サイトのサイトマップを生成します。
投稿者毎のトップページとブログ記事ページを出力します。

システム設定に、Django のアプリケーションとサイトの設定を追加します。

settings

INSTALLED_APPS = [
    ...
    'django.contrib.sites',
    'django.contrib.sitemaps',
]

SITE_ID = 1

共通クラスです。
Sitemapを継承し実装します。
get_urlsをオーバーライドし、多言語対応のhreflang要素を追加します。

BlogSitemap

class BlogSitemap(Sitemap):

    def reverse(self, obj):
        pass

    def location(self, obj):
        path = self.reverse(obj)
        if obj['language_code'] == settings.LANGUAGE_CODE:
            return path
        else:
            return f'/{obj["language_code"]}{path}'

    def get_urls(self, page=1, site=None, protocol=None):
        urls = super().get_urls(page, site, protocol)

        # alternate
        kwalternates_w = {}
        for url in urls:
            obj = url['item']
            alternates = kwalternates_w.get(obj['key'], [])
            kwalternates_w[obj['key']] = alternates
            alternates.append({
                'lang_code': obj['language_code'],
                'location': url['location'],
            })
        # alternate x-default
        kwalternates = {}
        for key, alternates in kwalternates_w.items():
            if len(alternates) < 2:
                continue
            alternate_default = next((a for a in alternates if a['lang_code'] == 'en'), None)
            if not alternate_default:
                alternate_default = next((a for a in alternates if a['lang_code'] == settings.LANGUAGE_CODE), None)
            alternates.append({
                'lang_code': 'x-default',
                'location': alternate_default['location'],
            })
            kwalternates[key] = alternates

        for url in urls:
            obj = url['item']
            alternates = kwalternates.get(obj['key'])
            if alternates:
                url['alternates'] = alternates
        return urls

多言語対応のhreflangタグを出力するため、Django のテンプレートを置き換えます。

sitemap.xml

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
{% spaceless %}
{% for url in urlset %}
  <url>
    <loc>{{ url.location }}</loc>
    {% if url.lastmod %}<lastmod>{{ url.lastmod|date:"Y-m-d" }}</lastmod>{% endif %}
    {% if url.changefreq %}<changefreq>{{ url.changefreq }}</changefreq>{% endif %}
    {% if url.priority %}<priority>{{ url.priority }}</priority>{% endif %}
    {% for alternate in url.alternates %}
    <xhtml:link rel="alternate" hreflang="{{ alternate.lang_code }}" href="{{ alternate.location }}"/>
    {% endfor %}
  </url>
{% endfor %}
{% endspaceless %}
</urlset>

投稿者毎のトップページを出力するクラスです。
共通クラスBlogSitemapを継承し実装します。
投稿記事テーブルを投稿者毎に集約し最終の更新日時を出力します。

AuthorSitemap

class AuthorSitemap(BlogSitemap):
    changefreq = "always"
    priority = 0.5

    def items(self):
        items = models.PostContent.objects.values_list('post__author__user__username', 'language_code') \
                .annotate(updated_date=Max('post__updated_date')).order_by('post__author_id')
        return [{'key':item[0], 'language_code':item[1], 'updated_date':item[2]} for item in items]

    def reverse(self, obj):
        return reverse('blog:index', args=(obj['key'],))

    def lastmod(self, obj):
        return obj['updated_date']

投稿記事ページを出力するクラスです。
共通クラスSitemapを継承し実装します。

PostSitemap

class PostSitemap(BlogSitemap):
    changefreq = "never"
    priority = 1.0

    def items(self):
        items = models.PostContent.objects.values_list('post__id', 'post__author__user__username', 'language_code') \
                .annotate(updated_date=Max('post__updated_date')).order_by('post__author_id')
        return [{
                'key': f'{item[0]},{item[1]}',
                'id': item[0],
                'auth_name': item[1],
                'language_code': item[2],
                'updated_date': item[3],
            } for item in items]

    def reverse(self, obj):
        return reverse('blog:detail', args=(obj['auth_name'], obj['id'],))

    def lastmod(self, obj):
        return obj['updated_date']