From d67f0fd4cc2a4af08f51f4466150d49da7798729 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 7 Jan 2017 15:35:21 +0100 Subject: [PATCH] Generalize scoping. This fixes #603 --- CHANGES | 5 +++++ jinja2/compiler.py | 6 +++--- jinja2/runtime.py | 9 +++++++-- tests/test_regression.py | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 7ed12dc8..bd0dc75b 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,11 @@ Version 2.9 tests in one expression without extra parentheses. In particular you can now write ``foo is divisibleby 2 or foo is divisibleby 3`` as you would expect. +- Greatly changed the scoping system to be more consistent with what template + designers and developers expect. There is now no more magic difference + between the different include and import constructs. Context is now always + propagated the same way. The only remaining differences is the defaults + for `with context` and `without context`. Version 2.8.2 ------------- diff --git a/jinja2/compiler.py b/jinja2/compiler.py index 6595d632..6b2212e6 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -866,7 +866,7 @@ class CodeGenerator(NodeVisitor): if node.with_context: loop = self.environment.is_async and 'async for' or 'for' self.writeline('%s event in template.root_render_func(' - 'template.new_context(context.parent, True, ' + 'template.new_context(context.get_all(), True, ' '%s)):' % (loop, self.dump_local_context(frame))) elif self.environment.is_async: self.writeline('for event in (await ' @@ -900,7 +900,7 @@ class CodeGenerator(NodeVisitor): self.visit(node.template, frame) self.write(', %r).' % self.name) if node.with_context: - self.write('make_module%s(context.parent, True, %s)' + self.write('make_module%s(context.get_all(), True, %s)' % (self.environment.is_async and '_async' or '', self.dump_local_context(frame))) elif self.environment.is_async: @@ -918,7 +918,7 @@ class CodeGenerator(NodeVisitor): self.visit(node.template, frame) self.write(', %r).' % self.name) if node.with_context: - self.write('make_module%s(context.parent, True, %s)' + self.write('make_module%s(context.get_all(), True, %s)' % (self.environment.is_async and '_async' or '', self.dump_local_context(frame))) elif self.environment.is_async: diff --git a/jinja2/runtime.py b/jinja2/runtime.py index 43f25063..95268e5a 100644 --- a/jinja2/runtime.py +++ b/jinja2/runtime.py @@ -170,9 +170,14 @@ class Context(object): return dict((k, self.vars[k]) for k in self.exported_vars) def get_all(self): - """Return a copy of the complete context as dict including the - exported variables. + """Return the complete context as dict including the exported + variables. For optimizations reasons this might not return an + actual copy so be careful with using it. """ + if not self.vars: + return self.parent + if not self.parent: + return self.vars return dict(self.parent, **self.vars) @internalcode diff --git a/tests/test_regression.py b/tests/test_regression.py index 4deaebe9..7d2ad3a8 100644 --- a/tests/test_regression.py +++ b/tests/test_regression.py @@ -352,3 +352,38 @@ class TestBug(object): ''') assert list(map(int, tmpl.render().split())) == \ [3, 2, 1, 5, 4, 3, 7, 6, 5] + + def test_scopes_and_blocks(self): + env = Environment(loader=DictLoader({ + 'a.html': ''' + {%- set foo = 'bar' -%} + {% include 'x.html' -%} + ''', + 'b.html': ''' + {%- set foo = 'bar' -%} + {% block test %}{% include 'x.html' %}{% endblock -%} + ''', + 'c.html': ''' + {%- set foo = 'bar' -%} + {% block test %}{% set foo = foo + %}{% include 'x.html' %}{% endblock -%} + ''', + 'x.html': '''{{ foo }}|{{ test }}''' + })) + + a = env.get_template('a.html') + b = env.get_template('b.html') + c = env.get_template('c.html') + + assert a.render(test='x').strip() == 'bar|x' + assert b.render(test='x').strip() == 'bar|x' + assert c.render(test='x').strip() == 'bar|x' + + def test_scopes_and_include(self): + env = Environment(loader=DictLoader({ + 'include.html': '{{ var }}', + 'base.html': '{% include "include.html" %}', + 'child.html': '{% extends "base.html" %}{% set var = 42 %}', + })) + t = env.get_template('child.html') + assert t.render() == '42' -- 2.47.2