Skip to content

Commit

Permalink
Add a Template UIModule, allowing templates to be called with keyword
Browse files Browse the repository at this point in the history
arguments instead of inheriting the caller's namespace with {% include %}.
  • Loading branch information
bdarnell committed Jun 5, 2011
1 parent ce3e1de commit 5664bf0
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 19 deletions.
2 changes: 1 addition & 1 deletion demos/chat/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
41 changes: 40 additions & 1 deletion tornado/test/web_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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>"""))
105 changes: 88 additions & 17 deletions tornado/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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):
Expand Down Expand Up @@ -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", {}))
Expand Down Expand Up @@ -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):
Expand Down

0 comments on commit 5664bf0

Please sign in to comment.