はじめに
Wagtailでもページをレンダリングする際にテンプレートにコンテキスト情報が渡されている.前回までに見てきたような単純な使い方では特にそれを意識する必要はなかったが,コンテキストに独自の情報を追加できれば便利なこともあるだろう.Wagtailでは,それを簡単に実現するための方法が用意されている.
今回は,ListPage
クラスを追加するとともに,コンテキストに情報を追加してからページをレンダリングするための標準的な流れを押さえよう.
ページクラスの追加
今回も新しいページクラス(ListPage
クラス)の定義を1つ追加する.そのため,最初に,cms/models.pyを下のように拡張しよう.
from django.db import models
from modelcluster.fields import ParentalKey
from wagtail.core.models import Page, Orderable
from wagtail.core.fields import RichTextField
from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, PageChooserPanel
from wagtail.images.edit_handlers import ImageChooserPanel
class TopPage(Page):
...
subpage_types = ['cms.ListPage', 'cms.PlainPage']
class PlainPage(Page):
...
class ListPage(Page):
cover_image = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
intro = models.CharField(max_length=255)
main_body = RichTextField(blank=True)
content_panels = Page.content_panels + [
ImageChooserPanel('cover_image'),
FieldPanel('intro'),
FieldPanel('main_body', classname="full"),
InlinePanel('related_pages', label="Related Pages"),
]
parent_page_types = ['cms.TopPage', 'cms.ListPage']
def get_top_page(self):
pages = self.get_ancestors().type(TopPage)
return pages[0]
class NavItems(Orderable):
...
class RelatedPages(Orderable):
base_page = ParentalKey(Page, related_name='related_pages')
page = models.ForeignKey(
Page,
on_delete=models.CASCADE,
related_name='+'
)
panels = [
PageChooserPanel('page'),
]
ListPage
クラスとそこで用いる新しいOrderable
のサブクラスRelatedPages
の定義が追加されているのがわかる.RelatedPages
は,ListPage
クラスのページに表示する関連ページをリストアップしておくための道具立てである.前回取り上げたNavItems
とほぼ同じなので詳しい説明は不要だろう.
ListPage
クラスの定義は,このRelatedPages
が紐付けられていることを除くとPlainPage
とほぼ等しい.parent_page_types
の指定によると,このページはTopPage
もしくはListPage
自身の子要素として位置づけることができるようになっている.
なお,ListPage
クラスの定義が追加されたことを受けて,TopPage
クラスにも子要素の指定を追加した.
コンテキストへの独自情報の追加
続いて,今回の本題に入ろう.すなわち,テンプレートに渡すコンテキストに独自の情報を追加してみることにする.これはPage
クラスのget_context()
メソッドをオーバーライドすることによって簡単に実現することができる.ListPage
を題材に,具体的な例を見てみよう.
class ListPage(Page):
...
def get_breads(self):
breads = self.get_ancestors().descendant_of(self.get_top_page(), True)
return breads
def get_context(self, request):
context = super().get_context(request)
context['top_page'] = self.get_top_page()
context['breads'] = self.get_breads()
context['page_list'] = [item.page for item in self.related_pages.all()]
return context
get_context()
メソッドについて説明する前に,先に簡単にget_breads()
メソッドを見ておこう.これは,自分から親子関係を遡ってトップページにたどり着くまでのページをすべて取得し,逆順にトップページから並べたリストを返すメソッドになっている.これは,後で実装するパンくずリストのために利用する.
続いて,get_context()
メソッドを見てほしい.スーパークラス(すなわちデフォルトのPage
クラス)のget_context()
メソッドを呼んだ後,それが返す辞書context
に新たな要素を追加していることがわかる.具体的には,get_top_page()
メソッドで取得されるトップページ,get_breads()
メソッドで得られるパンくずリスト情報,そして,このページに紐付けられているRelatedPages
のリストである.こうすることで,テンプレート側からそれらの情報に簡単にアクセスできるようになる.
実は,これらの情報は,前回のナビゲーションバーの実装で見たように,あえてコンテキストに含めなくともテンプレートからアクセスできるものである.そのため,get_context()
メソッドの威力を実感しにくいかもしれない.しかし,このメソッドは,request
を引数にとっていることからもわかるように,テンプレートから直接アクセスするのが難しい情報をコンテキストに含めることもできる(次回以降にそのような例にも触れてみたいと思う).
なお,テンプレートの統一のため,TopPage
クラスとPlainPage
クラスにも,下のように,同様の拡張を施しておこう.
class TopPage(Page):
...
def get_context(self, request):
context = super().get_context(request)
context['top_page'] = self.get_top_page()
return context
class PlainPage(Page):
...
def get_breads(self):
breads = self.get_ancestors().descendant_of(self.get_top_page(), True)
return breads
def get_context(self, request):
context = super().get_context(request)
context['top_page'] = self.get_top_page()
context['breads'] = self.get_breads()
return context
リストページのテンプレート
続いて,新たに追加したListPage
クラスのページを表示できるようにしよう.そのために,次のようなテンプレート,templates/cms/list_page.htmlを用意した.
{% extends 'cms/plain_page.html' %}
{% load wagtailcore_tags %}
{% block main_body %}
{{ block.super }}
{% for item_page in page_list %}
<hr>
<div class="my-4">
<a href="{% pageurl item_page %}">
<h4>{{ item_page.title }}</h4>
{{ item_page.specific.intro }}
</a>
{% if item_page.last_published_at <= item_page. %}
<p>Posted on {{ item_page.first_published_at|date }}</p>
{% else %}
<p>Last modified on {{ item_page.last_published_at|date }}</p>
{% endif %}
</div>
{% endfor %}
<hr>
{% endblock %}
templates/cms/plain_page.htmlを継承した上で,main_bodyブロック内でリッチテキストを表示した後にコンテキストのpage_list
に含まれるページの情報を1つずつ順に表示していることがわかる.なお,first_published_at
やlast_published_at
はデフォルトのPage
クラスが持っているフィールドであり,それぞれ自動的に初公開日と最終更新日の値が入る.
この例のように,コンテキストに追加した情報の値にはそのキーを用いて直接アクセスできるようになる.したがって,templates/cms/plain_page.htmlの記述も少し単純化することができる.具体的には,page.get_top_page
となっている箇所をすべてtop_page
に変更しておこう.
パンくずリストの実装
次に,コンテキストに追加した情報breads
を用いて,パンくずリストを実装しよう.すべてのページに表示されるように,templates/cms/plain_page.htmlの中に表示用のコードを追加する.具体的には,mainブロック内のmain_bodyブロックの直前に新たにbreadcrumbブロックを下記のように挿入した.
...
{% block main %}
...
{% block breadcrumb %}
<nav class="my-2">
<ol class="breadcrumb">
{% for bread in breads %}
<li class="breadcrumb-item">
<a href="{% pageurl bread %}">
{{ bread.title }}
</a>
</li>
{% endfor %}
<li class="breadcrumb-item active">
{{ page.title }}
</li>
</ol>
</nav>
{% endblock %}
{% block main_body %}
....
{% endblock %}
....
{% endblock %}
....
コンテキストからbreads
というキーでパンくずリストに表示するページのリストにアスセスし,そこから1つずつページを取り出して,そのタイトルをラベルにしたリンクを表示していることがわかる.
おわりに
今回は,ページをレンダリングする際にテンプレートに渡されるコンテキストに独自の情報を追加するための簡単な方法を紹介し,それを利用してパンくずリストを実装してみた.
次回は,最後のページクラスPostPage
を追加するとともに,その中で用いるStraemField
という便利なフィールドの使い方に慣れよう.