Render streamfield content from a chosen page

1,051 views
Skip to first unread message

Timo Rieber

unread,
Nov 14, 2015, 5:37:18 PM11/14/15
to Wagtail support
Let's assume I have two page models PageA and PageB, both depending on the StreamField feature. Instances of PageA can reference instances of PageB using a PageChooserBlock. When instanceA is rendered I want some data from the referenced instanceB to be rendered as well.

While looping over the blocks of instanceA I can access the normal model fields of instanceB (e.g. value.url). value.specific.body (one of the StreamFields) can also be rendered, but I have no idea how to access specific data from within that StreamField. I tried many different notations, but with no luck.

Does anybody have any suggestions?

Brett Grace

unread,
Nov 14, 2015, 5:55:22 PM11/14/15
to Wagtail support
As I understand, you want to render some specific blocks out of a StreamField. Take a look at https://wagtail.readthedocs.org/en/v1.2/topics/streamfield.html#template-rendering

Essentially you'll need to filter on the type of the block. Putting this in a tag or putting a helper on the page itself may be useful here, so that you can use Python.

If you've already tried to use this technique, I apologize. Posting examples of what you have already tried is helpful to others in determining what the problem is.

Also, unless I misunderstand things, it seems likely that the issue of one page referencing another is a bit of a red herring—presumably you have the same problem whether the StreamField is attached to PageA or PageB? Or are you seeing different behavior when try to render same page vs. referenced page?

Timo Rieber

unread,
Nov 15, 2015, 11:08:14 AM11/15/15
to Wagtail support
Thanks for the quick response, Brett.

Rendering field data from within instanceA or instanceB is no problem.

My challenge currently is when instanceA is rendered to render/get some information from the StreamField of related instanceB. Here is my sample code:

# Page classes and blocks

class SectionBlock(blocks.StructBlock):
    title = blocks.CharBlock(required=True)
    body = blocks.StreamBlock([
        ('article', ArticleBlock()),
        ('servicepage', blocks.PageChooserBlock(template='mysite/blocks/page.html'))
    ])

    class Meta:
        icon = 'placeholder'
        template = 'mysite/blocks/section.html'


class SectionPage(CorePage):
    """Section page for any number of section blocks"""
    body = fields.StreamField([
        ('section', SectionBlock()),
    ])


class TestBlock(blocks.StreamBlock):
    body = blocks.StreamBlock([
        ('title', blocks.CharBlock(required=True)),
        ('paragraph', blocks.RichTextBlock()),
        ('image', ImageChooserBlock()),
        ('person', PersonBlock()),
    ])

    class Meta:
        icon = 'site'


class TestPage(CorePage):
    """Test page"""
    body = fields.StreamField(TestBlock())


# section_page.html

{% block body-content %}
    {% for block in page.body %}
        {{ block }}
    {% endfor %}
{% endblock %}

# section.html

<section>
    {{ value.title }}

    {% for block in value.body %}
        {{ block }}
    {% endfor %}
</section>

# page.html

Id: {{ value.id }}
Title: {{ value.title }}
Url: {{ value.url }}
Body: {{ value.specific.body }}

A section page object references a test page object ('servicepage' in the StreamBlock). Rendering all the blocks from section page works well, including the servicepage block (rendered through page.html). But what I'm trying to achieve is to render a single information from the TestBlock body, for example paragraph or the names of the referenced Image or Person objects. I neither get their native values (strings) nor their rendered output (except value.specific.body, but I don't want the whole page to be included).

Thanks in advance
Timo

Matthew Westcott

unread,
Nov 16, 2015, 10:29:31 AM11/16/15
to wag...@googlegroups.com
Hi Timo,

Any time you've retrieved a StreamField value (e.g. by accessing value.specific.body within a template), you have the option of iterating over it rather than rendering it directly - this will give you a sequence of objects with 'block_type' and 'value' properties:

{% for block in value.specific.body %}
{% if block.block_type == 'person' %}
<li>{{ block.value.first_name }}</li>
{% endif %}
{% endfor %}

As Brett mentions, it's often cleaner to do this sort of logic within Python code rather than in the template. There are a couple of approaches that would work here, the simplest being to add a method to the TestPage model:

class TestPage(CorePage):
...
def contributor_names(self):
return [block.value.first_name for block in self.body if block.block_type == 'person']

- and then accessing {{ value.specific.contributor_names }} in the template. Alternatively, as of Wagtail 1.2, all block types support a 'get_context' method for passing additional context variables to the block's template - you'd define this on your servicepage block by subclassing PageChooserBlock (rather than using PageChooserBlock directly):

class ServicePageBlock(blocks.PageChooserBlock):
def get_context(self, value):
context = super(EventBlock, self).get_context(value)
context['contributor_names'] = [block.value.first_name for block in self.body if block.block_type == 'person']
return context

class Meta:
template = 'mysite/blocks/page.html'

'contributor_names' will then be available as a variable on mysite/blocks/page.html.

Cheers,
- Matt
> --
> You received this message because you are subscribed to the Google Groups "Wagtail support" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to wagtail+u...@googlegroups.com.
> To post to this group, send email to wag...@googlegroups.com.
> Visit this group at http://groups.google.com/group/wagtail.
> To view this discussion on the web, visit https://groups.google.com/d/msgid/wagtail/020b542c-c075-494e-83b7-c76df73b5f5f%40googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Timo Rieber

unread,
Nov 17, 2015, 3:11:25 AM11/17/15
to Wagtail support
Thanks, Matt.

Before I stated my question I tried iterating over value.specific.body as you did in your example, but with no luck. Yesterday I gave it a second try with even simpler blocks and relations and it worked. Perhaps I missed something. Will check this out with my original code in the next few days.

Shift the logic to python is also a good idea, but for now remains my fallback, as I try to discover how far (and still clean) I'll get with the template-only approach.

Again thanks to all for the useful help.

Cheers,
Timo

Brett Grace

unread,
Nov 17, 2015, 1:06:21 PM11/17/15
to Wagtail support
If you find this occurs again in your more complex case, consider writing a unit test to duplicate what your template is trying to do, and then narrow it down from there.

Debugging templates is unnecessarily hard because Django's template engine is not very strict, so it will swallow some exceptions or implicitly convert missing values to falsey ones. A little bit of Googling turned up some monkey patching you can do to the some of the exception handling classes to force them to spill a stack trace. Perhaps more recent versions of Django provide a better mechanism.

If you are using PyCharm or IntelliJ IDEA you can trap template exceptions in the graphical debugger (I'm sure you can do this in other environments, too, I just can't say which ones or how)... however you will catch a lot of exceptions before you get to the one you seek. (If there is one...)

Good luck!
Reply all
Reply to author
Forward
0 new messages