Wagtail Streamblock Template Rendering

Viewed 402

I'm trying to create a block that allows me to add multiple blocks, where each is a different size in a size estimator.

class SizeItem(blocks.StructBlock):
    name = blocks.CharBlock(required=True)
    image = ImageChooserBlock(required=True)
    description = blocks.TextBlock(required=True)

    class Meta: #noqa
        icon = 'arrows-up-down'
        label = 'Size Estimator Item'


class SizeEstimator(blocks.StructBlock):
    description = blocks.TextBlock(required=False)
    size_items = blocks.StreamBlock([
        ('size_item', SizeItem())
    ], required=False)

    class Meta: #noqa
        template = 'streams/size_estimator.html'
        icon =  'list-ul'
        label = 'Size Estimator'

The issue is that I need the names, images, and descriptions all grouped within different areas of the SizeEstimator template:

<div>
    <div>
        <ul>
            <li>names</li>
        </ul>
    </div>
    <div>
        <div>
            images
        </div>
    </div>
    <div>
        descritions
    </div>
</div>

so I can't use a template on the SizeItem because there is no way to separate the pieces in the SizeEstimator template. But if I try:

<div class="se-wrap flexy">
  <div class="se-sizes">
    {% for item in self.size_items %}
      <div class="se-size-btn">
        {{item}}
      </div>
    {% endfor %}
  </div>
</div>

I get plain text back with each SizeItem attribute name and value. I can't just pull the values like item.name because the output is blank.

What is a good way to add orderable blocks to another block where you can piece out the contents of the oderable blocks?

1 Answers

Since you've defined size_items as a StreamBlock, looping over self.size_items will give you a sequence of StreamChild objects with block_type and value attributes. In this case, block_type will always be 'size_item' (since that's the only block type defined within that stream), and value will be the data for one SizeItem, as a dict. So, you can access the individual fields of that item with a template like:

<div class="se-wrap flexy">
  <div class="se-sizes">
    {% for item in self.size_items %}
      <div class="se-size-btn">
        {{item.value.name}}
      </div>
    {% endfor %}
  </div>
  <div class="se-images">
    {% for item in self.size_items %}
      <div class="se-size-btn">
        {% image item.value.image width-400 %}
      </div>
    {% endfor %}
  </div>
</div>

If you were to define size_items as size_items = blocks.ListBlock(SizeItem(), required=False) instead, this would simplify things, since a ListBlock returns a list of child values directly - there's no intermediate StreamChild object to identify the type of each block, and so you'd be able to write {{item.name}} in your template instead.

Rather than unpacking the data inside the template, you might find it neater to add a get_context method to the SizeEstimator block that sets up a few variables to pass to the template:

class SizeEstimator(blocks.StructBlock):
    # ...
    def get_context(self, value, parent_context=None):
        context = super().get_context(value, parent_context=parent_context)

        names = []
        images = []
        descriptions = []

        for item in value:
            names.append(item.value['name'])  # or item['name'] if you're using a ListBlock
            images.append(item.value['image'])
            descriptions.append(item.value['description'])

        # make these lists available to the template
        context['names'] = name
        context['images'] = images
        context['descriptions'] = descriptions

        return context

You'll then be able to write {% for name in names %} in your template.

Related