pvl.verkko: refactor into dhcp -> hosts -> web+db modules, reworking index page
authorTero Marttila <terom@paivola.fi>
Sat, 26 Jan 2013 17:52:40 +0200
changeset 178 f9f5e669bace
parent 177 b21b2efe1e6c
child 179 706972d09f05
pvl.verkko: refactor into dhcp -> hosts -> web+db modules, reworking index page
bin/pvl.verkko-dhcp
pvl/verkko/__init__.py
pvl/verkko/db.py
pvl/verkko/dhcp.py
pvl/verkko/hosts.py
pvl/verkko/urls.py
pvl/verkko/web.py
pvl/verkko/wsgi.py
pvl/web/application.py
pvl/web/html.py
--- a/bin/pvl.verkko-dhcp	Sat Jan 26 14:40:05 2013 +0200
+++ b/bin/pvl.verkko-dhcp	Sat Jan 26 17:52:40 2013 +0200
@@ -5,7 +5,8 @@
 from pvl import __version__
 import pvl.args
 import pvl.web.args
-import pvl.verkko.wsgi
+import pvl.verkko
+import pvl.verkko.dhcp
 
 import optparse
 import logging; log = logging.getLogger('main')
@@ -49,8 +50,11 @@
     # parse cmdline
     options, args = parse_argv(argv, doc=__doc__)
 
+    # open
+    database = pvl.verkko.Database(options.database_read)
+
     # app
-    application = pvl.web.args.apply(options, pvl.verkko.wsgi.Application, options.database_read)
+    application = pvl.web.args.apply(options, pvl.verkko.dhcp.Application, database)
 
     # wsgi wrapper
     run_simple('0.0.0.0', 8080, application,
--- a/pvl/verkko/__init__.py	Sat Jan 26 14:40:05 2013 +0200
+++ b/pvl/verkko/__init__.py	Sat Jan 26 17:52:40 2013 +0200
@@ -0,0 +1,4 @@
+
+from pvl.verkko import db
+from pvl.verkko.db import Database
+
--- a/pvl/verkko/db.py	Sat Jan 26 14:40:05 2013 +0200
+++ b/pvl/verkko/db.py	Sat Jan 26 17:52:40 2013 +0200
@@ -1,3 +1,7 @@
+"""
+    SQLAlchemy Database tables model for pvl.verkko
+"""
+
 import sqlalchemy
 from sqlalchemy import *
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/verkko/dhcp.py	Sat Jan 26 17:52:40 2013 +0200
@@ -0,0 +1,72 @@
+# encoding: utf-8
+import pvl.web
+import pvl.verkko.web
+
+from pvl.web import html, urls
+from pvl.verkko import hosts
+
+import logging; log = logging.getLogger('pvl.verkko.dhcp')
+
+class Index (pvl.verkko.web.DatabaseHandler) :
+    TITLE = u"Päivölä Verkko"
+
+    CSS = pvl.verkko.web.DatabaseHandler.CSS + (
+            '/static/dhcp/forms.css',
+    )
+
+    def render_link (self, title, **opts) :
+        return html.a(href=self.url(hosts.ListHandler, **opts))(title)
+
+    def render_links (self, attr, titlevalues) :
+        return html.ul(
+                html.li(
+                    self.render_link(title, **{attr: value})
+                ) for title, value in titlevalues
+        )
+
+    def render (self) :
+        return (
+            html.h2("Interval"),
+            self.render_links('seen', (
+                        ("Hour",    '1h'),
+                        ("Day",     '1d'),
+                        #("Month",   '30d'),
+                        #("Year",    '365d'),
+            )),
+            html.h2("State"),
+            self.render_links('state', (
+                        ("Valid",       ('DHCPACK', 'DHCPRELEASE')),
+                        ("Incomplete",  ('DHCPDISCOVER', 'DHCPOFFER', 'DHCPREQUEST')),
+                        ("Invalid",     ('DHCPNAK', )),
+            )),
+
+            html.h2("IP/MAC"),
+            html.form(action=self.url(hosts.ListHandler), method='get')(
+                html.fieldset(
+                    html.ul(
+                        html.li(
+                            html.label(for_='ip')("IP"),
+                            html.input(type='text', name='ip'),
+                        ),
+
+                        html.li(
+                            html.label(for_='mac')("MAC"),
+                            html.input(type='text', name='mac'),
+                        ),
+
+                        html.li(
+                            html.input(type='submit', value="Search"),
+                        ),
+                    )
+                )
+            ),
+        )
+
+class Application (pvl.verkko.web.Application) :
+    URLS = urls.Map((
+        urls.rule('/',                       Index),
+        urls.rule('/hosts/',                 hosts.ListHandler),
+        urls.rule('/hosts/<int:id>',         hosts.ItemHandler),
+        urls.rule('/hosts/realtime',         hosts.RealtimeHandler),
+    ))
+
--- a/pvl/verkko/hosts.py	Sat Jan 26 14:40:05 2013 +0200
+++ b/pvl/verkko/hosts.py	Sat Jan 26 17:52:40 2013 +0200
@@ -1,4 +1,4 @@
-from pvl.verkko import db, web
+from pvl.verkko import web, db
 from pvl.verkko.utils import parse_timedelta, IPv4Network
 
 from pvl.web import html
@@ -10,7 +10,17 @@
 
 import logging; log = logging.getLogger('pvl.verkko.hosts')
 
-# XXX: this should actually be DHCPHost
+## Model
+import json
+import time
+
+def dt2ts (dt) :
+    return int(time.mktime(dt.timetuple()))
+
+def ts2dt (ts) :
+    return datetime.datetime.fromtimestamp(ts)
+
+# TODO: this should be DHCPHost
 class Host (object) :
     DATE_FMT = '%Y%m%d'
     TIME_FMT = '%H:%M:%S'
@@ -129,10 +139,19 @@
     #state   = db.dhcp_hosts.c.,
 ))
 
+## Controller 
+class BaseHandler (web.DatabaseHandler) :
+    """
+        Common controller stuff for DHCP hosts
+    """
 
-   
- 
-class BaseHandler (web.Handler) :
+    CSS = (
+        "/static/dhcp/hosts.css", 
+    )
+    JS = (
+        #"/static/jquery/jquery.js"
+    )
+
     HOST_ATTRS = {
         'id':       Host.id,
         'net':      Host.gw,
@@ -408,6 +427,10 @@
             )
 
 class ItemHandler (BaseHandler) :
+    """
+        A specific DHCP host, along with a list of related hosts.
+    """
+
     def process (self, id) :
         self.hosts = self.query()
         self.host = self.hosts.get(id)
@@ -456,29 +479,13 @@
 
 
 class ListHandler (BaseHandler) :
+    """
+        List of DHCP hosts for given filter.
+    """
+
     # pagination
     PAGE = 10
 
-    # views
-    VIEWS = (
-        ("Last hour",   dict(seen='1h')),
-        ("Last day",    dict(seen='24h')),
-        ("All",         dict()),
-    ) + tuple(
-        ("Network " + network,          dict(ip=network)) for network in (
-            '194.197.235.0/24',
-            '10.1.0.0/16',
-            '10.4.0.0/16',
-            '10.5.0.0/16',
-            '10.6.0.0/16',
-            '10.10.0.0/16',
-        )
-    ) + (
-        ("Valid",       dict(state=('DHCPACK', 'DHCPRELEASE'))),
-        ("Incomplete",  dict(state=('DHCPDISCOVER', 'DHCPOFFER', 'DHCPREQUEST'))),
-        ("Invalid",     dict(state=('DHCPNAK', ))),
-    )
-
     def process (self) :
         hosts = self.query()
 
@@ -511,18 +518,9 @@
             html.a(href=self.url())(html('&laquo;'), 'Back') if self.filters else None,
         )
 
-import json
-import time
-
-def dt2ts (dt) :
-    return int(time.mktime(dt.timetuple()))
-
-def ts2dt (ts) :
-    return datetime.datetime.fromtimestamp(ts)
-
 class RealtimeHandler (BaseHandler) :
-    TITLE = "Pseudo-Realtime hosts.."
-    CSS = web.Handler.CSS + (
+    TITLE = "DHCP Pseudo-Realtime hosts.."
+    CSS = BaseHandler.CSS + (
         'http://code.jquery.com/ui/1.9.0/themes/base/jquery-ui.css',
     )
     JS = (
--- a/pvl/verkko/urls.py	Sat Jan 26 14:40:05 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-from werkzeug.routing import Map, Rule
-
-def rule (string, endpoint, **opts) :
-    return Rule(string, endpoint=endpoint, **opts)
-
-# URL -> Handler
-from pvl.verkko import hosts
-
-# index page here :)
-from pvl.verkko import web
-
-class Index (web.Handler) :
-    def render (self) :
-        html = web.html
-
-        return (
-            html.ul(
-                html.li(
-                    "DHCP Hosts",
-                    html.ul(
-                        html.li(html.a(href=self.url(hosts.ListHandler, **opts))(title)) for title, opts in hosts.ListHandler.VIEWS
-                    )
-                ),
-            )
-        )
-
-urls = Map((
-    rule('/',                       Index),
-    rule('/hosts/',                 hosts.ListHandler),
-    rule('/hosts/<int:id>',         hosts.ItemHandler),
-    rule('/hosts/realtime',         hosts.RealtimeHandler),
-))
-
--- a/pvl/verkko/web.py	Sat Jan 26 14:40:05 2013 +0200
+++ b/pvl/verkko/web.py	Sat Jan 26 17:52:40 2013 +0200
@@ -1,41 +1,20 @@
 # encoding: utf-8
-import pvl.web.application
-
-# view
-from pvl.web.html import tags as html
+import pvl.web
+from pvl.web import html, urls
 
-from werkzeug.wrappers import Response
-from werkzeug.exceptions import (
-        HTTPException, 
-        BadRequest,         # 400
-        NotFound,           # 404
-)
-from werkzeug.utils import redirect
+import logging; log = logging.getLogger('pvl.verkko.web')
 
-class Handler (pvl.web.application.Handler) :
-    CSS = (
-        "/static/dhcp/hosts.css", 
-    )
-    JS = (
-        #"/static/jquery/jquery.js"
-    )
+class DatabaseHandler (pvl.web.Handler) :
+    """
+        Request handler with pvl.verkko.Database session
+    """
 
     def __init__ (self, app, request, urls, params) :
-        super(Handler, self).__init__(app, request, urls, params)
+        super(DatabaseHandler, self).__init__(app, request, urls, params)
 
         # new ORM session per request
         self.db = app.db.session() 
 
-    def title (self) :
-        """
-            Render site/page title as text.
-        """
-        
-        if self.TITLE :
-            return u"Päivölä Verkko :: {title}".format(title=self.TITLE)
-        else :
-            return u"Päivölä Verkko"
-
     def cleanup (self) :
         """
             After request processing.
@@ -43,3 +22,18 @@
         
         # XXX: SQLAlchemy doesn't automatically close these...?
         self.db.close()
+
+class Application (pvl.web.Application) :
+    """
+        Application with pvl.verkko.Database
+    """
+
+    def __init__ (self, db, **opts) :
+        """
+            db      - pvl.verkko.Database
+        """
+
+        super(Application, self).__init__(**opts)
+
+        self.db = db
+
--- a/pvl/verkko/wsgi.py	Sat Jan 26 14:40:05 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-import pvl.web
-
-import logging; log = logging.getLogger('pvl.verkko.wsgi')
-
-from pvl.verkko import urls, db as database
-
-class Application (pvl.web.Application) :
-    URLS  = urls.urls
-
-    def __init__ (self, db, **opts) :
-        """
-            Initialize app with db.
-        """
-
-        super(Application, self).__init__(**opts)
-
-        self.db = database.Database(db)
-
--- a/pvl/web/application.py	Sat Jan 26 14:40:05 2013 +0200
+++ b/pvl/web/application.py	Sat Jan 26 17:52:40 2013 +0200
@@ -1,5 +1,5 @@
 """
-    WSGI Application.
+    WSGI Application with pvl.web.urls-mapped Handlers building pvl.web.html.
 """
 
 import werkzeug
@@ -135,6 +135,9 @@
         )
         body = html(self.render())
 
+        if not title :
+            raise Exception("%s: no page title!" % (self, ))
+
         if self.app.layout :
             return self.app.layout.format(
                 TITLE   = unicode(title),
--- a/pvl/web/html.py	Sat Jan 26 14:40:05 2013 +0200
+++ b/pvl/web/html.py	Sat Jan 26 17:52:40 2013 +0200
@@ -7,6 +7,7 @@
 """
 
 # XXX: needs some refactoring for Text vs Tag now
+# XXX: not all tags work in self-closing form, e.g. empty html.title() breaks badly
 
 import itertools as itertools
 import types as types