Sarun Dahal
Sarun Dahal

Reputation: 377

Unable to render Listblock inside StreamBlock in template Wagtail

I have StructBlock inside a Listblock, all of which sit insde a class BannerBlock as follows:

class BannerBlock(blocks.StreamBlock):
    """
    Blocks for displaying individual banners
    """
    
    banners = blocks.ListBlock(
        blocks.StructBlock(
            [
                ('image', ImageChooserBlock(required=True)),
                ('title', blocks.CharBlock(required=True, max_length=128)),
                ('description', blocks.CharBlock(required=True, max_length=1024)),
                ('link', blocks.URLBlock(required=False)),
            ]
        )
    )
    
    class Meta:
        template = "home/banner_block.html"
        icon = "placeholder"
        label = "Banners"

The template Banner_block is as follows:

{% load wagtailimages_tags %}

<!-- Banner -->
<div id="banner">
    {% for banner in self.banners %}
        <article data-position="bottom right">
            <div class="inner">
                {% image banner.image original as image %}
                <img src="{{ image.url }}" alt="">
                <div class="features">
                    <a href="{{ banner.link.url }}" class="c-accent alt no-bg">
                    <h2>{{ banner.title }}</h2>
                    <p>{{ banner.description }}</p>
                    </a>
                </div>
            </div>
        </article>
    {% endfor %}
</div>

Finally, I am trying to render each Banner (Listblock) as follows:

{% for block in page.banners_collection %}
     {% include_block block %}
{% endfor %} 

However, Each Listblock is rendered as plain text as list items. How do I render each item correctly? Thank you.

Upvotes: 0

Views: 734

Answers (1)

gasman
gasman

Reputation: 25292

I'm assuming the banners_collection field on your page model is defined as:

banners_collection = StreamField(BannerBlock())

where you're specifying a StreamBlock to use at the top level of the StreamField, rather than the more basic setup of

some_stream = StreamField([
    ('first_block_type', some_block_definition),
    ('another_block_type', some_block_definition),
])

where you pass a list of blocks for StreamField to make a stream from. This means that when you access page.banners_collection, it will give you a single instance of a BannerBlock value. When you loop over this value with for block in page.banners_collection, you're actually getting back the ListBlock values in your stream, and rendering them individually with include_block - bypassing the template you set up for BannerBlock. Your ListBlock values don't have a template of their own, so they end up using the default rendering.

If you call include_block on the BannerBlock value as a whole:

{% include_block page.banners_collection %}

then it will use your template. However, there's a secondary problem - in the line

{% for banner in self.banners %}

self.banners is not valid, because a stream value behaves as a sequence, not a dictionary. You can't look up the banners children by name - you need to loop over the list of children finding the ones that are of type banners. (Except, in your case, you can skip that check because banners is the only available block type within the stream). The correct template code would look like:

<div id="banner">
    {% for block in self %}
        {# the 'block' object here has properties block_type (which is always 'banners' here) and value #}
        {% for banner in block.value %}
            {# banner is a StructBlock value, with image, title, description, link properties #}
            <article data-position="bottom right">
                <div class="inner">
                    {% image banner.image original as image %}
                    ...
               </div>
           </article>
        {% endfor %}
    {% endfor %}
</div>

However, are you sure you need the ListBlock in addition to the StreamBlock? A StreamBlock is already a sequence of blocks, so a ListBlock inside a StreamBlock gives you a list of lists of banners. If you simplified the BannerBlock value to

class BannerBlock(blocks.StreamBlock):
    """
    Blocks for displaying individual banners
    """
    
    banner = blocks.StructBlock(
        [
            ('image', ImageChooserBlock(required=True)),
            ('title', blocks.CharBlock(required=True, max_length=128)),
            ('description', blocks.CharBlock(required=True, max_length=1024)),
            ('link', blocks.URLBlock(required=False)),
        ]
    )
    
    class Meta:
        template = "home/banner_block.html"
        icon = "placeholder"
        label = "Banners"

then BannerBlock as a whole would be a list of banners, and the template would become

<div id="banner">
    {% for block in self %}
        {# block.value is now a StructBlock value #}
        <article data-position="bottom right">
            <div class="inner">
                {% image block.value.image original as image %}
                ...
           </div>
       </article>
    {% endfor %}
</div>

Upvotes: 1

Related Questions