はじめに
前回ブロクポストをイメージしたページクラスPostPage
を導入したので,今回は,それにタグとカテゴリの機能を追加してみよう.これは頻出トピックで,例えば,公式ドキュメントにあるYour first Wagtail siteやここのチュートリアルでも扱われている.
カテゴリモデルとタグモデルの追加
最初に,下記のように,カテゴリとタグのモデルの定義をcms/models.pyに追加し,それらをPostPage
に紐付けする.
...
from modelcluster.fields import ..., ParentalManyToManyField
from modelcluster.contrib.taggit import ClusterTaggableManager
from taggit.models import TaggedItemBase, Tag as TaggitTag
...
class PostPage(Page):
...
categories = ParentalManyToManyField(
'cms.PostCategory',
blank=True
)
tags = ClusterTaggableManager(
through='cms.PostTag',
blank=True
)
...
content_panels = Page.content_panels + [
...,
FieldPanel('categories', widget=forms.CheckboxSelectMultiple),
FieldPanel('tags'),
]
...
...
@register_snippet
class PostCategory(models.Model):
name = models.CharField(max_length=255)
panels = [
FieldPanel('name'),
]
def __str__(self):
return self.name
class Meta:
verbose_name = "Category"
verbose_name_plural = "Categories"
class PostTag(TaggedItemBase):
content_object = ParentalKey(PostPage, related_name='tagged_posts')
@register_snippet
class Tag(TaggitTag):
class Meta:
proxy = True
PostCategory
クラスから見ていこう.これはカテゴリを表すためのもので,Djangoデフォルトのdjango.db.models.Model
クラスを継承し,name
という文字列だけをフィールドに保持したシンプルなクラスになっている.@register_snippet
デコレータは,このクラスをスニペットとしてWagtailに登録してくれる.そして,その結果,管理サイトのスニペット編集画面でカテゴリの編集が行えるようになる.panels
の指定はその際に用いる編集パネルの設定である.
PostPage
クラスに追加されたフィールドcategories
を見ればわかるように,上で定義したカテゴリをページに多対多で紐付けする際にはParentalManyToManyField
を用いればよい.また,content_panels
の箇所では,引数にwidget=
を明示することで,チェックボックスでページとカテゴリの対応関係を指定できるようにしている.
次に,タグのモデルに目を移そう.Wagtailでは,django-taggitを用いてタグ付け機能を実装するのが標準になっている.PostTag
クラスの定義やtags
フィールドのPostPage
クラスへの追加の仕方がカテゴリの場合と異なっているのはそのためである(ここでは詳細は省略する).なお,ここまでの設定で,PostPage
の編集画面でそのページに自由に新しいタグを追加することができるようになっているはずである .
TaggitTag
という名称でインポートしたtaggit.models.Tag
クラスのproxyモデルとしてTag
クラスを定義し,それに@register_snippet
デコレータを適用しているのは,管理サイトのスニペット編集画面にカテゴリだけでなくタグも現れるようにしておくためである.これによって,スニペット編集画面を開くとそれまでに追加されたタグの一覧を確認することができるようになって便利だと思う.
この段階で管理サイトにログインし,スニペット編集画面からいくつかカテゴリを追加してみてほしい.そして,PostPage
クラスのページの編集画面から,カテゴリを選択したり,タグを追加したりしてみよう.
タグとカテゴリのサイドバーへの表示
続いて,カテゴリが指定されたり,タグが追加されたりしたPostPage
クラスのページを表示する際に,それらの情報がサイドバーに現れるようにしてみる.そのために,templates/cms/post_page.htmlを下のように修正した.
{% block side_bar %}
{{ block.super }}
<div class="card my-4">
<h4 class="card-header">
Categories
</h4>
<div class="card-body">
{% for category in page.categories.all %}
<a class="btn btn-outline-primary btn-sm m-1" href="{% pageurl top_page %}category/?category={{ category }}" role="button">
{{ category.name }}
</a>
{% endfor %}
</div>
</div>
<div class="card my-4">
<h4 class="card-header">
Tags
</h4>
<div class="card-body">
{% for tag in page.tags.all %}
<a class="btn btn-outline-primary btn-sm m-1" href="{% pageurl top_page %}tag/?tag={{ tag }}" role="button">
{{ tag.name }}
</a>
{% endfor %}
</div>
</div>
{% endblock %}
カテゴリ,タグともにBbootstrapのカード内にボタンとして表示されるようにしてあることがわかる.なお,ボタンのリンク先は,カテゴリの場合だと,
top_pageのURL/category/?category=カテゴリの文字列
タグの場合だと,
top_pageのURL/tag/?tag=タグの文字列
となっていることがわかる.ここで,これらを引き受けるためのページとして,対応するTopPage
のページの子要素としてListPage
クラスのページをカテゴリ用とタグ用にそれぞれ1つずつ作成しておこう(編集画面のPROMOTEタブでSlug文字列をそれぞれcategoryおよびtagに設定しておくこと).
同一カテゴリ・タグを付与されたページリストの表示
続いて,上で作成した,ボタンのリンク先のListPage
インスタンスが表示された際に,指定されたカテゴリやタグが付与されたPostPage
のページが一覧表示されるように,ListPage
の定義にちょとした細工を加えよう.具体的には,get_context()
メソッドを下のように修正する.
class ListPage(Page):
...
def get_context(self, request):
context = super().get_context(request)
context['top_page'] = self.get_top_page()
context['breads'] = self.get_breads()
if self.related_pages.count():
context['page_list'] = [item.page for item in self.related_pages.all()]
else:
tag = request.GET.get('tag')
category = request.GET.get('category')
if category:
context['category'] = category
context['page_list'] = PostPage.objects.descendant_of(self.get_top_page()).filter(categories__name=category).live().order_by('-first_published_at')
elif tag:
context['tag'] = tag
context['page_list'] = PostPage.objects.descendant_of(self.get_top_page()).filter(tags__name=tag).live().order_by('-first_published_at')
else:
context['page_list'] = self.get_children().live().order_by('-first_published_at')
return context
if
以下で,条件に応じてコンテキストに含めるpage_list
を変更していることがわかる.詳細は上のコードで確認してもらえればと思うが,ポイントは,GETメソッドのクエリにcategory
やtag
が指定されていた場合に,それが付与されているPostPage
インスタンスのみを取得して投稿日の降順に並べ替えたものをpage_list
としている点である.また,指定されたカテゴリやタグの文字列自身もコンテキストに含めている.
したがって,上記のボタンを押してcategory
やtag
のクエリ付きのURLに飛ばされると,対応するListPage
がレンダリングされる前にこの修正されたget_context()
メソッドが呼ばれ,page_list
に,指定されたカテゴリやタグが付与されたPostPage
インスタンスのみが入るので,結果としてそれらが一覧表示されることになる.
ついでに,templates/cms/list_page.htmlに下記の修正を加えて,どのようなカテゴリ,あるいはタグに対応するページが表示されているのかがわかるように,ジャンボトロンにそれらの情報を表示するようにしておこう.
{% block header_text %}
{{ block.super }}
{% if category %}
<h5>Posts in category: {{ category|upper }}</h5>
{% elif tag %}
<h5>Posts given tag: {{ tag|upper }}</h5>
{% endif %}
{% endblock %}
おわりに
今回はPostPage
のページにカテゴリとタグの情報を追加する方法について簡単に見てきた,またその過程で,Wagtailのスニペットの扱い方にも触れた.
次回以降の予定は今のところ未定だが,興味深いトピックはまだまだ残っているので,機会があればいつかまた続編を書きたいと思う.