In a project that I'm working on I'd like to be able to declare some common macros which can conveniently be extended by a theme based on Jinja templates.
Here is a representative example of the way I have themes setup and structured:
$ tree
.
├── base
│ ├── common_macros.j2
│ └── example.j2
├── example.py
└── theme1
└── common_macros.j2
With example.py looking like:
from pathlib import Path
import jinja2
if __name__ == '__main__':
tmplt_root = Path(__file__).parent
# Setup the base loader to be able to use both "example.j2" and "base/example.j2",
# thereby allowing themes to implement "example.j2" by extending "base/example.j2".
base_template_paths = [str(tmplt_root), str(tmplt_root / 'base')]
env1 = jinja2.Environment(
loader=jinja2.FileSystemLoader(base_template_paths),
)
env_themed = jinja2.Environment(
loader=jinja2.FileSystemLoader(['theme1/'] + base_template_paths),
)
print('BASE')
print('----------')
print(env1.get_template('example.j2').render())
print('Theme 1')
print('----------')
print(env_themed.get_template('example.j2').render())
base/example.j2:
{% from "common_macros.j2" import macro1 %}
{% block main -%}
{{ macro1() }}
{%- endblock main %}
base/common_macros.j2:
{% macro macro1() -%}
Macro 1 base
{% endmacro %}
With this setup I'm able to implement an overriden macro in theme1/common_macros.j2 (and can even call the base implementation!):
{% import "base/common_macros.j2" as base %}
{% macro macro1() -%}
Theme macro 1!
{{ base.macro1() }}
{% endmacro %}
So far, so good!
$ python example.py
BASE
----------
Macro 1 base
Theme 1
----------
Theme macro 1!
Macro 1 base
The problem comes when I add another macro to base/common_macros.j2 (and use it in example.j2) at which point the following error occurs:
$ python example.py
BASE
----------
Macro 1 base
Macro 2 base
Theme 1
----------
Traceback (most recent call last):
File "example.py", line 25, in <module>
print(env_themed.get_template('example.j2').render())
File "jinja2/environment.py", line 1090, in render
self.environment.handle_exception()
File "jinja2/environment.py", line 832, in handle_exception
reraise(*rewrite_traceback_stack(source=source))
File "jinja2/_compat.py", line 28, in reraise
raise value.with_traceback(tb)
File "base/example.j2", line 3, in top-level template code
{% block main -%}
File "base/example.j2", line 5, in block "main"
{{ macro2() }}
jinja2.exceptions.UndefinedError: the template 'common_macros.j2' (imported on line 1 in 'example.j2') does not export the requested name 'macro2'
Clearly this is caused by the theme1/common_macros.j2 override not implementing all of the macros that are in the base.
My question: What can I do to make this work without needing to redefine all of the macros from base/common_macros.j2 in the theme1/common_macros.j2?
Approaches that I've tried and failed with so far:
Import all macros from
base/common_macros.j2intotheme1/common_macros.j2(equivalent offrom base import *in Python - remember, I don't want to write down all the macro names that exist in base in each theme, otherwise upgrades would get tedious!).Unfortunately this doesn't exist (Jinja templates, how to do an import of all macros to follow the DRY principle?)
Include the base macros with
{% include "base/common_macros.j2" %}in the theme (akin toextendsfor non-macro templates).Unfortunately only variables exposed explicitly in
theme1/common_macros.j2are visible (as per https://jinja.palletsprojects.com/en/2.10.x/templates/#import)
I'm more than happy to hear alternative ideas about how to do theming with Jinja if it leads to a solution to the problem and doesn't increase the complexity.