]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
imported templates can see the current globals 1241/head
authorAmy <leiamy12@gmail.com>
Mon, 22 Jun 2020 13:54:08 +0000 (09:54 -0400)
committerDavid Lord <davidism@gmail.com>
Mon, 22 Jun 2020 16:56:10 +0000 (09:56 -0700)
_get_default_module takes an optional context to indicate that the
template is imported. If there are differences between the environment
and rendered template globals, a new module is used for the imported
template.

CHANGES.rst
src/jinja2/asyncsupport.py
src/jinja2/compiler.py
src/jinja2/environment.py
src/jinja2/runtime.py
tests/test_imports.py

index 57de4ae59df43989730695028a4addb4f8277686..d42f213c1feb016224249833e0b37a84b37fc71e 100644 (file)
@@ -11,6 +11,8 @@ Unreleased
 -   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
index 3aef7ad233cc7ad68b8530e4d21249f6a5ed3de7..e46a85a3a3527893c6a490d6ec2956a410bacdb6 100644 (file)
@@ -116,10 +116,10 @@ async def get_default_module_async(self):
 
 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
 
index 045a3a88dca30be63aeb97042a3f2d8e09521786..abdbe6da65f92c45457e25b0c2d773db8a7cf770 100644 (file)
@@ -925,7 +925,7 @@ class CodeGenerator(NodeVisitor):
         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})")
 
@@ -944,7 +944,7 @@ class CodeGenerator(NodeVisitor):
         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 = []
index 3c93c4843f867f3155943a91f8b34356c8a62d31..556f7255f051e67dbf7805c0b3c6a4376888531e 100644 (file)
@@ -1120,7 +1120,24 @@ class Template:
         )
 
     @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()
index 00f1f59f645f22fd2b24ca11fb8cd5f2c2ae7356..7b5925b16245fc1b5f1a973104f9f08f5ab04b5c 100644 (file)
@@ -97,7 +97,9 @@ def new_context(
         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:
@@ -179,13 +181,14 @@ class Context(metaclass=ContextMeta):
     _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
index bb7400f864c662de88d1ede557792ab7d45768a4..054c9010480dab6d4a0fe068bbd11f892a8e3d8b 100644 (file)
@@ -102,14 +102,31 @@ class TestImports:
         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(