2
votes

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

1 Answers

3
votes

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...?)