From 5664bf0781c54e3728399fdf680fae40118b9a1d Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sun, 5 Jun 2011 15:41:25 -0700 Subject: [PATCH] Add a Template UIModule, allowing templates to be called with keyword arguments instead of inheriting the caller's namespace with {% include %}. --- demos/chat/templates/index.html | 2 +- tornado/test/web_test.py | 41 ++++++++++++- tornado/web.py | 105 ++++++++++++++++++++++++++------ 3 files changed, 129 insertions(+), 19 deletions(-) diff --git a/demos/chat/templates/index.html b/demos/chat/templates/index.html index 0e85ded2..70549797 100644 --- a/demos/chat/templates/index.html +++ b/demos/chat/templates/index.html @@ -13,7 +13,7 @@
{% for message in messages %} - {% include "message.html" %} + {% module Template("message.html", message=message) %} {% end %}
diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py index cfb12cf6..87ffabad 100644 --- a/tornado/test/web_test.py +++ b/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": """\ + +{% for e in entries %} +{% module Template("entry.html", entry=e) %} +{% end %} +""", + "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="", html_body=' + +' for p in paths) - sloc = html.rindex('') - html = html[:sloc] + js + '\n' + html[sloc:] + sloc = html.rindex(b('')) + html = html[:sloc] + utf8(js) + b('\n') + html[sloc:] if js_embed: - js = '' - sloc = html.rindex('') - html = html[:sloc] + js + '\n' + html[sloc:] + js = b('') + sloc = html.rindex(b('')) + html = html[:sloc] + js + b('\n') + html[sloc:] if css_files: paths = [] unique_paths = set() @@ -511,19 +511,19 @@ class RequestHandler(object): css = ''.join('' for p in paths) - hloc = html.index('') - html = html[:hloc] + css + '\n' + html[hloc:] + hloc = html.index(b('')) + html = html[:hloc] + utf8(css) + b('\n') + html[hloc:] if css_embed: - css = '' - hloc = html.index('') - html = html[:hloc] + css + '\n' + html[hloc:] + css = b('') + hloc = html.index(b('')) + html = html[:hloc] + css + b('\n') + html[hloc:] if html_heads: - hloc = html.index('') - html = html[:hloc] + ''.join(html_heads) + '\n' + html[hloc:] + hloc = html.index(b('')) + html = html[:hloc] + b('').join(html_heads) + b('\n') + html[hloc:] if html_bodies: - hloc = html.index('') - html = html[:hloc] + ''.join(html_bodies) + '\n' + html[hloc:] + hloc = html.index(b('')) + 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):