Add a Template UIModule, allowing templates to be called with keyword
arguments instead of inheriting the caller's namespace with {% include %}.
This commit is contained in:
parent
ce3e1dedaf
commit
5664bf0781
|
@ -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">
|
||||
|
|
|
@ -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 @@ class WebTest(AsyncHTTPTestCase, LogTrapTestCase):
|
|||
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
tornado/web.py
105
tornado/web.py
|
@ -492,13 +492,13 @@ class RequestHandler(object):
|
|||
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 @@ class RequestHandler(object):
|
|||
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 @@ class Application(object):
|
|||
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):
|
||||
|
|
Loading…
Reference in New Issue