Wagtailでタグを実装する方法[Django]
Wagtailでタグ機能を実装する実用的な方法を解説します。
ブログやメディアの記事数が多くなると、それぞれの記事のトピックに合わせてタグをつけて整理したくなります。この記事では、Wagtailでタグを実装する実践的な方法を紹介します。
Wagtailでタグを実装する基本的な方法
まずはWagtailにおける最も基本的なタグの実装方法について解説します。
Wagtailでは、タグの機能を二つのDjango向けのモジュールを利用することで実現しています。一つは、django-taggitで、Djangoにおけるタグ機能実装の最もポピュラーなライブラリになります。もう一つは、django-modelclusterで、WagtailのAdmin上でのタグの管理に必要なParentalKey
というモデルを提供しています。
WagtailのPage
モデルを継承したモデルにタグをつけるには、django-taggit
のTag
モデルとPage
モデルを紐づけるためのモデルを作成する必要があります。taggit.models.TaggedItemBase
というモデルを継承した、リレーション用のモデルを作成し、django-modelcluster
のClusterTaggableManager
でPage
モデルのフィールドで紐付けします。
下記のサンプルでは、ArticlePage
モデル(メディアの記事ページ)にArticleTag
(記事のタグ)を紐付けしています。
# models.py
from modelcluster.fields import ParentalKey
from modelcluster.contrib.taggit import ClusterTaggableManager
from taggit.models import TaggedItemBase
class ArticleTag(TaggedItemBase):
# articlesアプリのArticlePageモデルにタグをつける
content_object = ParentalKey('articles.ArticlePage', on_delete=models.CASCADE, related_name='tagged_items')
class ArticlePage(Page):
...
tags = ClusterTaggableManager(through=ArticleTag, blank=True)
# promote_panelsにセットすることも可能
content_panels = Page.content_panels + [
...
FieldPanel('tags'),
]
これで、ArticlePage
のAdminにタグを入力するフィールドが表示されます。,
区切りで単語を入力するとそれぞれの単語がタグとして登録できます。
タグとPageモデルはMany-to-Many(多対多)の関係性で紐づいているため、テンプレートからは以下のような形で呼び出すことができます。
# article.html
<!-- この記事ページに紐づく全てのタグを表示し、タグページへのリンクを作成する -->
{% for tag in article.tags.all %}
<a href="/article/?tag={{ tag }}">{{ tag }}</a>
{% endfor %}
上記のリンク先に対応する、記事一覧ページでタグで絞り込みをできるようにしましょう。記事の一覧を表示するArticleIndexPage
を作成し、tag
パラメータがある場合にはそのタグがついた記事でフィルターをかけるようにします。
# models.py
class ArticleIndexPage(Page):
...
def get_context(self, request):
context = super().get_context(request)
# すべてのArticlePageを取得
articles = ArticlePage.objects.live().public()
# tagパラメータがURLに含まれていれば、それで記事を絞り込む
tag = request.GET.get('tag')
if tag:
articles = articles.filter(tags__name=tag)
context['articles'] = articles
return context
また、Djangoのクエリからはrelated_name
で紐づけたtagged_items
という名前経由で、そのタグがついているArticlePage
インスタンスの一覧を取得することができます。
>>> from articles.models import ArticleTag
>>> tag = ArticleTag.objects.first()
>>> tag # Wagtailという名前のタグ
<ArticleTag: Wagtail>
>>> tag.tagged_items.first() # Wagtailというタグがついた記事の最初のインスタンスを取得
<TaggedArticle: The Zen of Wagtail(日本語訳) tagged with Wagtail>
Tagモデルをカスタマイズする
上記の基本的な方法で作成したタグは、django-taggit
のTag
モデルで管理されます。このTag
モデルは、同じ方法で作成された別のモデルのTagと共通して管理されてしまうため、別のモデル用に作成したタグが、他のモデルのAdminでも入力候補として表示されてしまいます。
そのため実運用を考えると、Tag
モデルをカスタマイズして利用するのがおすすめです。
独自のタグを作成するためには、taggit.models.TagBase
を継承したTag
モデルを作成し、taggit.models.ItemBase
を継承したリレーション用のモデルを作成してタグ付けしたいモデルと紐付けを行います。
以下の例では、articles
アプリ内の記事モデル(ArticlePage
)用にArticleTag
というタグを実装しています。
# models.py
from django.db import models
from modelcluster.contrib.taggit import ClusterTaggableManager
from modelcluster.fields import ParentalKey
from taggit.models import TagBase, ItemBase
# ArticlePageに紐づけるタグモデル
# TagBaseを継承する
class ArticleTag(TagBase):
class Meta:
verbose_name = "article tag"
verbose_name_plural = "article tags"
# リレーション用のモデル
# ItemBaseを継承する
class TaggedArticle(ItemBase):
tag = models.ForeignKey(ArticleTag, related_name="tagged_articles", on_delete=models.CASCADE)
content_object = ParentalKey(
to='articles.ArticlePage',
on_delete=models.CASCADE,
related_name='tagged_items',
)
class ArticlePage(Page):
...
# リレーション用モデルに紐付けする
tags = ClusterTaggableManager(through='articles.TaggedArticle', blank=True)
TagをSnippetとして登録する
作成したTag
はSnippetとして登録しておくと便利です。利点は大きく二つあります。
一つは、Tagの追加・削除・制限ができるようになることです。Tag
はAdminのフォームからデータベースに追加すると、あとからそのタグを編集・削除できないようになっています。そのため、一度記事につけたタグを削除しても、そのタグはデータベースに残ったままになり、タグ一覧の情報を取得すると削除したタグも取得してしまいます。Tag
をSnippetとして登録することで、Snippetの管理画面からTag
を追加・削除・編集することができるようになります。
もう一つの利点は、slugをカスタマイズできるようになることです。タグ名が英語である場合には問題ないのですが、日本語のタグを作ろうとした場合にはslugがローマ字読みとして自動変換されてしまいます。例えば、『プログラミング』というタグを登録すると『puroguramingu』というslugが自動で登録されます。Tag
をSnippetとして登録しておくことで、Snippetの管理画面からslugも変更できるようになり、日本語のタグであっても自由なslugにすることができます。
Snippetの登録は通常の方法と同じで、register_snippet
を利用します。先ほど実装したArticleTag
をSnippetにしてみます。
# models.py
from taggit.models import TagBase
from wagtail.snippets.models import register_snippet
# Snippetとして登録する
@register_snippet
class ArticleTag(TagBase):
class Meta:
verbose_name = "article tag"
verbose_name_plural = "article tags"
これで管理画面からタグを追加・削除・編集することができます。
また、デフォルトの実装だと、タグは記事ページのAdminフォームから自由につけることができますが、意図しないタグをつけてしまうことがあります。記事ページから登録できるタグを制限したい場合には、Tag
モデルにfree_tagging = False
を指定しましょう。
# models.py
from taggit.models import TagBase
from wagtail.snippets.models import register_snippet
@register_snippet
class ArticleTag(TagBase):
free_tagging = False # 追加
class Meta:
verbose_name = "article tag"
verbose_name_plural = "article tags"
こうすることで、記事ページからタグを付ける場合には、Snippetの方から登録したタグの一覧から選択しなければいけなくなります。