Permalink
Browse files

Added support for optional `scoped` modifier to blocks.

--HG--
branch : trunk
  • Loading branch information...
mitsuhiko committed Feb 19, 2009
1 parent 271a0eb commit 74a0cd92cca22f947959c0125d59f472b8353e16
Showing with 92 additions and 39 deletions.
  1. +1 −0 CHANGES
  2. +26 −0 docs/templates.rst
  3. +6 −2 jinja2/compiler.py
  4. +3 −16 jinja2/environment.py
  5. +1 −1 jinja2/nodes.py
  6. +1 −0 jinja2/parser.py
  7. +45 −20 jinja2/runtime.py
  8. +9 −0 tests/test_inheritance.py
View
@@ -18,6 +18,7 @@ Version 2.2
- Fixed a bug that caused internal errors if names where used as iteration
variable and regular variable *after* the loop if that variable was unused
*before* the loop. (#331)
+- Added support for optional `scoped` modifier to blocks.
Version 2.1.1
-------------
View
@@ -378,6 +378,32 @@ readability::
However the name after the `endblock` word must match the block name.
+Block Nesting and Scope
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Blocks can be nested for more complex layouts. However per default blocks
+may not access variables from outer scopes::
+
+ {% for item in seq %}
+ <li>{% block loop_item %}{{ item }}{% endblock %}</li>
+ {% endfor %}
+
+This example would output empty ``<li>`` items because `item` is unavailable
+inside the block. The reason for this is that if the block is replaced by
+a child template a variable would appear that was not defined in the block or
+passed to the context.
+
+Starting with Jinja 2.2 you can explicitly specify that variables are
+available in a block by setting the block to "scoped" by adding the `scoped`
+modifier to a block declaration::
+
+ {% for item in seq %}
+ <li>{% block loop_item scoped %}{{ item }}{% endblock %}</li>
+ {% endfor %}
+
+ When overriding a block the `scoped` modifier does not have to be provided.
+
+
HTML Escaping
-------------
View
@@ -783,8 +783,12 @@ def visit_Block(self, node, frame):
self.writeline('if parent_template is None:')
self.indent()
level += 1
- self.writeline('for event in context.blocks[%r][0](context):' %
- node.name, node)
+ if node.scoped:
+ context = 'context.derived(locals())'
+ else:
+ context = 'context'
+ self.writeline('for event in context.blocks[%r][0](%s):' % (
+ node.name, context), node)
self.indent()
self.simple_write('event', frame)
self.outdent(level)
View
@@ -15,7 +15,7 @@
from jinja2.parser import Parser
from jinja2.optimizer import optimize
from jinja2.compiler import generate
-from jinja2.runtime import Undefined, Context
+from jinja2.runtime import Undefined, new_context
from jinja2.exceptions import TemplateSyntaxError
from jinja2.utils import import_string, LRUCache, Markup, missing, \
concat, consume
@@ -646,21 +646,8 @@ def new_context(self, vars=None, shared=False, locals=None):
`locals` can be a dict of local variables for internal usage.
"""
- if vars is None:
- vars = {}
- if shared:
- parent = vars
- else:
- parent = dict(self.globals, **vars)
- if locals:
- # if the parent is shared a copy should be created because
- # we don't want to modify the dict passed
- if shared:
- parent = dict(parent)
- for key, value in locals.iteritems():
- if key[:2] == 'l_' and value is not missing:
- parent[key[2:]] = value
- return Context(self.environment, parent, self.name, self.blocks)
+ return new_context(self.environment, self.name, self.blocks,
+ vars, shared, self.globals, locals)
def make_module(self, vars=None, shared=False, locals=None):
"""This method works like the :attr:`module` attribute when called
View
@@ -269,7 +269,7 @@ class FilterBlock(Stmt):
class Block(Stmt):
"""A node that represents a block."""
- fields = ('name', 'body')
+ fields = ('name', 'body', 'scoped')
class Include(Stmt):
View
@@ -149,6 +149,7 @@ def parse_if(self):
def parse_block(self):
node = nodes.Block(lineno=self.stream.next().lineno)
node.name = self.stream.expect('name').value
+ node.scoped = self.stream.skip_if('name:scoped')
node.body = self.parse_statements(('name:endblock',), drop_needle=True)
self.stream.skip_if('name:' + node.name)
return node
View
@@ -17,7 +17,7 @@
# these variables are exported to the template runtime
-__all__ = ['LoopContext', 'Context', 'TemplateReference', 'Macro', 'Markup',
+__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
'TemplateRuntimeError', 'missing', 'concat', 'escape',
'markup_join', 'unicode_join', 'TemplateNotFound']
@@ -42,6 +42,45 @@ def unicode_join(seq):
return concat(imap(unicode, seq))
+def new_context(environment, template_name, blocks, vars=None,
+ shared=None, globals=None, locals=None):
+ """Internal helper to for context creation."""
+ if vars is None:
+ vars = {}
+ if shared:
+ parent = vars
+ else:
+ parent = dict(globals or (), **vars)
+ if locals:
+ # if the parent is shared a copy should be created because
+ # we don't want to modify the dict passed
+ if shared:
+ parent = dict(parent)
+ for key, value in locals.iteritems():
+ if key[:2] == 'l_' and value is not missing:
+ parent[key[2:]] = value
+ return Context(environment, parent, template_name, blocks)
+
+
+class TemplateReference(object):
+ """The `self` in templates."""
+
+ def __init__(self, context):
+ self.__context = context
+
+ def __getitem__(self, name):
+ blocks = self.__context.blocks[name]
+ wrap = self.__context.environment.autoescape and \
+ Markup or (lambda x: x)
+ return BlockReference(name, self.__context, blocks, 0)
+
+ def __repr__(self):
+ return '<%s %r>' % (
+ self.__class__.__name__,
+ self.__context.name
+ )
+
+
class Context(object):
"""The template context holds the variables of a template. It stores the
values passed to the template and also the names the template exports.
@@ -132,6 +171,11 @@ def call(__self, __obj, *args, **kwargs):
args = (__self.environment,) + args
return __obj(*args, **kwargs)
+ def derived(self, locals=None):
+ """Internal helper function to create a derived context."""
+ return new_context(self.environment, self.name, self.blocks,
+ self.parent, True, None, locals)
+
def _all(meth):
proxy = lambda self: getattr(self.get_all(), meth)()
proxy.__doc__ = getattr(dict, meth).__doc__
@@ -174,25 +218,6 @@ def __repr__(self):
pass
-class TemplateReference(object):
- """The `self` in templates."""
-
- def __init__(self, context):
- self.__context = context
-
- def __getitem__(self, name):
- blocks = self.__context.blocks[name]
- wrap = self.__context.environment.autoescape and \
- Markup or (lambda x: x)
- return BlockReference(name, self.__context, blocks, 0)
-
- def __repr__(self):
- return '<%s %r>' % (
- self.__class__.__name__,
- self.__context.name
- )
-
-
class BlockReference(object):
"""One block on a template reference."""
@@ -166,3 +166,12 @@ def test_fixed_macro_scoping_bug():
{% block content %}&nbsp;{% endblock %}
'''
})).get_template("test.html").render().split() == [u'outer_box', u'my_macro']
+
+
+def test_scoped_block():
+ env = Environment(loader=DictLoader({
+ 'master.html': '{% for item in seq %}[{% block item scoped %}'
+ '{% endblock %}]{% endfor %}'
+ }))
+ t = env.from_string('{% extends "master.html" %}{% block item %}{{ item }}{% endblock %}')
+ assert t.render(seq=range(5)) == '[0][1][2][3][4]'

0 comments on commit 74a0cd9

Please sign in to comment.