Reputation: 377
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
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