2

I am new to Wagtail but have done my homework and read the documentation and searched the web for additional references but cannot figure out why I cannot successfully loop through and render values entered into varous blocks inside a Steamfield. I have tried it both within the main page template and with a template for the block.

Here is my model (the template for agenda_item is commented out right now):

class AgendaPage(Page):
author= models.CharField(max_length=255)
date = models.DateField('Post date')
agenda = StreamField([
    ('agenda_item', blocks.StreamBlock([
        ('item_title', blocks.TextBlock()),
        ('item_content', blocks.ListBlock(blocks.StructBlock([
            ('item_text', blocks.TextBlock()),
            ('mtg_doc', blocks.StructBlock([
                ('doc_description', blocks.TextBlock()),
                ('doc_link', blocks.TextBlock())
            ]))
        ])))

    ]
    #,
    #template='blocks/agenda_temp.html',
    ))
])




content_panels = Page.content_panels + [
    FieldPanel('author'),
    FieldPanel('date'),
    StreamFieldPanel('agenda'),

]

When I have the most basic template like this, all the values entered when publishing the page in the editor are rendered, but with the name of the block preceding it. So in this basic template:

{% for block in self.agenda %}
     {{ block.value }}
{% endfor %} 

If I try to access the values within separately, I get nothing. The below is just an example but I have tried many other syntax combinations, including using a separate template for the block called "agenda_item" to no avail:

{% if block.block_type == 'item_title' %}

           <h2>{{ block.value }}<h2>

      {% endif %}

Is there perhaps something wrong with my Streamfield nesting, even though it does get saved to the database and rendered with the simple {{block}} tag?

UPDATE: I have accepted this answer because it solved my template rendering issues, but perhaps this screenshot will help illustrate my existing issue. The streamblock 'agenda_item' is available by clicking the + in the editor interface to add additional child blocks to an 'agenda_item' as well as to add a new 'agenda_item' which is great, and almost exactly what I need. The issue is that I only want 'item_title' to be available for a new 'agenda_item' and not for children within the 'agenda_item'. This is why I originally nested the streamfield children as such but then could not access the lowest level nested block in the template rendering. So scaling back the levels of the streamfield solved that but now the user may mistakenly add an item_title where it is not necessary or valid. My question is: What available streamfield block(or combination thereof) might help accomplish this?

Valid XHTML
(source: pocketsofactivity.com)

My existing model and panel definitions look like this:

agenda = StreamField([
    ('agenda_item', blocks.StreamBlock([
        ('item_title', blocks.TextBlock()),
        ('item_text', blocks.TextBlock()),
        ('mtg_doc', blocks.StructBlock([
            ('mtg_doc_upload', DocumentChooserBlock(required=True)),
            ('submitted_late', blocks.BooleanBlock(required=False, help_text='Submitted Late')),
            ('heldover', blocks.BooleanBlock(required=False, help_text='Held Over')),
            ('heldover_from', blocks.DateBlock(required=False, help_text="Held Over From")),
        ])),
        ('item_audio', DocumentChooserBlock(required=False)),
    ]))
])




content_panels = Page.content_panels + [
    FieldPanel('author'),
    FieldPanel('date'),
    FieldPanel('mtg_date'),
    FieldPanel('mtg_time'),
    StreamFieldPanel('mtg_media'),
    StreamFieldPanel('agenda'),

]

1 Answer 1

3

In the way your code is currently written, your top-level blocks are always of type agenda_item, so you need to account for that when looping over them:

{% for block in self.agenda %}
    {% if block.block_type == 'agenda_item' %} {# will always be true, but included here for clarity #}
        {% for subblock in block.value %}
            {% if subblock.block_type == 'item_title' %}
                <h2>{{ subblock.value }}</h2>
            {% elif subblock.block_type == 'item_content' %}
                rendering for item_content...
            {% endif %}
        {% endfor %}
    {% endif %}
{% endfor %}

However, I don't think you really need the agenda_item StreamBlock at all - it's just adding an unnecessary level of indirection that's tripping you up. As far as I can see, you can achieve the same result by just making item_title and item_content the two available block types at the top level:

agenda = StreamField([
    ('item_title', blocks.TextBlock()),
    ('item_content', blocks.ListBlock(blocks.StructBlock([
        ('item_text', blocks.TextBlock()),
        ('mtg_doc', blocks.StructBlock([
            ('doc_description', blocks.TextBlock()),
            ('doc_link', blocks.TextBlock())
        ]))
    ])))
])

(Or did you intend agenda_item to be a StructBlock instead...?)

Sign up to request clarification or add additional context in comments.

5 Comments

I think I did want agenda_item to be a structblock so that I could make a list. I will try this subblock syntax or remove a level, nowhere in Wagtail docs is the word subblock ever written, that I can find
'subblock' isn't a Wagtail concept - that's just the variable name I chose in that code example, to distinguish it from the top-level block.
This solution returns the item_title value, but I am unable to properly iterate over the structblock:item_content, it get this rendered:[StructValue([('item_text', ' ...etc by using the same for and if syntax, any thoughts?
I had to use this syntax to access the structblock values, would like to understand when I do and dont need this:{{ block.bound_blocks.item_text.render }}
By making 'item_title' an available block in the main 'agenda_item' streamblock, it becomes available every time the + is clicked in the editor. I need that to only show up once for each new agenda item, then only the other blocks to show up when + is clicked within that item

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.