fix html/templates to use a Container type (inherited by Tag) for flat lists of tags
authorTero Marttila <terom@fixme.fi>
Wed, 10 Jun 2009 21:59:49 +0300
changeset 78 d580323b4bfa
parent 77 2a53c5ade434
child 79 e5400304a3d3
fix html/templates to use a Container type (inherited by Tag) for flat lists of tags
degal/html.py
degal/templates.py
--- a/degal/html.py	Fri Jun 05 23:59:14 2009 +0300
+++ b/degal/html.py	Wed Jun 10 21:59:49 2009 +0300
@@ -78,7 +78,70 @@
     # default .render method
     render = render_unicode
 
-class Tag (IRenderable) :
+class Container (IRenderable) :
+    """
+        A container holds a sequence of other renderable items.
+    """
+
+    @classmethod
+    def process_contents (cls, contents) :
+        """
+            Postprocess contents iterable to return new list.
+
+            Items that are None will be omitted from the return value.
+
+            >>> list(Container.process_contents([]))
+            []
+            >>> list(Container.process_contents([None]))
+            []
+            >>> list(Container.process_contents([u'foo']))
+            [u'foo']
+        """
+        
+        for content in contents :
+            if content is None :
+                continue
+                        
+            else :
+                # normal, handle as unicode data
+                yield content
+
+    def __init__ (self, *contents) :
+        """
+            Construct this container with the given sub-items
+        """
+        
+        # store postprocessed
+        self.contents = list(self.process_contents(contents))
+    
+    def render_raw_lines (self, **render_opts) :
+        """
+            Render our contents as a series of non-indented lines, with the contents handling indentation themselves.
+
+            >>> list(Container(5).render_raw_lines())
+            [u'5']
+            >>> list(Container('line1', 'line2').render_raw_lines())
+            [u'line1', u'line2']
+            >>> list(Container('a', Tag('b', 'bb'), 'c').render_raw_lines())
+            [u'a', u'<b>', u'\\tbb', u'</b>', u'c']
+            >>> list(Container(Tag('hr'), Tag('foo')('bar')).render_raw_lines())
+            [u'<hr />', u'<foo>', u'\\tbar', u'</foo>']
+        """
+
+        for content in self.contents :
+            if isinstance(content, IRenderable) :
+                # sub-items
+                for line in content.render_raw_lines(**render_opts) :
+                    yield line
+            
+            else :
+                # escape raw values
+                yield escape(unicode(content))
+
+    def __repr__ (self) :
+        return 'Container(%s)' % ', '.join(repr(c) for c in self.contents)
+
+class Tag (Container) :
     """
         A HTML tag, with attributes and contents, which can a mixture of data and other renderables(tags).
 
@@ -86,25 +149,6 @@
     """
 
     @staticmethod
-    def process_contents (contents) :
-        """
-            Postprocess contents iterable to return new list.
-
-            Items that are None will be omitted from the return value.
-
-            XXX: flatten
-
-            >>> Tag.process_contents([])
-            []
-            >>> Tag.process_contents([None])
-            []
-            >>> Tag.process_contents([u'foo'])
-            [u'foo']
-        """
-
-        return [c for c in contents if c is not None]
-
-    @staticmethod
     def process_attrs (attrs) :
         """
             Postprocess attributes.
@@ -113,15 +157,15 @@
 
             TODO: only remove one underscore
 
-            >>> Tag.process_attrs(dict())
+            >>> dict(Tag.process_attrs(dict()))
             {}
-            >>> Tag.process_attrs(dict(foo='bar'))
+            >>> dict(Tag.process_attrs(dict(foo='bar')))
             {'foo': 'bar'}
-            >>> Tag.process_attrs(dict(class_='bar', frob=None))
+            >>> dict(Tag.process_attrs(dict(class_='bar', frob=None)))
             {'class': 'bar'}
         """
 
-        return dict((k.rstrip('_'), v) for k, v in attrs.iteritems() if v is not None)
+        return ((k.rstrip('_'), v) for k, v in attrs.iteritems() if v is not None)
 
     def __init__ (self, name, *contents, **attrs) :
         """
@@ -138,12 +182,13 @@
             >>> Tag('foo', class_='ten')
             Tag('foo', class='ten')
         """
-
+        
+        # store contents as container
+        super(Tag, self).__init__(*contents)
+        
+        # store postprocessed stuff
         self.name = name
-
-        # store filtered/processed versions
-        self.contents = self.process_contents(contents)
-        self.attrs = self.process_attrs(attrs)
+        self.attrs = dict(self.process_attrs(attrs))
 
     def __call__ (self, *contents, **attrs) :
         """
@@ -196,28 +241,6 @@
 
         return " ".join(self.format_attr(n, v) for n, v in self.attrs.iteritems())
 
-    def render_contents (self, **render_opts) :
-        """
-            Render the contents of the tag as a series of indented lines, with given render_lines options for subtags
-
-            >>> list(Tag('x', 5).render_contents())
-            [u'5']
-            >>> list(Tag('x', 'line1', 'line2').render_contents())
-            [u'line1', u'line2']
-            >>> list(Tag('x', 'a', Tag('b', 'bb'), 'c').render_contents())
-            [u'a', u'<b>', u'\\tbb', u'</b>', u'c']
-        """
-
-        for content in self.contents :
-            if isinstance(content, IRenderable) :
-                # sub-tags
-                for line in content.render_raw_lines(**render_opts) :
-                    yield line
-            
-            else :
-                # escape raw values
-                yield escape(unicode(content))
-
     def render_raw_lines (self, indent=u'\t') :
         """
             Render the tag and indented content
@@ -230,10 +253,11 @@
         attrs_stuff = (" " + self.render_attrs()) if self.attrs else ""
 
         if self.contents :
-            # tag with content
+            # wrapping tags
             yield u"<%s%s>" % (self.name, attrs_stuff)
-
-            for line in self.render_contents(indent=indent) :
+            
+            # subcontents
+            for line in super(Tag, self).render_raw_lines(indent=indent) :
                 yield indent + line
 
             yield u"</%s>" % (self.name, )
@@ -271,6 +295,10 @@
 
 class XHTMLDocument (IRenderable) :
     """
+        A full XHTML document with XML header, doctype, head and body.
+
+        XXX: current rendering is a bit of a kludge
+
         <?xml version="..." encoding="..." ?>
         <!DOCTYPE ...>
         
@@ -350,6 +378,7 @@
         return Tag(name)
 
 # pretty names
+container = Container
 tag = Tag
 tags = TagFactory()
 raw = Text
--- a/degal/templates.py	Fri Jun 05 23:59:14 2009 +0300
+++ b/degal/templates.py	Wed Jun 10 21:59:49 2009 +0300
@@ -5,21 +5,21 @@
 import html
 from html import tags
 
-def link_from (from_page, to_page) :
+def link_from (source, target) :
     """
         Returns a partial a tag linking from the given page to the given page
 
         XXX: URLEncode unicode -> str!
     """
 
-    return tags.a(href=to_page.path_from(from_page))
+    return tags.a(href=source.path_to(target))
 
-def image_link (from_page, image, image_html) :
+def image_link (from_page, image, target) :
     """
         Link to the given image
     """
 
-    return link_from(from_page, image_html)(
+    return link_from(from_page, target)(
         tags.img(src=image.path_from(from_page))
     )
 
@@ -28,7 +28,7 @@
         The per-image view
     """
 
-    return [
+    return html.container(
         tags.div(id_='image')(
             # title
             tags.h1(image.title) if image.title else None,
@@ -50,10 +50,10 @@
         ),
 
         # extended info, metadata
-        tags.div(id_='info')(
-            tags.p("%s: " % name, value) for name, value in image.metadata
-        ),
-    ]
+        tags.div(id_='info')(*(
+            tags.p(("%s: " % name), value) for name, value in image.metadata.iteritems()
+        )),
+    )
 
 def folder_link (from_page, folder, page=0) :
     """
@@ -82,7 +82,7 @@
                     if cur_page > 0 else
                 tags.li(tags.span(html.raw("&laquo; Prev"))),
                 
-                ((
+                html.container((
                     # page link
                     tags.li(folder_page_link(folder, page)(page + 1))
                         if page != cur_page else
@@ -104,7 +104,7 @@
     # render the paginate-view once
     paginate = folder_paginate(folder, cur_page)
 
-    return [
+    return html.container(
         # title
         tags.h1(folder.title) if folder.title else None,
 
@@ -121,9 +121,9 @@
         paginate,
 
         # image thumbnails
-        ((
+        html.container(*((
             image_link(folder, image, image.html)
-        ) for image in folder.images),
+        ) for image in folder.images)),
 
         # lower paginate
         paginate,
@@ -132,7 +132,7 @@
         tags.p(id='description')(folder.description) if folder.description else None,
 
         # shorturl
-    ]
+    )
 
 def breadcrumb_trail (gallery, page) :
     """
@@ -150,7 +150,8 @@
             yield html.raw("&raquo;")
 
         # link from this page to sub-page
-        yield link_from(page, segment)(segment.title)
+        # XXX: title
+        yield link_from(page, segment)(segment.name.title())
 
 
 def breadcrumb (gallery, page) :
@@ -159,7 +160,7 @@
     """
 
     return tags.div(id='breadcrumb')(
-        breadcrumb_trail(gallery, page)
+        *breadcrumb_trail(gallery, page)
     )
 
 def master (gallery, title, page, body) :
@@ -168,14 +169,14 @@
     """
 
     return html.XHTMLDocument(
-        head=[
+        head=html.container(
             tags.title(title),
             
             # stylesheet
             tags.link(rel='Stylesheet', type='text/css', href=gallery.stylesheet.path_from(page))
-        ],
+        ),
 
-        body=[
+        body=html.container(
             # top-of-page breadcrumb nav
             breadcrumb(gallery, page),
             
@@ -184,6 +185,6 @@
             
             # footer
             tags.p(id='about')(tags.a(href='http://projects.qmsk.net/degal')('Degal'), gallery.version)
-        ],
+        ),
     )