默认情况下,片段缺乏页面的许多特性,例如预览、修订和工作流。通过继承适当的 mixin 类,可以将这些特性单独添加到每个片段模型中。

使片段可预览

如果片段模型继承自 [PreviewableMixin], Wagtail 将自动在编辑器中添加实时预览面板。除了继承 mixin 之外,模型还必须重写 [get_preview_template()] 或 [serve_preview()] 。例如, Advert 片段可以按如下方式预览:

# ...

from wagtail.models import PreviewableMixin

# ...

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

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

    def get_preview_template(self, request, mode_name):
        return "demo/previews/advert.html"

使用以下 demo/previews/advert.html 模板:

<!DOCTYPE html>
<html>
    <head>
        <title>{{ object.text }}</title>
    </head>
    <body>
        <a href="{{ object.url }}">{{ object.text }}</a>
    </body>
</html>

默认上下文中可用的变量是 request (伪造的 HttpRequest 对象)和 object (代码片段实例)。要自定义上下文,您可以重写 [get_preview_context()] 方法。

默认情况下, serve_preview 方法返回使用请求对象、 get_preview_template 返回的模板以及 get_preview_context 返回的上下文对象呈现的 TemplateResponse 。您可以重写 serve_preview 方法来自定义渲染和/或路由逻辑。

与页面类似,您可以通过覆盖 [preview_modes] 属性来定义多种预览模式。例如,以下 Advert 代码段有两种预览模式:

# ...

from wagtail.models import PreviewableMixin

# ...

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

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

    @property
    def preview_modes(self):
        return PreviewableMixin.DEFAULT_PREVIEW_MODES + [("alt", "Alternate")]

    def get_preview_template(self, request, mode_name):
        templates = {
            "": "demo/previews/advert.html",  # 默认预览模式
            "alt": "demo/previews/advert_alt.html",  # 替代预览模式
        }
        return templates.get(mode_name, templates[""])

    def get_preview_context(self, request, mode_name):
        context = super().get_preview_context(request, mode_name)
        if mode_name == "alt":
            context["extra_context"] = "Alternate preview mode"
        return context

使片段可搜索

如果代码片段模型继承自 wagtail.search.index.Indexed ,如 索引自定义模型 中所述, Wagtail 将自动将搜索框添加到该代码片段类型的选择器界面。例如, Advert 片段可以按如下方式进行搜索:

# ...

from wagtail.search import index

# ...

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'),
        index.AutocompleteField('text'),
    ]

保存片段的修订

如果一个片段模型继承自 [RevisionMixin],那么当你在片段管理中保存任何更改时,Wagtail 将自动保存修订。除了继承mixin之外,建议将 [Revision] 模型的 GenericRelation 定义为 [revisions] 属性,以便您可以执行相关查询。如果您需要自定义如何获取修订(例如,处理用于具有多表继承的模型的内容类型),您可以定义一个属性。示例,Advert 片段可以修改如下:

# ...
from django.contrib.contenttypes.fields import GenericRelation
from wagtail.models import RevisionMixin
# ...


class Advert(RevisionMixin, models.Model):
    url = models.URLField(null=True, blank=True)
    text = models.CharField(max_length=255)
    # If no custom logic is required, this can be defined as `revisions` directly
    _revisions = GenericRelation("wagtailcore.Revision", related_query_name="advert")

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

    @property
    def revisions(self):
        # Some custom logic here if necessary
        return self._revisions

如果你的片段模型使用 Django 的 ManyToManyField 定义关系,你需要将模型类改为继承自 modelcluster.models.ClusterableModel 而不是 django.models.model,并将 ManyToManyField 替换为 ParentalManyToManyField 。内联模型应该继续使用 ParentalKey 而不是 ForeignKey 。为了允许将关系存储在修订中,这是必要的。有关详细信息,请参阅本教程的作者部分。例如:

# ...
from django.db import models
from modelcluster.fields import ParentalKey, ParentalManyToManyField
from modelcluster.models import ClusterableModel
from wagtail.models import RevisionMixin
# ...


class ShirtColour(models.Model):
    name = models.CharField(max_length=255)

    panels = [FieldPanel("name")]


class ShirtCategory(models.Model):
    name = models.CharField(max_length=255)

    panels = [FieldPanel("name")]


class Shirt(RevisionMixin, ClusterableModel):
    name = models.CharField(max_length=255)
    colour = models.ForeignKey("shirts.ShirtColour", on_delete=models.SET_NULL, blank=True, null=True)
    categories = ParentalManyToManyField("shirts.ShirtCategory", blank=True)
    revisions = GenericRelation("wagtailcore.Revision", related_query_name="shirt")

    panels = [
        FieldPanel("name"),
        FieldPanel("colour"),
        FieldPanel("categories", widget=forms.CheckboxSelectMultiple),
        InlinePanel("images", label="Images"),
    ]


class ShirtImage(models.Model):
    shirt = ParentalKey("shirts.Shirt", related_name="images")
    image = models.ForeignKey("wagtailimages.Image", on_delete=models.CASCADE, related_name="+")
    caption = models.CharField(max_length=255, blank=True)
    panels = [
        FieldPanel("image"),
        FieldPanel("caption"),
    ]

RevisionMixin 包含需要添加到数据库表中的 latest_revision 字段。确保在进行上述更改后运行 makemigrationsmigrate 管理命令,以将更改应用到数据库。

应用 RevisionMixin 后,代码片段管理员所做的任何更改都将创建包含代码片段实例状态的 Revision 模型的实例。修订实例附加到编辑操作的 [audit log] 条目,允许您恢复到以前的修订或从代码片段历史记录页面比较修订之间的更改。

您还可以通过调用 [save_revision()] 方法以编程方式保存修订。应用 mixin 后,建议对已存在的片段的每个实例(如果有)至少调用一次此方法(或将片段保存在管理中),以便在数据库表中填充 latest_revision 字段。

保存片段的草稿更改

如果代码片段模型继承自 [DraftStateMixin] , Wagtail 将自动向列表视图添加实时/草稿状态列,将“保存”操作菜单更改为“保存草稿”,并在编辑器中添加新的“发布”操作菜单。您在代码片段管理中保存的任何更改都将保存为修订版本,并且在您发布更改之前不会反映在“实时”代码片段实例。

由于 DraftStateMixin 的工作方式是将草案更改保存为修订版,因此从这个 mixin 继承的也需要从 RevisionMixin 继承。有关详细信息,请参阅上面保存代码片段的修订版

如果模型的面板定义中有PublishingPanel, Wagtail还允许您为模型的实例设置发布计划。

例如, Advert 片段可以通过如下定义来保存草稿更改和发布计划:

# ...

from django.contrib.contenttypes.fields import GenericRelation
from wagtail.admin.panels import PublishingPanel
from wagtail.models import DraftStateMixin, RevisionMixin

# ...

class Advert(DraftStateMixin, RevisionMixin, models.Model):
    url = models.URLField(null=True, blank=True)
    text = models.CharField(max_length=255)
    _revisions = GenericRelation("wagtailcore.Revision", related_query_name="advert")

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

    @property
    def revisions(self):
        return self._revisions

DraftStateMixin 包含需要添加到数据库表中的其他字段。确保在进行上述更改后运行 makemigrationsmigrate 管理命令,以将更改应用到数据库。

您可以通过调用 [instance.publish(revision)] 或调用 [revision.publish()] 以编程方式发布修订。应用 mixin 后,建议为已存在的片段的每个实例(如果有)至少发布一个修订版,以便 latest_revisionlive_revision 字段填充到数据库表中。

如果您使用计划发布功能,请确保定期运行 [publish_scheduled] 管理命令。有关更多详细信息,请参阅 [Scheduled Publishing] 。

发布片段实例需要有在片段模型上的发布权限。对于应用了 DraftStateMixin 的模型,Wagtail会自动创建相应的发布权限,并将其显示在 Wagtail 管理界面的“Groups”区域中。有关如何配置权限的详细信息,请参见权限。

警告:
Wagtail 尚无阻止编辑者在页面中包含未发布(“草稿”)片段的机制。在页面中包含启用了 DraftStateMixin 的代码片段时,请确保添加必要的检查来处理草稿代码片段的呈现方式(例如,通过检查其 live 字段)。我们计划在未来改进这一点。

锁定片段

如果一个片段模型继承自 LockableMixin , Wagtail 将自动添加锁定模型实例的功能。编辑时,Wagtail 会在“状态”侧面板显示锁定信息,如果用户有权限,则会有一个锁定 / 解锁实例的按钮。

如果模型还配置为计划发布(如上面 保存片段的修改草稿 所示),Wagtail 将锁定计划发布的任何实例。

与页面类似,锁定片段的用户仍然可以编辑它,除非 WAGTAILADMIN_GLOBAL_EDIT_LOCK 设置为 True

例如,可以通过如下定义来锁定 Advert 片段的实例:

# ...
from wagtail.models import LockableMixin
# ...


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

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

如果你使用其他 mixins,确保在其他 mixins 之后应用 LockableMixin,但要在 RevisionMixin 之前 ( 按从左到右的顺序 )。例如,对于 DraftStateMixinRevisionMixin,模型的正确继承应该是 class MyModel(DraftStateMixin, LockableMixin, RevisionMixin)。有一个系统检查来强制混合(mixins)的顺序。

LockableMixin 包含需要添加到数据库表中的其他字段。确保在进行上述更改后运行 makemigrationmigrate 管理命令,以将更改应用到数据库。

锁定和解锁片段实例分别需要对片段模型的锁定和解锁权限。对于应用了 LockableMixin 的模型,Wagtail 会自动创建相应的锁定和解锁权限,并将其显示在 Wagtail 管理界面的“Groups”区域中。有关如何配置权限的详细信息,请参见权限。

为片段启用工作流

如果一个片段模型继承自 WorkflowMixin , Wagtail 将自动为模型分配添加工作流的功能。将工作流指定给片段模型后,编辑器中将显示“提交审核”和其他工作流操作菜单项。状态侧面板还将显示当前工作流的信息。

由于 WorkflowMixin 使用了 Wagtail 中的修订和发布机制,因此从这个 mixin 继承的类也继承自 RevisionMixinDraftStateMixin 。此外,还建议继承 LockableMixin 来启用锁定,这样片段实例就可以被锁定,并且只有在工作流中才可以被审查者编辑。有关更多细节,请参阅上述部分。

例如,工作流 ( 带锁定 ) 可以通过如下定义来为 Advert 片段启用:

# ...
from wagtail.models import DraftStateMixin, LockableMixin, RevisionMixin, WorkflowMixin
# ...


class Advert(WorkflowMixin, DraftStateMixin, LockableMixin, RevisionMixin, models.Model):
    url = models.URLField(null=True, blank=True)
    text = models.CharField(max_length=255)
    _revisions = GenericRelation("wagtailcore.Revision", related_query_name="advert")
    workflow_states = GenericRelation(
        "wagtailcore.WorkflowState",
        content_type_field="base_content_type",
        object_id_field="object_id",
        related_query_name="advert",
        for_concrete_model=False,
    )

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

    @property
    def revisions(self):
        return self._revisions

WorkflowMixin 所需的其他 mixin 包括了需要添加到数据库表中的其他字段。确保在进行上述更改后运行 makemigrationmigrate 管理命令,以将更改应用到数据库。

启用 mixin 后,您可以设置工作流将工作流指定给片段模型。有关详细信息,请参阅如何调节配置工作流

管理仪表板和工作流报告还将向您显示已提交到工作流的片段 ( 与页面一起 )。

为片段添加标签

向片段添加标签与向页面添加标签非常相似。唯一的区别是,如果没有应用 RevisionMixin,应该使用 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')


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

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

[documentation on tagging pages] 提供了有关如何在视图中使用标签的更多信息。

片段中的内联模型

与页面类似,您可以在片段中嵌套其他模型。

from django.db import models
from modelcluster.fields import ParentalKey
from wagtail.models import Orderable


class BandMember(Orderable):
    band = ParentalKey("music.Band", related_name="members", on_delete=models.CASCADE)
    name = models.CharField(max_length=255)


@register_snippet
class Band(ClusterableModel):
    name = models.CharField(max_length=255)
    panels = [
        FieldPanel("name"),
        InlinePanel("members")
    ]

关于 如何在页面中使用内联模型 的文档提供了更多信息,这些信息也适用于片段。