From ea847c5f3352393a69461255412311941add9a78 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 2 May 2008 20:04:32 +0200 Subject: [PATCH] added "with context" or "without context" import/include modifiers --HG-- branch : trunk --- docs/templates.rst | 27 ++++++++++++++++++++++++--- jinja2/compiler.py | 28 +++++++++++++++++++++------- jinja2/environment.py | 12 +++++++++++- jinja2/lexer.py | 2 +- jinja2/nodes.py | 6 +++--- jinja2/parser.py | 36 +++++++++++++++++++++++++++++------- 6 files changed, 89 insertions(+), 22 deletions(-) diff --git a/docs/templates.rst b/docs/templates.rst index 8e5b023..18bd422 100644 --- a/docs/templates.rst +++ b/docs/templates.rst @@ -552,8 +552,9 @@ rendered contents of that file into the current namespace:: Body {% include 'footer.html' %} -Included templates have access to the current template variables minus local -modifications. +Included templates have access to the variables of the active context by +default. For more details about context behavior of imports and includes +see :ref:`import-visibility`. .. _import: @@ -564,7 +565,8 @@ Jinja2 supports putting often used code into macros. These macros can go into different templates and get imported from there. This works similar to the import statements in Python. It's important to know that imports are cached and imported templates don't have access to the current template variables, -just the globals. +just the globals by defualt. For more details about context behavior of +imports and includes see :ref:`import-visibility`. There are two ways to import templates. You can import the complete template into a variable or request specific macros / exported variables from it. @@ -606,6 +608,25 @@ namespace::

{{ textarea('comment') }}

+.. _import-visibility: + +Import Context Behavior +----------------------- + +Per default included templates are passed the current context and imported +templates not. The reason for this is that imports unlike includes are +cached as imports are often used just as a module that holds macros. + +This however can be changed of course explicitly. By adding `with context` +or `without context` to the import/include directive the current context +can be passed to the template and caching is disabled automatically. + +Here two examples:: + + {% from 'forms.html' import input with context %} + {% include 'header.html' without context %} + + .. _expressions: Expressions diff --git a/jinja2/compiler.py b/jinja2/compiler.py index faa8b41..3d4fcbe 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -701,11 +701,17 @@ class CodeGenerator(NodeVisitor): def visit_Include(self, node, frame): """Handles includes.""" - self.writeline('included_template = environment.get_template(', node) - self.visit(node.template, frame) - self.write(', %r)' % self.name) - self.writeline('for event in included_template.root_render_func(' - 'included_template.new_context(context.parent, True)):') + if node.with_context: + self.writeline('template = environment.get_template(', node) + self.visit(node.template, frame) + self.write(', %r)' % self.name) + self.writeline('for event in template.root_render_func(' + 'template.new_context(context.parent, True)):') + else: + self.writeline('for event in environment.get_template(', node) + self.visit(node.template, frame) + self.write(', %r).module._TemplateModule__body_stream:' % + self.name) self.indent() if frame.buffer is None: self.writeline('yield event') @@ -720,7 +726,11 @@ class CodeGenerator(NodeVisitor): self.write('context.vars[%r] = ' % node.target) self.write('environment.get_template(') self.visit(node.template, frame) - self.write(', %r).module' % self.name) + self.write(', %r).' % self.name) + if node.with_context: + self.write('make_module(context.parent, True)') + else: + self.write('module') if frame.toplevel and not node.target.startswith('__'): self.writeline('context.exported_vars.discard(%r)' % node.target) @@ -729,7 +739,11 @@ class CodeGenerator(NodeVisitor): self.newline(node) self.write('included_template = environment.get_template(') self.visit(node.template, frame) - self.write(', %r).module' % self.name) + self.write(', %r).' % self.name) + if node.with_context: + self.write('make_module(context.parent, True)') + else: + self.write('module') for name in node.names: if isinstance(name, tuple): name, alias = name diff --git a/jinja2/environment.py b/jinja2/environment.py index ef916a4..68571fd 100644 --- a/jinja2/environment.py +++ b/jinja2/environment.py @@ -529,6 +529,12 @@ class Template(object): parent = dict(self.globals, **vars) return Context(self.environment, parent, self.name, self.blocks) + def make_module(self, vars=None, shared=False): + """Like the `module` property but always reevaluates the template + and it's possible to provide a context. + """ + return TemplateModule(self, self.new_context(vars, shared)) + @property def module(self): """The template as module. This is used for imports in the @@ -543,7 +549,7 @@ class Template(object): """ if hasattr(self, '_module'): return self._module - self._module = rv = TemplateModule(self, self.new_context()) + self._module = rv = self.make_module() return rv def get_corresponding_lineno(self, lineno): @@ -583,6 +589,10 @@ class TemplateModule(object): """ def __init__(self, template, context): + # don't alter this attribute unless you change it in the + # compiler too. The Include without context passing directly + # uses the mangled name. The reason why we use a mangled one + # is to avoid name clashes with macros with those names. self.__body_stream = tuple(template.root_render_func(context)) self.__dict__.update(context.get_exported()) self.__name__ = template.name diff --git a/jinja2/lexer.py b/jinja2/lexer.py index ade9f92..3b65b95 100644 --- a/jinja2/lexer.py +++ b/jinja2/lexer.py @@ -218,7 +218,7 @@ class TokenStream(object): self.current = old_token return result - def skip(self, n): + def skip(self, n=1): """Got n tokens ahead.""" for x in xrange(n): self.next() diff --git a/jinja2/nodes.py b/jinja2/nodes.py index b3255d5..d0372e8 100644 --- a/jinja2/nodes.py +++ b/jinja2/nodes.py @@ -265,12 +265,12 @@ class Block(Stmt): class Include(Stmt): """A node that represents the include tag.""" - fields = ('template',) + fields = ('template', 'with_context') class Import(Stmt): """A node that represents the import tag.""" - fields = ('template', 'target') + fields = ('template', 'target', 'with_context') class FromImport(Stmt): @@ -284,7 +284,7 @@ class FromImport(Stmt): The list of names may contain tuples if aliases are wanted. """ - fields = ('template', 'names') + fields = ('template', 'names', 'with_context') class Trans(Stmt): diff --git a/jinja2/parser.py b/jinja2/parser.py index 84b110c..4239e25 100644 --- a/jinja2/parser.py +++ b/jinja2/parser.py @@ -123,8 +123,7 @@ class Parser(object): """Parse an if construct.""" node = result = nodes.If(lineno=self.stream.expect('name:if').lineno) while 1: - # TODO: exclude conditional expressions here - node.test = self.parse_tuple() + node.test = self.parse_tuple(no_condexpr=True) node.body = self.parse_statements(('name:elif', 'name:else', 'name:endif')) token = self.stream.next() @@ -152,10 +151,20 @@ class Parser(object): node.template = self.parse_expression() return node + def parse_import_context(self, node, default): + if (self.stream.current.test('name:with') or + self.stream.current.test('name:without')) and \ + self.stream.look().test('name:context'): + node.with_context = self.stream.next().value == 'with' + self.stream.skip() + else: + node.with_context = default + return node + def parse_include(self): node = nodes.Include(lineno=self.stream.next().lineno) node.template = self.parse_expression() - return node + return self.parse_import_context(node, True) def parse_import(self): node = nodes.Import(lineno=self.stream.next().lineno) @@ -166,17 +175,28 @@ class Parser(object): raise TemplateSyntaxError('can\'t assign imported template ' 'to %r' % node.target, node.lineno, self.filename) - return node + return self.parse_import_context(node, False) def parse_from(self): node = nodes.FromImport(lineno=self.stream.next().lineno) node.template = self.parse_expression() self.stream.expect('name:import') node.names = [] + + def parse_context(): + if self.stream.current.value in ('with', 'without') and \ + self.stream.look().test('name:context'): + node.with_context = self.stream.next().value == 'with' + self.stream.skip() + return True + return False + while 1: if node.names: self.stream.expect('comma') if self.stream.current.type is 'name': + if parse_context(): + break target = nodes.Name(self.stream.current.value, 'store') if not target.can_assign(): raise TemplateSyntaxError('can\'t import object named %r' @@ -198,12 +218,14 @@ class Parser(object): node.names.append((target.name, alias.value)) else: node.names.append(target.name) - if self.stream.current.type is not 'comma': + if parse_context() or self.stream.current.type is not 'comma': break else: break - if self.stream.current.type is 'comma': - self.stream.next() + if not hasattr(node, 'with_context'): + node.with_context = False + if self.stream.current.type is 'comma': + self.stream.next() return node def parse_signature(self, node): -- 2.26.2