使用 StreamBlock 时如何解决 Wagtail 循环块依赖

问题描述 投票:0回答:2

我想实现这样的目标,

from wagtail.wagtailcore.blocks import StreamBlock, StructBlock


class CarouselBlock(StructBlock):

    content = StreamBlock([
        ('tab', TabBlock()),
        ('carousel', CarouselBlock())
    ])


class TabBlock(StructBlock):

    content = StreamBlock([
        ('tab', TabBlock()),
        ('carousel', CarouselBlock())
    ])

在轮播中,我可以添加一个选项卡或另一个轮播,在选项卡内我可以添加一个轮播或另一个选项卡。

处理此类编程案例的最佳实践是什么。

python django wagtail wagtail-streamfield
2个回答
8
投票

不幸的是,即使您找到了在定义中设置循环引用的方法,我认为也不可能实现这项工作。 Wagtail 代码中的多个地方会尝试将定义作为树进行遍历,并最终导致无限递归。

例如,当在迁移中冻结 StreamField 定义时会发生这种情况 - 它将将对命名的 StructBlock / StreamBlock 子类的任何引用扩展为普通的 StructBlock / StreamBlock 构造函数(请参阅 http://docs.wagtail.io/en/v1.5.2 /topics/streamfield.html#streamfield-definitions-within-migrations),在这种情况下将无限扩展。同样,为编辑表单构建 HTML 也会失败,因为它将尝试为表单中的每个可重复元素构建 HTML 模板(即,每当您单击添加新轮播或新选项卡时要添加的 HTML 块) ) - 对于顶级轮播、二级轮播、三级轮播等重复使用相同的模板还不够聪明,因此将生成无限数量的模板。

您要么需要对嵌套级别的数量进行硬编码限制(例如 CarouselBlock 可以包含 SecondLevelCarousel 块,而 SecondLevelCarousel 块可以包含 ThirdLevelCarousel 块,但仅此而已),或者提出替代数据表示形式它将数据输入分布在多个视图中,而不是单个无限嵌套的表单中。例如,您可以将 Carousel 和 Tab 定义为片段模型,并使用 SnippetChooserBlock 定义它们之间的父/子链接:

@register_snippet
class Carousel(models.Model):
    content = StreamField([
        ('carousel', blocks.SnippetChooserBlock('myapp.Carousel')),
        ('tab', blocks.SnippetChooserBlock('myapp.Tab')),
    ])

(当然,如果你走这条路,你必须确保不要建立任何循环的父/子关系,否则你又回到了第一个:-))


0
投票

@Gooshan 可以创建一个递归 StructBlock,其中 Streamblock 作为子块,递归块作为 StreamBlock 的子块等。您需要使用 BaseStructBlock local_blocks 本地(实例)变量,而不是声明子块作为类属性。这将允许您在运行时而不是仅在项目启动时定义块/子块结构。

您还需要设置最大递归深度,这将省略倒数第二个深度上的递归 StructBlock,否则最终会出现递归深度错误。

这是我用来在菜单 StreamBlock 上创建子菜单的示例:

class RecursiveSubMenuBlock(MenuStructBlock):
    def __init__(
            self, 
            local_blocks=(), 
            max_depth=3, 
            _depth=0, 
            *args, 
            **kwargs
        ):
        _depth += 1
        if _depth <= max_depth:
            streamblocks = (
                ("sub_menu", RecursiveSubMenuBlock(local_blocks, max_depth, _depth, *args, **kwargs)),
                ("autofill_submenu", AutoPageLinksBlock()),
             ) if _depth < max_depth else ()
            streamblocks += (
                ("link", MenulLinkBlock(link_types=['page', 'url_link'])),
                ("divider", SubMenuDividerBlock())
            )
            local_blocks += (
                ("title", CharBlock(max_length=50, label=_("Submenu Display Title"))),
                ("items", StreamBlock(streamblocks)),
                *local_blocks
            )
        super().__init__(local_blocks, _depth=_depth, *args, **kwargs)

    class Meta:
        icon = 'folder-open-1'
        template = "menu/submenu.html"
        label = _("Sub Menu")
        label_format = label + ": {title}"
        form_classname = "struct-block sub-menu-block"


class MenuStreamBlock(StreamBlock):
    submenu = RecursiveSubMenuBlock(show_options=MenuItemOptions(sticky=True))
    autofill_menu = AutoPageLinksBlock(show_options=MenuItemOptions(sticky=True))
    link = MenulLinkBlock(show_options=MenuItemOptions(sticky=True))
    search_form = SearchMenuBlock(show_options=MenuItemOptions(sticky=True))
    user_menu = UserMenuBlock(show_options=MenuItemOptions(sticky=True))
    ....

默认允许 3 层子菜单:

recursive block demo

© www.soinside.com 2019 - 2024. All rights reserved.