]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add a Template UIModule, allowing templates to be called with keyword
authorBen Darnell <ben@bendarnell.com>
Sun, 5 Jun 2011 22:41:25 +0000 (15:41 -0700)
committerBen Darnell <ben@bendarnell.com>
Sun, 5 Jun 2011 22:41:25 +0000 (15:41 -0700)
arguments instead of inheriting the caller's namespace with {% include %}.

demos/chat/templates/index.html
tornado/test/web_test.py
tornado/web.py

index 0e85ded2806226868181792c714c18d8cb9d474e..70549797b136ca52f10598e454ef0bdd2ba09342 100644 (file)
@@ -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">
index cfb12cf6e087dcffd975f07fea720e910833e940..87ffabadb35d4cb1357761ddade7b2f0e7836ca0 100644 (file)
@@ -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>"""))
index af1643b2eb2ac268dae4e7b3b0b3d8f5577669d5..bff107aa7a37d9e2e0f8a43128392c7a0c998754 100644 (file)
@@ -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):