added "with context" or "without context" import/include modifiers
--HG-- branch : trunk
This commit is contained in:
parent
4325e37d6a
commit
ea847c5f33
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user