はじめに
Wagtailが用意してくれているフィールドの中にStreamField
と呼ばれるものがある,これを用いると,複数の異なるブロックを好きな順序で組み合わせてコンテンツを構成していくことができる.例えば,Google Colaboratoryでコードブロックとテキストブロックを組み合わせていくようなイメージだ.
今回は,PostPage
クラスを追加し,その中でこのStreamField
を使ってみることにしよう.
StreamFieldをもつページクラスの追加
ということで,最初にStreamField
をもつ新しいページクラスPostPage
を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, StreamField
from wagtail.core.blocks import RawHTMLBlock, RichTextBlock
from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, PageChooserPanel, StreamFieldPanel
from wagtail.images.edit_handlers import ImageChooserPanel
class TopPage(Page):
...
class PlainPage(Page):
...
class ListPage(Page):
...
subpage_types = ['cms.ListPage', 'cms.PostPage']
...
class PostPage(Page):
cover_image = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
intro = models.CharField(max_length=250)
main_body = StreamField([
('rich_text', RichTextBlock(icon='doc-full', label='Rich Text')),
('html', RawHTMLBlock(icon='code', label='Raw Html')),
('code', CodeBlock(icon='code', label='Pretty Code')),
])
content_panels = Page.content_panels + [
ImageChooserPanel('cover_image'),
FieldPanel('intro'),
StreamFieldPanel('main_body'),
InlinePanel('related_pages', label="Related Pages"),
]
parent_page_types = ['cms.ListPage']
subpage_types = []
def get_top_page(self):
...
def get_breads(self):
...
def get_context(self, request):
...
class NavItems(Orderable):
...
class RelatedPages(Orderable):
...
これまでに定義してきたTopPage
,PlainPage
,ListPage
などとよく似ていることがわかる.なお,テンプレートと同じように,例えばPlainPage
をベースにして,他のページクラスをそれを継承したサブクラスとして定義する方法も考えられるが,ここではそれには立ち入らない.
これまでのページとの主な相違点として,main_body
がRichTextField
ではなくStreamField
で定義されていることを確認しよう.そして,content_panels
を見ると,StreamField
の編集パネルはStreamFieldPanel
と指定しておけばよさそうなこともわかる.これらのSteamField
に関係する箇所以外は特に説明の必要はないと思う.
StreamField
でコンテンツを構成していく際に利用可能なブロックは,引数の中のリストに列挙されている.それらのうち,RichTextBlock
とRawHTMLBlock
は,Wagtailがデフォルトで用意してくれているものであり,それらの引数icon
やlabel
は管理サイトの編集画面に表示されるアイコンやラベルを指定している.RichTextBlock
は,リッチテキスト形式で文書を投入するためのブロック,RawHTMLBlock
は,htmlのコードを直接そのまま投入するためのブロックである.
一方,CodeBlock
は(RawHTMLBlock
を継承して)独自に定義したブロックである.これについては,次の節で少し詳しく説明しよう.
独自ブロックの追加
StreamField
に独自ブロックを追加する例として,Google Code Prettifyを用いてシンタックスハイライトされたプログラムコードを表示するためのブロックCodeBlock
を追加してみよう.自作のブロックを含めるためには,そのブロックのクラスと,それを表示するためのテンプレートを用意する必要がある.
まず,CodeBlock
クラスを定義しよう.cms/blocks.pyというファイルを作成し,その中に下記のコードを書き込む.
from wagtail.core.blocks import RawHTMLBlock
class CodeBlock(RawHTMLBlock):
class Meta:
template = 'cms/blocks/code.html'
これを見ればわかるように,CodeBlock
は実質的にはデフォルトのRawHTMLBlock
と同じであるが,Meta
に指定してあるように,レンダリングの際に利用するテンプレートが変更されている.
次にこのテンプレートを用意する.templates/cms/の下にblocks/というディレクトリを追加し,その中にcode.htmlというファイルを作成して,下記のコードを書き込む.
<pre class="prettyprint">
<code>{{ value }}</code>
</pre>
このように,<pre>
タグにclass="prettyprint"
を指定しておくことでGoogle Code Prettifyが適用されるはずである.ただし,Google Code PrettifyのためのスクリプトをCDNから読み込んでおく必要がある.これは,templates/cms/base.htmlに下記のように追加した.
...
{% block extra_js %}
...
<script src="https://cdn.jsdelivr.net/gh/google/code-prettify@master/loader/run_prettify.js"></script>
{% endblock %}
この段階で,管理サイトにログインして,いずれかのListPage
クラスのページの下にPostPage
のページをいくつか追加しておこう.
ポストページのテンプレート
以上で準備が整ったので,PostPage
クラスのページの表示に進む.PostPage
はブログポストをイメージしたページなので,ジャンボトロンの中に投稿日時,もしくは更新日時を表示させるようにしてみよう.そこで,まずtemplates/cms/plain_page.htmlのheaderブロック内のテキスト部分を下記のようにheader_textブロックで囲んでおく.
...
{% block header %}
...
<div class="container">
{% block header_text %}
<h1 class="display-4">{{ page.title }}</h1>
<p class="lead">{{ page.intro }}</p>
{% endblock %}
</div>
</div>
{% endblock %}
...
次に,このtemplates/cms/plain_page.htmlを継承して,templates/cms/post_page.htmlを次のように作成した.
{% extends 'cms/plain_page.html' %}
{% load wagtailcore_tags %}
{% block header_text %}
{{ block.super }}
{% if page.last_published_at > page.first_published_at %}
<span>Last modified on {{ page.last_published_at|date }}</span>
{% else %}
<span>Posted on {{ page.first_published_at|date }}</span>
{% endif %}
{% endblock %}
{% block main_body %}
<div class="rich-text my-5">
{% for block in page.main_body %}
{% include_block block %}
{% endfor %}
</div>
{% 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.first_published_at %}
<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 %}
header_textブロックの中を見ると,何度か更新されているページは最終更新日,そうでないページは投稿日がそれぞれ表示されるようになっていることが見て取れる.
また,main_bodyブロックの中の最初の<div>
を見ると,テンプレートの中でStreamField
を処理する方法がわかる.具体的には,for
文でブロックを順に取り出して,include_block
すればよい.
実際にブラウザに表示させてみて,プログラムコードに無事シンタックスハイライトが適用されていることを確認しておこう.
RichTextフィールドでのコードハイライト
上ではStreamField
に独自ブロックを追加することでコードハイライトを行ってみたが,別の方法として,RichTextField
にコードハイライトの機能を盛り込むこともできる.ここでは,おまけとして,その方法も簡単に紹介しておこう.
Wagtailで用いられているリッチテキストエディタDraftailには,ここに説明されているように,独自の編集機能を追加できるようになっている.
ここでは,コードハイライトのための編集機能を追加してみる.具体的には,cms/wagtail_hooks.pyというファイルを作成し,下記の内容を書き込めばよい.この結果,リッチテキストエディタのツールバーにCodeというボタンが追加される.そして,ある範囲を選択してこのボタンを押すと,その範囲のテキストにこの編集機能が適用される.すなわち,そのテキストは,tag
に指定に従って,<pre class="prettyprint">
というタグで囲まれることになる.
import wagtail.admin.rich_text.editors.draftail.features as draftail_features
from wagtail.admin.rich_text.converters.html_to_contentstate import BlockElementHandler
from wagtail.core import hooks
@hooks.register('register_rich_text_features')
def register_code_feature(features):
feature_name = 'code'
type_ = 'code'
tag = 'pre class="prettyprint"'
control = {
'type': type_,
'label': 'Code',
'description': 'code',
'element': 'code',
}
features.register_editor_plugin(
'draftail', feature_name, draftail_features.BlockFeature(control)
)
features.register_converter_rule('contentstate', feature_name, {
'from_database_format': {tag: BlockElementHandler(type_)},
'to_database_format': {'block_map': {type_: tag}},
})
features.default_features.append(feature_name)
おわりに
今回は,StreamField
の便利さを紹介するとともに,RichTextField
に簡単な編集機能を追加する方法にも触れてみた.
次回は,公式サイトのチュートリアルYour first Wagtail siteでも扱われている,タグとカテゴリの機能を追加してみたいと思う.