- Remove code that was marked deprecated.
- Use :pep:`451` API to load templates with
:class:`~loaders.PackageLoader`. :issue:`1168`
+- Fix a bug that caused imported macros to not have access to the
+ current template's globals. :issue:`688`
Version 2.11.2
def wrap_default_module(original_default_module):
@internalcode
- def _get_default_module(self):
+ def _get_default_module(self, ctx=None):
if self.environment.is_async:
raise RuntimeError("Template module attribute is unavailable in async mode")
- return original_default_module(self)
+ return original_default_module(self, ctx)
return _get_default_module
elif self.environment.is_async:
self.write("_get_default_module_async()")
else:
- self.write("_get_default_module()")
+ self.write("_get_default_module(context)")
if frame.toplevel and not node.target.startswith("_"):
self.writeline(f"context.exported_vars.discard({node.target!r})")
elif self.environment.is_async:
self.write("_get_default_module_async()")
else:
- self.write("_get_default_module()")
+ self.write("_get_default_module(context)")
var_names = []
discarded_names = []
)
@internalcode
- def _get_default_module(self):
+ def _get_default_module(self, ctx=None):
+ """If a context is passed in, this means that the template was
+ imported. Imported templates have access to the current template's
+ globals by default, but they can only be accessed via the context
+ during runtime.
+
+ If there are new globals, we need to create a new
+ module because the cached module is already rendered and will not have
+ access to globals from the current context. This new module is not
+ cached as :attr:`_module` because the template can be imported elsewhere,
+ and it should have access to only the current template's globals.
+ """
+ if ctx is not None:
+ globals = {
+ key: ctx.parent[key] for key in ctx.globals_keys - self.globals.keys()
+ }
+ if globals:
+ return self.make_module(globals)
if self._module is not None:
return self._module
self._module = rv = self.make_module()
for key, value in locals.items():
if value is not missing:
parent[key] = value
- return environment.context_class(environment, parent, template_name, blocks)
+ return environment.context_class(
+ environment, parent, template_name, blocks, globals=globals
+ )
class TemplateReference:
_legacy_resolve_mode = False
_fast_resolve_mode = False
- def __init__(self, environment, parent, name, blocks):
+ def __init__(self, environment, parent, name, blocks, globals=None):
self.parent = parent
self.vars = {}
self.environment = environment
self.eval_ctx = EvalContext(self.environment, name)
self.exported_vars = set()
self.name = name
+ self.globals_keys = set() if globals is None else set(globals)
# create the initial mapping of blocks. Whenever template inheritance
# takes place the runtime will update this mapping with the new blocks
env = Environment(
loader=DictLoader(
{
- "macros": "{% macro testing() %}foo: {{ foo }}{% endmacro %}",
- "test": "{% import 'macros' as m %}{{ m.testing() }}",
+ "macros": "{% macro test() %}foo: {{ foo }}{% endmacro %}",
+ "test": "{% import 'macros' as m %}{{ m.test() }}",
+ "test1": "{% import 'macros' as m %}{{ m.test() }}",
}
)
)
tmpl = env.get_template("test", globals={"foo": "bar"})
assert tmpl.render() == "foo: bar"
+ tmpl = env.get_template("test1")
+ assert tmpl.render() == "foo: "
+
+ def test_import_with_globals_override(self, test_env):
+ env = Environment(
+ loader=DictLoader(
+ {
+ "macros": "{% set foo = '42' %}{% macro test() %}"
+ "foo: {{ foo }}{% endmacro %}",
+ "test": "{% from 'macros' import test %}{{ test() }}",
+ }
+ )
+ )
+ tmpl = env.get_template("test", globals={"foo": "bar"})
+ assert tmpl.render() == "foo: 42"
+
def test_from_import_with_globals(self, test_env):
env = Environment(
loader=DictLoader(