Djangoで、月別アーカイブ欄を作る

2019-08-16 / PythonDjango

概要

Djangoで、ブログや日記によくある月別アーカイブ欄を作成していきます。

私の日記でいうと、次の赤線で囲んだ部分ですね。 月別アーカイブ

Githubにソースコードを置いているので、欲しい方はダウンロードなどしてください。

モデルの作成

モデルは何でも良いのですが、日付のフィールドを持たせることを忘れないようにしましょう。

from django.db import models
from django.utils import timezone


class Post(models.Model):
    title = models.CharField('タイトル', max_length=255)
    text = models.TextField('本文')
    created_at = models.DateTimeField('作成日', default=timezone.now)

    def __str__(self):
        return self.title

テンプレートタグの作成

月別アーカイブ欄ですが、Djangoで、全てのページにカテゴリ一覧を表示するでやったように、テンプレートタグを使って作成していきます。

アプリケーション内にtemplatetagsディレクトリを作り、app_tags.pyを作ります(ファイル名は好きにつけてください)。中身は次のようにしておきます。

from django import template
from django.utils import timezone
from app.models import Post

register = template.Library()


@register.inclusion_tag('app/includes/month_links.html')
def render_month_links():
    return {
        'dates': Post.objects.filter(created_at__lte=timezone.now()).dates('created_at', 'month', order='DESC'),
    }

Djangoで、全てのページにカテゴリ一覧を表示するで紹介した、register.inclusion_tagデコレータをつけています。タグの内容を、テンプレートタグを使って描画できるものでしたね。

では、Post.objects.dates('created_at', 'month', order='DESC')というコードを説明しましょう。この処理は、次のような結果を返します。

<QuerySet [
    datetime.date(2100, 1, 1), 
    datetime.date(2019, 8, 1) , 
    datetime.date(2019, 7, 3), 
    datetime.date(2019, 2, 27), 
    datetime.date(2018, 12, 1)
]>

Postが存在する月の、日付データのQuerySetを返すのです。今回は未来の日付のPostを表示したくないので、.filter(created_at__lte=timezone.now())として現在時間までの記事だけに絞り込んでいる、という訳です。未来記事も表示したければ、filterは外してください。

こういった処理は、Djangoで、カスタムマネージャーを使うで紹介したように、マネージャークラス側で現在時間までの記事だけを返す処理を定義しておくと捗ります。

from django.db import models
from django.utils import timezone


class PostQuerySet(models.QuerySet):

    def published(self):
        return self.filter(created_at__lte=timezone.now())


class Post(models.Model):
    title = models.CharField('タイトル', max_length=255)
    text = models.TextField('本文')
    created_at = models.DateTimeField('作成日', default=timezone.now)

    objects = PostQuerySet.as_manager()

    def __str__(self):
        return self.title

すると、テンプレートタグ内でのfilter()といった部分を綺麗に書き換えることができます。

@register.inclusion_tag('app/includes/month_links.html')
def render_month_links():
    return {
        'dates': Post.objects.published().dates('created_at', 'month', order='DESC'),
    }

テンプレートファイルを作る

上のテンプレートタグで呼び出される、月別アーカイブ部分となるテンプレートファイルを作りましょう。app/includes/month_links.htmlです。

{% regroup dates by year as dates_by_year %}
<ul>
    {% for month in dates_by_year %}
        <li>{{ month.grouper }}年
            <ul>
                {% for d in month.list %}
                    <li>{{ d|date:'F' }}</li>
                {% endfor %}
            </ul>
        </li>
    {% endfor %}
</ul>

regroupという、少し複雑な組み込みテンプレートタグを利用しています。これも実際の動作を見たほうがわかりやすいでしょう。

datesオブジェクトの中身は、上で説明したように次のような内容です。

<QuerySet [
    datetime.date(2019, 8, 1) , 
    datetime.date(2019, 7, 3), 
    datetime.date(2019, 2, 27), 
    datetime.date(2018, 12, 1)
]>

各要素はdatetime.dateオブジェクトなので、yearmonthdayといった属性を持っています。今回は、それのyearで更にグループ化しているのです。結果としては次のような内容になります。

[
    {'grouper': 2019, 'list': [datetime.date(2019, 8, 1), datetime.date(2019, 7, 3), datetime.date(2019, 2, 27)]},
    {'grouper': 2018, 'list': [datetime.date(2018, 12, 1)]}

]

書式は。regroup データの集まり グループ化する属性名 結果できるリストの名前です。

後は、作成したrender_month_linksタグをテンプレートで呼び出すだけです。

<h2>Archives</h2>
{% render_month_links %}

これで月別アーカイブ部分は作成できました。しかし殆どの場合、月別アーカイブはリンクになっていて、クリックでその年や月のデータ一覧が表示されます。こういった機能は、Django、generic.datesモジュールを使うで紹介したgeneric.datesが便利で、一緒に使うと良いでしょう。「概要」にあるGithubのサンプルソースコードはgeneric.datesも使い、月別アーカイブもリンクにしているので、参考にしてみてください。

この記事の関連記事

Django、generic.datesモジュールを使う

2018-11-07 / PythonDjango

- Djangoのクラスビューには「generic.dates」という日付に関連したクラスビューもあります。今回は、これを紹介していきます。

Djangoで、カスタムマネージャーを使う

2018-12-27 / PythonDjango

- Djangoで、マネージャーのカスタマイズについて紹介しています。発行される初期クエリの変更などに便利です。

Djangoで、全てのページにカテゴリ一覧を表示する

2019-08-14 / PythonDjango

- Webサイトでは、全てのページに配置したい情報が出てきます。例えばサイドバーのカテゴリ一覧とか、ヘッダー内にあるサイト内検索といったコンテンツです。今回は、それらをDjangoで実装していきます。

コメント欄

記事にコメントする

まだコメントはありません。