question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

A shortcut for accessing StreamField blocks by name

See original GitHub issue

Is your proposal related to a problem?

It’s common for people to want to retrieve a block of a particular type from a StreamField value - for example, if ‘hero image’ is one of the block types defined in a page’s content, a developer may want to pull out the first such hero image for use in listings or as a social share image. (An example of this in the wild…)

This can be done in user code (either in Python or Django template code) by looping over the StreamField value until the appropriate block is found - however, I think this is a common enough scenario that Wagtail should provide a standard method / idiom for it.

Describe the solution you’d like

The StreamValue class could implement a by_type method that returns a list of blocks matching the given type:

page.body.by_type('hero_image')  # returns a list of all `hero_image` blocks in the `body` StreamField

Passing a parameter to by_type in this way would not be possible within a template, so as an additional feature, by_type could return a (lazily-evaluated?) dict consisting of all blocks organised by type when invoked with no parameters. This would make it possible to write something like:

<h1>{{ page.body.by_type.heading.0 }}</h1>

The by_type method will need tests and documentation.

Describe alternatives you’ve considered

StreamValue could be updated to behave as something like a dict as well as a list, e.g.page.body['hero_image'] - however, this feels a bit ambiguous about whether it should return a single item or a list. (Also, in template code where foo.bar is equivalent to foo['bar'], it means the block names end up in the same namespace as methods on StreamValue, which feels icky.)

Given that people will probably be using this for retrieving a single item most of the time, we could borrow BeautifulSoup’s pattern of page.body.find('hero_image') (or page.body.find(type='hero_image')?) which returns the first item or None, and page.body.find_all('hero_image') which returns a list. It’s not so obvious how to make this syntax work nicely within templates, though.

Additional context

Perhaps something for a later phase, but it’s worth bearing in mind that for this kind of StreamField access, optimisations like bulk_to_python and #7239 are actually counter-productive, since they’re pre-fetching all database objects in the stream but we only want one of them. Maybe this calls for us to treat StreamValues more like querysets, where the actual data retrieval happens as late as possible and these kinds of ‘filter’ operations act as modifiers to that final lookup.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:5
  • Comments:9 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
Scotchestercommented, Jun 6, 2021

This seems like a great idea! What do you think about calling it blocks_by_type? Just by_type feels a little ambiguous to me.

1reaction
gasmancommented, Sep 10, 2021

@chosak That sounds fair. The distinction between ‘types’ and ‘names’ is a bit blurred, partly due to the detail that when you write something like blocks.CharBlock() in a block definition, you’re instantiating CharBlock even though you’re not notionally creating any objects, just writing a definition. And that’s not something we’re going to solve here…

I’m happy to go with blocks_by_name as the less ambiguous choice. The one snag is that - following the names from @Tijani-Dia’s PR - blocks_of_name and first_block_of_name don’t sound right, because blocks don’t “belong to” a name. So maybe the answer is to overload the methods, depending on whether you pass a block name or not:

  • blocks_by_name('heading') - returns a list of all heading blocks
  • blocks_by_name() - returns a dict mapping block names to the list of blocks for that name, so that you can write things like {% for block in page.body.blocks_by_name.heading %} in templates
  • first_block_by_name('heading') - returns the first heading block, or None if no blocks of that type exist

and maybe (for symmetry)…

  • first_block_by_name() - returns a dict mapping block names to the first block for that name (or None if no such blocks exist), so that you can write {{ page.body.first_block_by_name.heading }}
Read more comments on GitHub >

github_iconTop Results From Across the Web

How to use StreamField for mixed content
The block types available to authors are defined as a list of (name, block_type) tuples: 'name' is used to identify the block type...
Read more >
How to retrieve Page content from streamfield in Wagtail CMS?
I've created SolutionPage and inside It's content field there is nested short_portfolio block. I've added several ProjectPage instances via ...
Read more >
Freeform page content using StreamField
The parameter to StreamField is a list of (name, block_type) tuples. 'name' is used to identify the block type within templates and the...
Read more >
How to Add a RichText StreamField to your Wagtail CMS Page
In this video we are going to learn how to create a RichTextBlock StreamField, and then we're going to duplicate that StreamField and...
Read more >
wagtail Changelog - pyup.io
Docs: Add class inheritance information to StreamField block sreference (Temidayo ... Add shortcut for accessing StreamField blocks by block name with new ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found