added "with context" or "without context" import/include modifiers

--HG--
branch : trunk
This commit is contained in:
Armin Ronacher 2008-05-02 20:04:32 +02:00
parent 4325e37d6a
commit ea847c5f33
6 changed files with 89 additions and 22 deletions

View File

@ -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::
<p>{{ textarea('comment') }}</p>
.. _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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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):

View File

@ -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):