Wagtailのsnippetの基本的な使い方

Wagtailのサイト上で使い回すコンテンツを設定する『Snippet』の基本的な使い方について解説します。

Wagtailのsnippetの基本的な使い方

Snippetとは、『断片』を意味する英単語ですが、プログラミングにおいては、再利用可能な小さなコードのまとまりを意味することがあります。

WagtailにおけるSnippetは、Pageの中で利用されるパーツを構成するための仕組みです。例えば、記事の中で利用する広告を登録したり、目を引くような特殊な記事内のバナーを設定したりすることができます。それらのSnippetは、それ単体ではPageを構成しませんが、Pageの中で何度でも再利用できるパーツとしてAdminから登録できるようになっています。

この記事では、WagtailにおけるSnippetの基本的な使い方を紹介します。

Template TagとしてSnippetを使う方法

まずは簡単なSnippetの例を見て、Snippetの基本を理解しましょう。

一般的なブログサイトで、サイドバーに画像形式のバナーを差し込む場合を想定します。この場合、バナーの画像の登録と、そのリンク先のURLを登録する必要があります。今回は簡便に、登録したすべてのバナーをサイドバーに表示させるような例を示します。

このSnippetは以下のように実装することができます。adsというアプリフォルダの中で作業を進めていることを想定しています。

# ads/models.py

from django.db import models
from wagtail.admin.edit_handlers import FieldPanel
from wagtail.snippets.models import register_snippet


@register_snippet
class Ad(models.Model):
    name = models.CharField(max_length=255)
    url = models.URLField(null=True, blank=True)
    image = models.ImageField(upload_to='image/ads')

    panels = [
        FieldPanel('name'),
        FieldPanel('url'),
        FieldPanel('image'),
    ]

    def __str__(self):
        return self.name

モデル自体は通常のDjangoのモデルと同じです。WagtailのAdminからそれぞれ入力できるようにFieldPanelを設定しています。@register_snippetを利用することで、このモデルをSnippetとして登録することができます。

今度はこのSnippetをTemplate Tagとして利用できるようにしていきます。adsアプリ内にtemplatetagsというフォルダを作成し、その中でTemplate Tagを追加していきます。

# ads/templatetags/ad_tags.py

from django import template
from ads.models import Ad

register = template.Library()

@register.inclusion_tag('ads/tags/ads.html', takes_context=True)
def ads(context):
    return {
        'ads': Ad.objects.all(),
        'request': context['request'],
    }

@register.inclusion_tag()は二つの引数を取ります。一つ目がTemplate Tagで利用するテンプレート名で、二つ目はrequest contextを利用するかどうかのBooleanです。ここでtakes_context=Trueを指定しないと、Wagtailの他のTemplate Tagが利用できない場合があるため、基本的にはこの設定が良いでしょう。

今回は簡便に、Ad.objects.all()ですべての登録したAdインスタンスを取得していますが、例えば表示のオンオフなども登録できるようにしてフィルタリングすることも可能です。

続いて、テンプレート側を作成しましょう。adsアプリ内のtemplates/adsフォルダの中に、tags/ads.htmlを作成します。

<!-- ads/templates/ads/tags/ads.html -->

{% for ad in ads %}
<section>
    <a href="{{ ad.url }}">
        <img src="{{ ad.image.url }}">
    </a>
</section>
{% endfor %}

このテンプレートファイルには、ad_tags.pyで指定したパラメータが渡されますので、その情報を利用して表示方法を指定します。今回は登録したAdのリンク付きの画像を並べるだけにしてあります。

最後に、このTemplate Tagを指定の箇所から呼び出してみます。以下の例では、homeアプリのトップページで使用する例です。

<!-- home/templates/home.html -->

{% load wagtailcore_tags ad_tags %} <!-- 登録したad_tagsをロードする -->

{% block content %}

{% ads %} <!-- ここにads.htmlが表示される -->

{% endblock %}

以上が、SnippetをTemplate Tagとして呼び出す方法です。

Adminからは以下のように、SnippetsメニューからAds snippetを登録できるようになっており、Adモデルで作成したフィールドに情報を入力できるようになっています。

wagtail-snippets-sample1

wagtail-snippets-sample2

Pageに紐づくSnippetを使う方法

Template TagとしてSnippetを使う方法では、Snippetを登録したら固定のテンプレートで表示されます。そのため、使い所としてはヘッダーやサイドバーなど、共通で利用するテンプレート内で利用するか、トップページの一部分だけで利用する場合などが考えられます。

一方で、それぞれの記事などでSnippetを個別に指定できるようにしたい場合もあります。その場合は、Pageモデルに紐づくようにSnippetを登録し、Adminから表示するSnippetを選択できるようにします。

以下の例では、一般的によく利用されるカテゴリを実装してみます。ブログサイトなどで個別の記事にカテゴリを割り当てる場合を想定しています。この場合、BlogCategoryモデルを作成してSnippetとして登録し、BlogArticleモデルに紐付けします。

# blogs/models.py

from django.db import models
from wagtail.admin.edit_handlers import FieldPanel
from wagtail.core.models import Page
from wagtail.snippets.models import register_snippet
from wagtail.snippets.edit_handlers import SnippetChooserPanel


# カテゴリを表現するモデル、Snippetとして登録
@register_snippet
class BlogCategory(models.Model):
    name = models.CharField("Category name", max_length=50)

    panels = [
        FieldPanel("name"),
    ]

    def __str__(self):
        return self.name


class BlogArticle(Page):
    ...

    # カテゴリを紐づける、blogアプリのBlogCategoryモデルを指定
    category = models.ForeignKey(
        'blog.BlogCategory',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    content_panels = Page.content_panels + [
        ...
        SnippetChooserPanel('category'), # AdminでSnippetを選択するパネルを追加
    ]

カテゴリの名前を登録するモデルを定義し、models.ForeignKeyPageモデルと紐付けをします。SnippetChooserPanelは、Admin上で以下の画像のように登録済みのSnippetから選択できるようにする設定です。

SnippetChooserPanelの表示

テンプレート上からは通常通り、{{ self.category.name }}の形で情報を呼び出すことができます。

Snippetを検索できるようにする

Snippetが増えてくると、目的のSnippetを探すのに時間がかかるようになります。そこで、特定の情報でSnippetを検索できるようにしましょう。

やることは大きく二つです。検索可能にしたいモデルでwagtail.search.index.Indexedを継承し、search_fieldsで検索対象のフィールドを指定するだけです。

試しに、Template Tagを利用した方法のときに作成したAdモデルを検索可能にしてみます。

# ads/models.py

from django.db import models
from wagtail.admin.edit_handlers import FieldPanel
from wagtail.snippets.models import register_snippet

from wagtail.search import index # 追加


@register_snippet
class Ad(index.Indexed, models.Model): # index.Indexedを追加
    name = models.CharField(max_length=255)
    url = models.URLField(null=True, blank=True)
    image = models.ImageField(upload_to='image/ads')

    panels = [
        FieldPanel('name'),
        FieldPanel('url'),
        FieldPanel('image'),
    ]

    # search_fieldsを追加、partial_match=Trueは部分一致検索の設定
    search_fields = [
        index.SearchField('name', partial_match=True),
    ]

    def __str__(self):
        return self.name

これで、Admin上でSnippetが検索可能になります。この実装で、Adminに検索ボックスが表示されます。

Snippetの検索ボックス

検索ボックスにテキストを入力することで、条件にマッチしたSnippetが表示されます。

Snippetの検索結果

Snippetにタグをつける

Snippetにも、Pageモデル同様にタグをつけることができます。 Snippetに登録した広告の管理などでタグをつけて整理するなどの便利な活用方法があります。

上記のAdモデルを修正してみます。タグ自体のモデルであるAdTagを作成し、Adモデルのtagsフィールドに紐づけます。

# ads/models.py

from django.db import models
from wagtail.admin.edit_handlers import FieldPanel
from wagtail.search import index
from wagtail.snippets.models import register_snippet

# Tagに必要なモジュールを追加
from modelcluster.fields import ParentalKey
from modelcluster.models import ClusterableModel
from taggit.models import TaggedItemBase
from taggit.managers import TaggableManager


# タグを表現するモデル、TaggedItemBaseを継承する
class AdTag(TaggedItemBase):
    # タグを紐づけたいモデルをParentalKeyで指定する
    # 今回はadsアプリの中のAdモデルを指定
    content_object = ParentalKey('ads.Ad', on_delete=models.CASCADE, related_name='tagged_items')


@register_snippet
class Ad(index.Indexed, ClusterableModel): # models.ModelからClusterableModelに変更
    name = models.CharField(max_length=255)
    url = models.URLField(null=True, blank=True)
    image = models.ImageField(upload_to='image/ad')
    tags = TaggableManager(through=AdTag, blank=True) # AdTagと紐付けしたフィールドを追加

    panels = [
        FieldPanel('name'),
        FieldPanel('url'),
        FieldPanel('image'),
        FieldPanel('tags'), # Adminの入力フィールドを追加
    ]

    search_fields = [
        index.SearchField('name', partial_match=True),
    ]

    def __str__(self):
        return self.name

通常のPageモデルにタグをつけるパターンとほぼ同じですが、tagsフィールドにtaggit.manager.TaggableManagerを利用する点に注意です。

Adminからは、TAGSのフィールドで、,で区切りを入れてテキストを入力すると、このSnippetに複数のタグをつけることができるようになります。

wagtailのsnippetsにtagをつける