Permalink
Browse files

Add a Template UIModule, allowing templates to be called with keyword

arguments instead of inheriting the caller's namespace with {% include %}.
  • Loading branch information...
1 parent ce3e1de commit 5664bf0781c54e3728399fdf680fae40118b9a1d @bdarnell bdarnell committed Jun 5, 2011
Showing with 129 additions and 19 deletions.
  1. +1 −1 demos/chat/templates/index.html
  2. +40 −1 tornado/test/web_test.py
  3. +88 −17 tornado/web.py
View
2 demos/chat/templates/index.html
@@ -13,7 +13,7 @@
<div id="body">
<div id="inbox">
{% for message in messages %}
- {% include "message.html" %}
+ {% module Template("message.html", message=message) %}
{% end %}
</div>
<div id="input">
View
41 tornado/test/web_test.py
@@ -241,16 +241,30 @@ class LinkifyHandler(RequestHandler):
def get(self):
self.render("linkify.html", message="http://example.com")
+class UIModuleResourceHandler(RequestHandler):
+ def get(self):
+ self.render("page.html", entries=[1,2])
+
class WebTest(AsyncHTTPTestCase, LogTrapTestCase):
def get_app(self):
loader = DictLoader({
- "linkify.html": "{% module linkify(message) %}"
+ "linkify.html": "{% module linkify(message) %}",
+ "page.html": """\
+<html><head></head><body>
+{% for e in entries %}
+{% module Template("entry.html", entry=e) %}
+{% end %}
+</body></html>""",
+ "entry.html": """\
+{{ set_resources(embedded_css=".entry { margin-bottom: 1em; }", embedded_javascript="js_embed()", css_files=["/base.css", "/foo.css"], javascript_files="/common.js", html_head="<meta>", html_body='<script src="/analytics.js"/>') }}
+<div class="entry">...</div>""",
})
urls = [
url("/typecheck/(.*)", TypeCheckHandler, name='typecheck'),
url("/decode_arg/(.*)", DecodeArgHandler),
url("/decode_arg_kw/(?P<arg>.*)", DecodeArgHandler),
url("/linkify", LinkifyHandler),
+ url("/uimodule_resources", UIModuleResourceHandler),
]
return Application(urls,
template_loader=loader,
@@ -291,3 +305,28 @@ def test_uimodule_unescaped(self):
response = self.fetch("/linkify")
self.assertEqual(response.body,
b("<a href=\"http://example.com\">http://example.com</a>"))
+
+ def test_uimodule_resources(self):
+ response = self.fetch("/uimodule_resources")
+ self.assertEqual(response.body, b("""\
+<html><head><link href="/base.css" type="text/css" rel="stylesheet"/><link href="/foo.css" type="text/css" rel="stylesheet"/>
+<style type="text/css">
+.entry { margin-bottom: 1em; }
+</style>
+<meta>
+</head><body>
+
+
+<div class="entry">...</div>
+
+
+<div class="entry">...</div>
+
+<script src="/common.js" type="text/javascript"></script>
+<script type="text/javascript">
+//<![CDATA[
+js_embed()
+//]]>
+</script>
+<script src="/analytics.js"/>
+</body></html>"""))
View
105 tornado/web.py
@@ -492,13 +492,13 @@ def is_absolute(path):
js = ''.join('<script src="' + escape.xhtml_escape(p) +
'" type="text/javascript"></script>'
for p in paths)
- sloc = html.rindex('</body>')
- html = html[:sloc] + js + '\n' + html[sloc:]
+ sloc = html.rindex(b('</body>'))
+ html = html[:sloc] + utf8(js) + b('\n') + html[sloc:]
if js_embed:
- js = '<script type="text/javascript">\n//<![CDATA[\n' + \
- '\n'.join(js_embed) + '\n//]]>\n</script>'
- sloc = html.rindex('</body>')
- html = html[:sloc] + js + '\n' + html[sloc:]
+ js = b('<script type="text/javascript">\n//<![CDATA[\n') + \
+ b('\n').join(js_embed) + b('\n//]]>\n</script>')
+ sloc = html.rindex(b('</body>'))
+ html = html[:sloc] + js + b('\n') + html[sloc:]
if css_files:
paths = []
unique_paths = set()
@@ -511,19 +511,19 @@ def is_absolute(path):
css = ''.join('<link href="' + escape.xhtml_escape(p) + '" '
'type="text/css" rel="stylesheet"/>'
for p in paths)
- hloc = html.index('</head>')
- html = html[:hloc] + css + '\n' + html[hloc:]
+ hloc = html.index(b('</head>'))
+ html = html[:hloc] + utf8(css) + b('\n') + html[hloc:]
if css_embed:
- css = '<style type="text/css">\n' + '\n'.join(css_embed) + \
- '\n</style>'
- hloc = html.index('</head>')
- html = html[:hloc] + css + '\n' + html[hloc:]
+ css = b('<style type="text/css">\n') + b('\n').join(css_embed) + \
+ b('\n</style>')
+ hloc = html.index(b('</head>'))
+ html = html[:hloc] + css + b('\n') + html[hloc:]
if html_heads:
- hloc = html.index('</head>')
- html = html[:hloc] + ''.join(html_heads) + '\n' + html[hloc:]
+ hloc = html.index(b('</head>'))
+ html = html[:hloc] + b('').join(html_heads) + b('\n') + html[hloc:]
if html_bodies:
- hloc = html.index('</body>')
- html = html[:hloc] + ''.join(html_bodies) + '\n' + html[hloc:]
+ hloc = html.index(b('</body>'))
+ html = html[:hloc] + b('').join(html_bodies) + b('\n') + html[hloc:]
self.finish(html)
def render_string(self, template_name, **kwargs):
@@ -1102,7 +1102,9 @@ def __init__(self, handlers=None, default_host="", transforms=None,
self.default_host = default_host
self.settings = settings
self.ui_modules = {'linkify': _linkify,
- 'xsrf_form_html': _xsrf_form_html}
+ 'xsrf_form_html': _xsrf_form_html,
+ 'Template': TemplateModule,
+ }
self.ui_methods = {}
self._wsgi = wsgi
self._load_ui_modules(settings.get("ui_modules", {}))
@@ -1618,6 +1620,75 @@ class _xsrf_form_html(UIModule):
def render(self):
return self.handler.xsrf_form_html()
+class TemplateModule(UIModule):
+ """UIModule that simply renders the given template.
+
+ {% module Template("foo.html") %} is similar to {% include "foo.html" %},
+ but the module version gets its own namespace (with kwargs passed to
+ Template()) instead of inheriting the outer template's namespace.
+
+ Templates rendered through this module also get access to UIModule's
+ automatic javascript/css features. Simply call set_resources
+ inside the template and give it keyword arguments corresponding to
+ the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }}
+ Note that these resources are output once per template file, not once
+ per instantiation of the template, so they must not depend on
+ any arguments to the template.
+ """
+ def __init__(self, handler):
+ super(TemplateModule, self).__init__(handler)
+ # keep resources in both a list and a dict to preserve order
+ self._resource_list = []
+ self._resource_dict = {}
+
+ def render(self, path, **kwargs):
+ def set_resources(**kwargs):
+ if path not in self._resource_dict:
+ self._resource_list.append(kwargs)
+ self._resource_dict[path] = kwargs
+ else:
+ if self._resource_dict[path] != kwargs:
+ raise ValueError("set_resources called with different "
+ "resources for the same template")
+ return ""
+ return self.render_string(path, set_resources=set_resources,
+ **kwargs)
+
+ def _get_resources(self, key):
+ return (r[key] for r in self._resource_list if key in r)
+
+ def embedded_javascript(self):
+ return "\n".join(self._get_resources("embedded_javascript"))
+
+ def javascript_files(self):
+ result = []
+ for f in self._get_resources("javascript_files"):
+ if isinstance(f, (unicode, bytes_type)):
+ result.append(f)
+ else:
+ result.extend(f)
+ return result
+
+ def embedded_css(self):
+ return "\n".join(self._get_resources("embedded_css"))
+
+ def css_files(self):
+ result = []
+ for f in self._get_resources("css_files"):
+ if isinstance(f, (unicode, bytes_type)):
+ result.append(f)
+ else:
+ result.extend(f)
+ return result
+
+ def html_head(self):
+ return "".join(self._get_resources("html_head"))
+
+ def html_body(self):
+ return "".join(self._get_resources("html_body"))
+
+
+
class URLSpec(object):
"""Specifies mappings between URLs and handlers."""
def __init__(self, pattern, handler_class, kwargs={}, name=None):

0 comments on commit 5664bf0

Please sign in to comment.