added "with context" or "without context" import/include modifiers
authorArmin Ronacher <armin.ronacher@active-4.com>
Fri, 2 May 2008 18:04:32 +0000 (20:04 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Fri, 2 May 2008 18:04:32 +0000 (20:04 +0200)
--HG--
branch : trunk

docs/templates.rst
jinja2/compiler.py
jinja2/environment.py
jinja2/lexer.py
jinja2/nodes.py
jinja2/parser.py

index 8e5b0235ed7c1252fd1ddca1415548cbe11008cc..18bd422e5e4277c5279a3246eb7d0fa2374b35a4 100644 (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
index faa8b4151853b0004d7b5ded3cfa6c8e7dcb4f95..3d4fcbe604e9fbfab03c394b0a0b63997d6fcbbd 100644 (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
index ef916a45e14b0d49d70809420d6da9bdb354dfd8..68571fdeb6e4ad33f16c9763257ef34b54fc3c09 100644 (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
index ade9f9225ce7679c7399334b5d22b30061614fb7..3b65b95b3f318116f16f7ed19c1404511e4149ff 100644 (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()
index b3255d54f11f9b35445b00f3727d7d8d47535809..d0372e808a02b200d696c9f4eff4ffcecbc5b134 100644 (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):
index 84b110c331cc548e44269d473bc9dba524cf8439..4239e25d8b13211ef00432a25934767913c836c3 100644 (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):