Skip to content

Footnotes are not replaced when RichTextWithFootnotesBlock is a StructBlock sub-block #22

@ababic

Description

@ababic

When you include a RichTextWithFootnotesBlock in a StreamBlock definition, things work great! This is because StreamBlock.to_python returns a StreamValue, which has a render_as_block() method that feeds back into StreamBlock.render_basic() to do the actual rendering... which calls the render() method of each sub-block, ultimately leading to RichTextWithFootnotesBlock.replace_footnote_tags() doing its thing.

When you include a RichTextWithFootnotesBlock as a sub-block of a StructBlock, things work out a little differently. The tendency is to do the following in StructBlock templates:

{% load wagtailcore_tags %}
{{ value.caption|richtext }}

This definitely won't trigger RichTextWithFootnotesBlock.replace_footnote_tags(), because we're basically just taking the value from the block and rendering it directly.

I thought that changing all instances of the above would resolve the problem:

{% load wagtailcore_tags %}
{% include_block value.caption %}

However, that didn't do the job because {% include_block %} only does anything special with the value if it has a render_as_block() method (like StreamValue does). If not, the value itself is used as the output value, which isn't really what we want.

What we want is for RichTextWithFootnotesBlock.render() to always be used in rendering, and in order to do that, RichTextWithFootnotesBlock needs to return something other than an instance of RichText... we need something with a render_as_block() method (like StreamValue does), so that we can always bring things back to RichTextWithFootnotesBlock.render(). Something like this worked in a project I am currently working on:

class RichTextBlockValue:
    """
    Value type returned by CustomRichTextBlockWithFootnotes.to_python().

    When rendered in a StructBlock, FieldBlock values are usually rendered directly, bypassing the
    footnote replacement logic that is usually triggered by RichTextBlockWithFootnotes.render(). 
    If the block instead returns a value with a 'render_as_block()' method, we can always reliably
    hand things back to RichTextBlockWithFootnotes.render() to take care of rendering, where the
    footnote replacement logic will kick-in.
    """

    def __init__(self, block, value):
        self.block = block
        self.source = value
        self.value = RichText(value)

    def render_as_block(self, context=None):
        return self.block.render(self, context=context)

    def __html__(self):
        return self.block.render(self)

    def __str__(self):
        return self.__html__()


class CustomRichTextWithFootnotesBlock(RichTextWithFootnotesBlock):
    def to_python(self, value):
        if isinstance(value, RichTextBlockValue):
            return value
        return RichTextBlockValue(self, value)

    def render(self, value, context=None):
        if isinstance(value, RichTextBlockValue):
            value = value.value
        return super().render(value, context=context)
   

With these changes, as long as I use {% include_block %} in all StructBlock templates, the footnotes are replaced successfully, wherever they are used:

{% load wagtailcore_tags %}
{% include_block value.caption %}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions