jinja icon indicating copy to clipboard operation
jinja copied to clipboard

Unable to override Imported macros from a child template with super()

Open funseiki opened this issue 8 years ago • 3 comments

It seems that macros defined by a child block are not passed in the context sent to imported macros.

In the example below, I have a macro which defines 'Foo'. The 'base' template uses import with context to load it in and then calls 'Foo'.

A child template extends "base" and redefines Foo. It then calls super() within a block. Doing so runs the Foo macro defined in 'macros' rather than the one defined in 'sub'.

~~Note: Using include instead import with context (or even import ), shows the desired output.~~ includes don't seem to actually allow for macros to be used.


Expected Behavior

sub Foo

Actual Behavior

base Foo

Template Code

from jinja2 import Environment, DictLoader

def test():
    d = DictLoader({
        'macros': """
# if not Foo
# macro Foo()
base Foo
# endmacro
# endif
        """,
        'base': """
# from "macros" import Foo with context
# block myblock
{{ Foo() }}
# endblock
        """,
        'sub': """
# extends "base"
# macro Foo()
sub Foo
# endmacro
# block myblock
{{ super() }}
# endblock
        """
    })

    env = Environment(loader=d, line_statement_prefix="#", line_comment_prefix="##")
    template = env.get_template("sub")
    string = template.render()
    print(string)
    return

if __name__ == '__main__':
    test()

Your Environment

  • Python version: 2.7.10
  • Jinja version: 2.9.5
  • OS: macOS 10.12.3

funseiki avatar Mar 22 '17 21:03 funseiki

The workaround for this appears to be to do the macro definition check before importing for each macro defined.

E.g.

# if not Foo
# from "macros" import Foo
# endif

funseiki avatar Mar 22 '17 21:03 funseiki

I think something similar is described in Null-Master Fallback tip. Using if not defined import is the recommended way.

There are no autotests for this specific behavior of macros. Should we add?

ngr avatar May 16 '18 21:05 ngr

It would be delightful to have default macros and override macros.

{% from overrides import this, that, the_other %}
{% for item in whatever %}
    {{this(item)}} {{that(item)}} {{the_other(item)}}
{% endfor %}

In the overrides template, we find this.

{% from defaults import this, that, the_other %}
{% this(item) %}
    <div><p>Preface {{item}}</p><p>Note: overridden macro with localized behavior</p></div>
{% endmacro %}

The defaults template has all of the macros with their default behavior.

This approach feels like it would allow macro overrides for applications that make more use of macros than blocks.

Here's my understanding of the current alternative:

    {%- from macros import this, that, the_other -%}
    {%- if not this is defined %}{%- from defaults import this -%}{%- endif -%}
    {%- if not that is defined %}{%- from defaults import that -%}{%- endif -%}
    {%- if not the_other is defined %}{%- from defaults import the_other -%}{%- endif -%}
{% for item in whatever %}
    {{this(item)}} {{that(item)}} {{the_other(item)}}
{% endfor %}

Pragmatically, it's not bad when there are a few macros. I've got a dozen, and it's a bit of a bother in the "consuming" template to preface the actual work with all those if not the_other is defined constructs.

slott56 avatar Jul 04 '22 18:07 slott56