Search code examples
carouselwagtailwagtail-streamfield

passing varialbeles to wagtail carousel


Using Wagtail, I need to display cards gallery where one of the feature of the card is a carousel images. I made it with images only, but when I tried to add a caption text to each carousel I had a problem to display the data. While I can display the first image on my carousel, I cannot navigate with the Next/Previous carousel icons, also, the caption text fields are blank. It seems like I don't use the right syntax to pass the variable names to the carousel.

As always, many thanks for any input on how to solve the syntax and/or a better block structure to my carousel model.

Data flow goes from carouselblock to blogpage (a child of) bloglistingpage

blocks.py:

class CarouselBlock(blocks.StreamBlock):
    carousel_image = blocks.StructBlock([
        ('image', ImageChooserBlock()),
        ('caption', blocks.TextBlock()),
    ])

    class Meta:
        icon = "cogs"
        template = "streams/custom_carousel_block.html"

models.py:

class BlogListingPage(Page):
    content = StreamField(
        [
            ...
            ("cards", blocks.CustomCardBlock()),
        ],
        null=True,
        blank=True,
    )

    content_panels = Page.content_panels + [
        ...
        StreamFieldPanel("content"),
    ]
    def get_context(self, request):
        # Update context to include only published posts, ordered by reverse-chron
        context = super().get_context(request)
        blogpages = self.get_children().live().order_by('-first_published_at')
        context['blogpages'] = blogpages
        return context

class BlogPage(Page):
    ...
    main_content = StreamField([
        ('carousel', CarouselBlock()), 
      ...
    ],
        null=True,
        blank=False)
    ...

    content_panels = Page.content_panels + [
        ...
        StreamFieldPanel('main_content'),
    ]

streams/custom_carousel_block.html:

{% load wagtailcore_tags wagtailimages_tags %}

<div id="carousel" class="carousel slide" data-ride="carousel">
    <div class="carousel-inner">
        {% for sub_block in self %}
                {% image carousel_image.value original as carousel_img %}
            <ul class="carousel_image">
                {% for img in block.value %}
                    <li class="carousel-item {% if forloop.first  %}active{% endif %}">
            <img src="{{ image.value.url }}" class="d-block w-100" alt="{{ image.value.alt }}">       
        {% for sub_block in all_carousel %}
            {# for caption I tried both syntax SPECIFIC/VALUE, none worked for me #}
            {% if sub_block.specific.caption %}
                <p>{{ sub_block.specific.caption }}</p>
            {% endif %}
        {% endfor %}
                    </li>
          {% endfor %}
                <li>{{carousel_image.value.caption}}</li>
            </ul>


        {% endfor %}
    </div>
    <a class="carousel-control-prev" href="#carousel" role="button" data-slide="prev">
        <span class="carousel-control-prev-icon" aria-hidden="true"></span>
        <span class="sr-only">Previous</span>
    </a>
    <a class="carousel-control-next" href="#carousel" role="button" data-slide="next">
        <span class="carousel-control-next-icon" aria-hidden="true"></span>
        <span class="sr-only">Next</span>
    </a>
</div>

Solution

  • Starting from the line:

    {% for sub_block in self %}
    

    Here self is the StreamBlock value, which behaves just as the top-level StreamField value would in your page template - looping over it with {% for sub_block in self %} will give you a sub_block object with two properties block_type and value.

    In this case, sub_block.block_type will always be 'carousel_image' because that's the only block type available within the stream. sub_block.value will give you the block's value - and since that block type is a StructBlock consisting of 'image' and 'caption', the value will be a dictionary consisting of those two fields, which you can access as sub_block.value.image and sub_block.value.caption.

    The code therefore becomes:

    {% for sub_block in self %}
        {% image sub_block.value.image original %}
        <p>{{ sub_block.value.caption }}</p>
    {% endfor %}
    

    (I'll leave you the task of fitting the HTML back around that.)