Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to solve Wagtail cyclic Block dependency when using a StreamBlock

I would like to achieve something like this,

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())
    ])

Where in a Carousel, I can add a tab or another carousel and inside a Tab I can add a carousel or another Tab.

What is the best practice in handling programing cases like these.

like image 710
Mevin Babu Avatar asked Jun 15 '16 11:06

Mevin Babu


1 Answers

Unfortunately I don't think it'll be possible to make this work, even if you find a way to set up the circular reference in the definition. There are various places in Wagtail's code that will try to traverse the definition as a tree, and end up in an infinite recursion.

For example, this happens when freezing a StreamField definition inside a migration - it will expand any references to named StructBlock / StreamBlock subclasses into plain StructBlock / StreamBlock constructors (see http://docs.wagtail.io/en/v1.5.2/topics/streamfield.html#streamfield-definitions-within-migrations), which would expand infinitely in this case. Similarly, building the HTML for the edit form will fail, as it will attempt to build an HTML template for each repeatable element in the form (i.e. the block of HTML to be added whenever you click to add a new carousel, or a new tab) - and it's not clever enough to re-use the same template for a top-level carousel, second-level carousel, third-level carousel and so on, so there will be an infinite number of templates to generate.

You'll either need to put a hard-coded limit on the number of levels of nesting (e.g. CarouselBlock can contain a SecondLevelCarousel block which can contain a ThirdLevelCarousel block, but no more than that), or come up with an alternative data representation that spreads the data entry across several views rather than a single infinitely-nestable form. For example, you could define Carousel and Tab as snippet models, and use SnippetChooserBlock to define parent/child links between them:

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

(of course, if you go down this route, you have to be sure not to set up any circular parent/child relations, or you're back to square one :-) )

like image 68
gasman Avatar answered Nov 16 '22 04:11

gasman