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

2019-08-14 / PythonDjango

概要

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

私の日記帳ではサイドバーにカテゴリがありますが、これはどのページに行っても表示されます。
カテゴリ

このブログだと、上部にサイト内検索がありますね。 これもどのページに行っても表示されます。

これらは、Category.objects.all()とかSearchForm(request.GET)みたいな感じで作っています。つまり、毎回テンプレートファイルへこれらのオブジェクトを渡す必要があるのです。

今回はカテゴリ一覧を毎回テンプレートファイルへ渡すように実装していきます。

サンプルモデル

次のようなモデルがあるとします。

from django.db import models


class Category(models.Model):
    name = models.CharField('カテゴリ名', max_length=255)

    def __str__(self):
        return self.name


class Post(models.Model):
    title = models.CharField('タイトル', max_length=255)
    text = models.TextField('本文')
    category = models.ForeignKey(Category, verbose_name='カテゴリ', on_delete=models.PROTECT)

    def __str__(self):
        return self.title

ブログっぽいモデルです。記事があり、記事はカテゴリを指定できます。このカテゴリを、全てのページのサイドバーにでも表示をしていきたいとします。

サンプルテンプレート

テンプレートファイルを用意します。開発時は、全てのページに共通となるベーステンプレートファイルを作ることが多く、他のテンプレートファイルはこのベーステンプレートを継承することになります。どのページにも表示したいデータ...今回ならばカテゴリ一覧は、ベーステンプレートファイルで定義することになるでしょう。

base.htmlといった名前で、次のようなものを作っておきます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width">
    <title>MySite</title>
    <style>
        * {
            margin: 0;
        }

        body {
            width: 960px;
            margin: 0 auto;
            display: grid;
            grid-template-columns: 1fr 600px 60px 300px 1fr;
            grid-template-rows: [head] 100px [main side] auto;
        }

        header {
            grid-column: 2 / -2;
            grid-row: head;
            align-self: center;
        }

        main {
            grid-column: 2;
            grid-row: main;
        }

        aside {
            grid-column: 4;
            grid-row: side;
        }

    </style>
</head>
<body>

<header>My Site</header>

<main>{% block content %}{% endblock %}</main>

<aside>
    <h2>Category</h2>
    <!-- ここにカテゴリの一覧を置きたい -->
</aside>

</body>
</html>

main要素は、メインコンテンツですね。{% block content %}{% endblock %}としておいて、他のテンプレートファイルで上書きできるようにしています。今回重要なのはaside要素の中で、ここにカテゴリの一覧を定義したいのですが、それをどうやって書くか?というのが主題です。

全てのビューでカテゴリ一覧を作る

1つ目の方法は、全てのビューからテンプレートファイルへカテゴリ一覧を渡す方法です。

例えばクラスベースビューならば、次のような感じでテンプレートファイルへ渡すデータを追加できます。

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['category_list'] = Category.objects.all()
        return context

base.htmlでは、次のようにしてカテゴリを取り出せます。

<aside>
    <h2>Category</h2>
    <!-- ここにカテゴリの一覧を置きたい -->
    <ul>
        {% for category in category_list %}
            <li><a href="">{{ category }}</a></li>
        {% endfor %}
    </ul>
</aside>

全てのビューに手作業で追加するのが大変なので、よくやるのが、次のようなMixinを作ることです。


class PostContextMixin(generic.base.ContextMixin):

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['category_list'] = Category.objects.all()
        return context

そして、このMixinをすべてのビューで継承します。次のような感じですね。

class PostList(PostContextMixin, generic.ListView):
    model = Post


class PostDetail(PostContextMixin, generic.DetailView):
    model = Post

この方法は、どうしてもビュー側のコードが増えます。そのアプリケーション内の、一部のビューにだけ適用したいといった場合には非常に有効ですが、全てのビューに適用したい場合は、他の方法もあります。

context_processorsを使う

まず、settings.pyに次のコードを追加します。

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'app.context_processors.common',  # 追加
            ],
        },
    },
]

アプリケーション内に、context_processors.pyを作ります。views.pyとかと同じ階層です。中身は、次のようにします。

from .models import Category


def common(request):
    context = {
        'category_list': Category.objects.all()
    }
    return context

テンプレートファイルは、先ほどと同様です。

<aside>
    <h2>Category</h2>
    <!-- ここにカテゴリの一覧を置きたい -->
    <ul>
        {% for category in category_list %}
            <li><a href="">{{ category }}</a></li>
        {% endfor %}
    </ul>
</aside>

これも上手く動作します。この方法は、Djangoプロジェクト内の全てのアプリケーションを含む、全てのテンプレートファイルへデータを渡したい場合に有効です。ただ全てのDjangoあぷりけーしょんに適用されるので、場合によっては大げさに感じるでしょう。

テンプレートタグを使う

まず、アプリケーションのディレクトリ内にtemplatetagsディレクトリを作り、中にapp_tags.pyというファイルを作ります。ファイル名は、好きなもので大丈夫です。中身は次のようにします。

from django import template
from app.models import Category

register = template.Library()


@register.inclusion_tag('app/includes/category_links.html')
def render_category_links():
    return {
        'category_list': Category.objects.all(),
    }

register.inclusion_tagデコレータを使うと、そのテンプレートタグの内容をテンプレートファイルを使って表示できます。上のコードならば、Category.objects.all()category_listという名前で、app/includes/category_links.htmlに渡せるということです。

app/includes/category_links.htmlは、次のような感じにしておきます。今までbase.htmlに書いていた内容を、移動させるだけです。

<ul>
    {% for category in category_list %}
        <li><a href="">{{ category }}</a></li>
    {% endfor %}
</ul>

後は、このテンプレートタグをbase.html側で呼び出すだけです。

{% load app_tags %}
<!DOCTYPE html>
...
...
<aside>
    <h2>Category</h2>
    <!-- ここにカテゴリの一覧を置きたい -->
    {% render_category_links %}
</aside>

この方法は結構柔軟なので、個人的にはオススメです。ビューが見づらくなる訳でもなく、使いたい場所にだけ使えます。

この記事の関連記事

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

2019-08-16 / PythonDjango

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

コメント欄

記事にコメントする

うちだ

udemy受講者です。 丁度カテゴリ一覧の表示について悩んでいたところなので、助かりました。

教材ファイルをダウソしても何故テンプレートタグがあるのか分かりませんでしたが、 ようやく理解出来ました。

コンテキストプロセッサーではなく、テンプレートタグにて 表示してみたいと思います。

今後ともブログ応援しております。

コメントに返信する