controllers: tidy up PageHandler a little
authorTero Marttila <terom@fixme.fi>
Thu, 20 Jan 2011 23:14:07 +0200
changeset 58 4f4150296cd3
parent 57 7a48e9d96ec8
child 59 de6abcbd3c03
controllers: tidy up PageHandler a little
static/layout.css
svv/controllers.py
svv/html.py
svv/utils.py
--- a/static/layout.css	Tue Jan 11 01:12:50 2011 +0200
+++ b/static/layout.css	Thu Jan 20 23:14:07 2011 +0200
@@ -21,6 +21,12 @@
     border-bottom: 1px dotted #aaa;
 }
 
+/* Header links are bare */
+div#header a
+{
+    text-decoration: none;
+}
+
 /* 
  * Container for horizontal layout 
  * XXX: for equal-height columns, I believe; see #menu
@@ -117,6 +123,9 @@
 /* Full-width Footer at bottom of page */
 div#footer
 {
+    /* Don't flow with #menu or #content */
+    clear: both;
+
     padding: 10px;
 
     border-top: 1px dotted #aaa;
@@ -126,3 +135,8 @@
 
     text-align: center;
 }
+
+div#footer .right
+{
+    float: right;
+}
--- a/svv/controllers.py	Tue Jan 11 01:12:50 2011 +0200
+++ b/svv/controllers.py	Thu Jan 20 23:14:07 2011 +0200
@@ -8,8 +8,10 @@
 import werkzeug
 from werkzeug import Response
 
-from svv import html, pdf
-from svv.html import tags
+from svv.html import tags as html
+from svv import pdf, utils
+
+import datetime
 
 class AppHandler (object):
     """
@@ -92,6 +94,93 @@
 
         Renders the layout template and HTML.
     """
+    
+    # XXX: how to handle site title vs page title?
+    def format_title (self) :
+        """
+            Render site/page title as plain text.
+        """
+
+        return u"SpeksiVVuokraus"
+    
+    def build_breadcrumb (self) :
+        """
+            Return an optional list of (title, target, args) segments leading up to and including this page.
+        """
+
+        return None
+
+    def render_header (self) :
+        """
+            Page header.
+        """
+        
+        from svv import urls
+        
+        # link to main page
+        return html.a(href=self.url_for(urls.Index))(
+            self.format_title()
+        )
+
+    def render_menu (self) :
+        """
+            Site navigation menu (vertical menu next to content)
+        """
+
+        # XXX: circular-import hack for urls
+        #      it's not sustainable for a base class to referr to its subclasses at define time
+        from svv import urls
+
+        # XXX: menu def, should be somewhere elsewhere..
+        menu = [
+            ("Kalenteri",       urls.CalendarView),
+            ("Uusi tilaus",     urls.NewOrderView),
+            ("Tilaukset",       urls.OrdersView),
+            ("Tilaajat",        urls.CustomersView),
+            ("Inventaari",      urls.InventoryView),
+        ]
+
+        # render
+        return html.ul(
+            html.li(
+                html.a(href=self.url_for(target))(title)
+
+            ) for title, target in menu
+        )
+   
+    def render_navigation (self) :
+        """
+            Page navigation menu (compact horizontal above content), used for breadcrumb
+        """
+
+        breadcrumb = self.build_breadcrumb()
+
+        if not breadcrumb :
+            # optional
+            return None
+        
+        return html.ul(
+            html.li(
+                html.a(href=self.url_for(target, **args))(
+                    title
+                )
+            ) for title, target, args in breadcrumb
+        )
+
+    def render_footer (self) :
+        """
+            Page footer.
+        """
+
+        # current tz-aware time
+        now = datetime.datetime.now(utils.LocalTimezone())
+        
+        return (
+            html.div(class_='right')(
+                # timestamp with timezone offset
+                now.strftime('%Y/%m/%d %H:%M:%S %Z (GMT%z)')
+            )
+        )
 
     def render_content (self, **args) :
         """
@@ -108,7 +197,7 @@
         """
 
         # XXX: layout template, should be somewhere far away
-        title = "Index"
+
         css = [
                 "/static/layout.css", 
                 "/static/style.css", 
@@ -133,41 +222,49 @@
                 #"/static/js/treelist.js"
         ]
 
-        head = (
-            tags.title(title),
-            (tags.link(rel='Stylesheet', type="text/css", href=src) for src in css),
-            # XXX: script can't self-close >_<
-            (tags.script(src=src)(" ") for src in scripts),
+        return html.document( 
+            head=(
+                html.title(
+                    # plaintext title
+                    self.format_title()
+                ),
+
+                (
+                    html.link(rel='Stylesheet', type="text/css", href=src) for src in css
+                ),
+
+                # XXX: script can't self-close?
+                (
+                    html.script(src=src, _selfclosing=False) for src in scripts
+                ),
+            ),
+
+            body=(
+                html.div(id='header')(
+                    self.render_header()
+                ),
+
+                html.div(id='container')(
+                    html.div(id='menu')(
+                        self.render_menu()
+                    ),
+                    
+                    # XXX: breadcrumb etc.
+                    # html.div(id='nav')(
+                    #    self.render_navgation()
+                    #),
+                    
+                    # the actual page content (pre-rendered)
+                    html.div(id='content')(
+                        content
+                    ),
+                ),
+                html.div(id='footer')(
+                    self.render_footer()
+                ),
+            ),
         )
 
-        # XXX: silly circular-import hack for urls
-        #      it's not sustainable for a base class to referr to its subclasses at define time
-        from svv import urls
-
-        header = ("Foo List")
-        nav = [tags.ul(tags.li(tags.a(href='#')(name)) for name in ['Nav A', 'Nav B', 'Nav C'])]
-        menu = [tags.ul(tags.li(tags.a(href=url)(name)) for name, url in [
-            ("Kalenteri",   self.url_for(urls.CalendarView)),
-            ("Uusi tilaus", self.url_for(urls.NewOrderView)),
-            ("Tilaukset",   self.url_for(urls.OrdersView)),
-            ("Tilaajat",    self.url_for(urls.CustomersView)),
-            ("Inventaari",  self.url_for(urls.InventoryView)),
-        ])]
-        footer = ("Copyright?")
-
-        layout = (
-            tags.div(id='header')(header),
-            tags.div(id='container')(
-                tags.div(id='menu')(menu),
-                # tags.div(id='nav')(nav),
-                tags.div(id='content')(content),
-            ),
-            tags.div(id='footer')(footer),
-        )
-        
-        # perform the actual rendering (run generators etc.)
-        return unicode(html.document(head, layout))
-        
     def render (self, **url_values) :
         """
             Render full page HTML
@@ -194,11 +291,11 @@
         response = super(PageHandler, self).respond(**url_values)
 
         if not response :
-            # render page HTML
-            html = self.render(**url_values)
+            # render page HTML as unicode
+            html = unicode(self.render(**url_values))
         
             # response object
-            # XXX: unicode?
+            # XXX: charset?
             return Response(html, mimetype='text/html')
 
         # ok
@@ -272,30 +369,30 @@
         data = self.DATA
 
         def render_item (id, text, children) :
-            return tags.li(
-                tags.div(class_='item')(
+            return html.li(
+                html.div(class_='item')(
                     # item text
-                    tags.a(tags.input(type='checkbox', name_=id, checked=(
+                    html.a(html.input(type='checkbox', name_=id, checked=(
                         'checked' if self.request.form.get(str(id)) else None
                     )), text, class_='text'),
                 ),
 
                 # children
-                tags.ul(render_item(*item) for item in children if item) if children else None,
+                html.ul(render_item(*item) for item in children if item) if children else None,
 
                 # 
                 class_=('more open' if children else None),
             )
 
         # XXX: nothing much to see here
-        return tags.h1("Mui.")
+        return html.h1("Mui.")
 
         # render it
         return (
-            tags.h3("Item list"),
-            tags.form(action='.', method='post')(
-                tags.ul(class_='treelist')(render_item(*item) for item in data),
-                tags.input(type='submit'),
+            html.h3("Item list"),
+            html.form(action='.', method='post')(
+                html.ul(class_='treelist')(render_item(*item) for item in data),
+                html.input(type='submit'),
             ),
         )
 
--- a/svv/html.py	Tue Jan 11 01:12:50 2011 +0200
+++ b/svv/html.py	Thu Jan 20 23:14:07 2011 +0200
@@ -402,6 +402,9 @@
         >>> str(TagFactory().raw("><")
         '><'
     """
+
+    # full XHTML document
+    document = Document
     
     # raw HTML
     raw = Text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/svv/utils.py	Thu Jan 20 23:14:07 2011 +0200
@@ -0,0 +1,55 @@
+"""
+    Miscellanous bucket of everything
+"""
+
+import datetime
+import time
+
+class LocalTimezone (datetime.tzinfo) :
+    """
+        Our platform's (the 'time' module) idea of our local timezone.
+    """
+
+    ZERO = datetime.timedelta(0)
+
+    STDOFFSET = datetime.timedelta(seconds = -time.timezone)
+
+    if time.daylight:
+        DSTOFFSET = datetime.timedelta(seconds = -time.altzone)
+    else:
+        DSTOFFSET = STDOFFSET
+
+    DSTDIFF = DSTOFFSET - STDOFFSET
+
+    def _isdst (self, dt) :
+        # time tuple in local time
+        tt = (dt.year, dt.month, dt.day,
+              dt.hour, dt.minute, dt.second,
+              dt.weekday(), 0, -1)
+        
+        # to UTC timestamp
+        stamp = time.mktime(tt)
+        
+        # roundtrip..
+        tt = time.localtime(stamp)
+        
+        # to figure out DST flag
+        return tt.tm_isdst > 0
+
+    def utcoffset (self, dt) :
+        if self._isdst(dt) :
+            return self.DSTOFFSET
+        else:
+            return self.STDOFFSET
+
+    def dst (self, dt) :
+        if self._isdst(dt) :
+            return self.DSTDIFF
+        else:
+            return self.ZERO
+
+    def tzname (self, dt) :
+        # varies by DST
+        return time.tzname[self._isdst(dt)]
+
+