片段

片段一般是页面中引用的一小段内容。用来动态定义一些内容,例如标题、边栏、脚注等。它可以在 Wagtail 后台管理界面中进行编辑。片段不从 Page 类继承,而使用 Django 模型类进行定义,所在不在页面树中显示。 片段通过 register_snippet 类修饰器进行注册,然后可以后台管理的片段子菜单中进行编辑。

片段缺少了页面的很多属性,例如排序、预定义的URL,也没有版本管理及审核流程等,在使用片段时应仔细考虑,有时更适合采用页面形式。一般片段是与其它页面进行组合使用,而不单独呈现。

片段模型

下面一个片段模型的例子:

from django.db import models

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

...

@register_snippet
class Advert(models.Model):
    url = models.URLField(null=True, blank=True)
    text = models.CharField(max_length=255)

    panels = [
        FieldPanel('url'),
        FieldPanel('text'),
    ]

    def __str__(self):
        return self.text

Advert 使用 Django 模型类做为基类,定义了两个属性:文本和URL。后台管理界面的编辑界面风格和页面风格类似,需要编辑的字段赋值给 panels 属性。片段编辑界面没有多个 Tab 页,也没有保存为草稿及提供审核的功能。

@register_snippet 告诉 Wagtail 将这个模型注册为片段。panels 列表定义了显示在编辑界面上的字段。通过 def __str__(self): 定义类对象的字符串显示内容也是很重要的,它输出的内容会显示在后台管理片段一览界面上,用来标识片段。

在模板标签中使用片段

在模板中最简单的使用片段方法是使用标签,使用 Django 中定制标签,具体可参考文档 django 定制模板标签 。这里简单说明一下过程,并解释和 Wagtail 相关的一些内容。

首先,在应用的 templatetags 文件夹中创建一个 Python 程序文件,例如 myproject/demo/templatetags/demo_tags.py。 增加相关 Django 模块及我们自定义模型的引用,最后加上 register 修饰符:

from django import template
from demo.models import Advert

register = template.Library()

...

# Advert snippets
@register.inclusion_tag('demo/tags/adverts.html', takes_context=True)
def adverts(context):
    return {
        'adverts': Advert.objects.all(),
        'request': context['request'],
    }

@register.inclusion_tag() 方法有两个参数: 模板以及是否传递上下文。在自定义标签中,最好将模板的上下文传入,一些 Wagtail 专有的模板标签,如 pageurl 需要上下文才能正常工作。 标签函数可以通过获取参数值后过滤广告列表返回一个模型实例,为了简化起见,这里采用 Advert.objects.all() 返回全部对象。

下面是 demo/tags/adverts.html 模板使用自定义标签的示例:

{% for advert in adverts %}
    <p>
        <a href="{{ advert.url }}">
            {{ advert.text }}
        </a>
    </p>
{% endfor %}

接下来在页面模板中,只需简单的引用片段模板标签即可:

{% load wagtailcore_tags demo_tags %}

...

{% block content %}

    ...

    {% adverts %}

{% endblock %}

将页面绑定到片段

在上面的例子中,标签返回的广告列表是固定的,在每个页面上都一样。这在通用的边栏可以使用,有时我们需要根据页面内容来关联特定的广告。 这里需要在页面模型中定义外键关系关联到片段,然后在 content_panels 增加 SnippetChooserPanel 片段选择器为一个页面选择指定的片段。例如,在 BookPage 页面实例中指定广告片段:

from wagtail.snippets.edit_handlers import SnippetChooserPanel
# ...
class BookPage(Page):
    advert = models.ForeignKey(
        'demo.Advert',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    content_panels = Page.content_panels + [
        SnippetChooserPanel('advert'),
        # ...
    ]

片段可以在模板文件中通过 page.advert 进行引用。

如果要关联多个片段, SnippetChooserPanel 可以不直接放在 BookPage 中,而是放在内嵌子对象中。这里使用 BookPageAdvertPlacement 子对象 (这样起名的原因是每次将一个广告放入一个图书面面):

from django.db import models

from wagtail.core.models import Page, Orderable
from wagtail.snippets.edit_handlers import SnippetChooserPanel

from modelcluster.fields import ParentalKey

...

class BookPageAdvertPlacement(Orderable, models.Model):
    page = ParentalKey('demo.BookPage', on_delete=models.CASCADE, related_name='advert_placements')
    advert = models.ForeignKey('demo.Advert', on_delete=models.CASCADE, related_name='+')

    class Meta(Orderable.Meta):
        verbose_name = "advert placement"
        verbose_name_plural = "advert placements"

    panels = [
        SnippetChooserPanel('advert'),
    ]

    def __str__(self):
        return self.page.title + " -> " + self.advert.text


class BookPage(Page):
    ...

    content_panels = Page.content_panels + [
        InlinePanel('advert_placements', label="Adverts"),
        # ...
    ]

这些子对象可以通过页面的 advert_placements 属性存取,从中可以引用的片段字段 advert。 在 BookPage 对应的模板中可以包含:

{% for advert_placement in page.advert_placements.all %}
    <p>
        <a href="{{ advert_placement.advert.url }}">
            {{ advert_placement.advert.text }}
        </a>
    </p>
{% endfor %}

增加片段搜索功能

如果片段模型从 wagtail.search.index.Indexed 继承,参考 索引定制模型 描述, Wagtail 将为这个片段类型自动增加搜索框。 例如,可搜索的 Advert 片段可以定义如下:

...

from wagtail.search import index

...

@register_snippet
class Advert(index.Indexed, models.Model):
    url = models.URLField(null=True, blank=True)
    text = models.CharField(max_length=255)

    panels = [
        FieldPanel('url'),
        FieldPanel('text'),
    ]

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

为片段打标签

为片段打标签与为页面打标签类似。唯一的区别是使用 taggit.manager.TaggableManager 而不是使用 ClusterTaggableManager

from modelcluster.fields import ParentalKey
from modelcluster.models import ClusterableModel
from taggit.models import TaggedItemBase
from taggit.managers import TaggableManager

class AdvertTag(TaggedItemBase):
    content_object = ParentalKey('demo.Advert', on_delete=models.CASCADE, related_name='tagged_items')

@register_snippet
class Advert(ClusterableModel):
    ...
    tags = TaggableManager(through=AdvertTag, blank=True)

    panels = [
        ...
        FieldPanel('tags'),
    ]

参考 页面打标签文档 以获取视图中使用标签的详细说明。