pvl.verkko.hosts: multi-value filters
authorTero Marttila <terom@paivola.fi>
Wed, 24 Oct 2012 16:04:41 +0300
changeset 19 4e2e26f4d058
parent 18 2d16489b8782
child 20 790e78bed63e
pvl.verkko.hosts: multi-value filters
pvl/verkko/hosts.py
pvl/verkko/urls.py
--- a/pvl/verkko/hosts.py	Wed Oct 24 16:04:27 2012 +0300
+++ b/pvl/verkko/hosts.py	Wed Oct 24 16:04:41 2012 +0300
@@ -191,6 +191,16 @@
 
             yield html.a(href=url(page=(page + 1)))(html("&raquo; Next"))
 
+        def render_filter (filter) :
+            value = filters.get(filter)
+
+            if value :
+                # XXX: multi-valued filters?
+                value = value[0]
+            else :
+                value = None
+
+            return html.input(type='text', name=filter, value=value)
 
         table = html.table(
             html.caption(title) if title else None,
@@ -209,7 +219,7 @@
                     ),
                     (
                         html.td(class_=class_)(
-                            html.input(type='text', name=filter, value=filters.get(filter)) if filter else None
+                            render_filter(filter) if filter else None
                         ) for title, sort, filter, class_ in COLS
                     )
                 ) if filters is not False else None
@@ -304,75 +314,103 @@
     # 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 filter (self, attr, value) :
+        """
+            Return filter expression for given attr == value
+        """
+
+        if attr == 'seen' :
+            column = Host.last_seen
+
+            if value.isdigit() :
+                # specific date
+                date = datetime.datetime.strptime(value, Host.DATE_FMT).date()
+
+                return db.between(date.strftime(Host.DATE_FMT), 
+                        db.func.strftime(Host.DATE_FMT, Host.first_seen),
+                        db.func.strftime(Host.DATE_FMT, Host.last_seen)
+                )
+            else :
+                # recent
+                timedelta = parse_timedelta(value)
+                
+                return ((db.func.now() - Host.last_seen) < timedelta)
+
+                # XXX: for sqlite, pgsql should handle this natively?
+                # to seconds
+                #timeout = timedelta.days * (24 * 60 * 60) + timedelta.seconds
+                
+                # WHERE strftime('%s', 'now') - strftime('%s', last_seen) < :timeout
+                #filter = (db.func.strftime('%s', 'now') - db.func.strftime('%s', Host.last_seen) < timeout)
+        
+        elif attr == 'ip' :
+            column = Host.ip
+
+            # column is IPv4 string literal format...
+            if '/' in value :
+                return (db.func.inet(Host.ip).op('<<')(db.func.cidr(value)))
+            else :
+                return (db.func.inet(Host.ip) == db.func.inet(value))
+
+        else :
+            # preprocess
+            like = False
+
+            if value.endswith('*') :
+                like = value.replace('*', '%')
+
+            elif attr == 'mac' :
+                value = Host.normalize_mac(value)
+
+            # filter
+            column = self.HOST_ATTRS[attr]
+
+            if like :
+                return (column.like(like))
+            else :
+                return (column == value)
+
     def process (self) :
         hosts = self.query()
 
         # filter?
-        column = None
         self.filters = {}
 
         for attr in self.HOST_ATTRS :
-            value = self.request.args.get(attr)
-
-            if not value :
-                continue
-
-            if attr == 'seen' :
-                column = Host.last_seen
-
-                if value.isdigit() :
-                    # specific date
-                    date = datetime.datetime.strptime(value, Host.DATE_FMT).date()
+            values = self.request.args.getlist(attr)
 
-                    filter = db.between(date.strftime(Host.DATE_FMT), 
-                            db.func.strftime(Host.DATE_FMT, Host.first_seen),
-                            db.func.strftime(Host.DATE_FMT, Host.last_seen)
-                    )
-                else :
-                    # recent
-                    timedelta = parse_timedelta(value)
-                    
-                    filter = ((db.func.now() - Host.last_seen) < timedelta)
-
-                    # XXX: for sqlite, pgsql should handle this natively?
-                    # to seconds
-                    #timeout = timedelta.days * (24 * 60 * 60) + timedelta.seconds
-                    
-                    # WHERE strftime('%s', 'now') - strftime('%s', last_seen) < :timeout
-                    #filter = (db.func.strftime('%s', 'now') - db.func.strftime('%s', Host.last_seen) < timeout)
+            if not values :
+                continue
             
-            elif attr == 'ip' :
-                column = Host.ip
-
-                # column is IPv4 string literal format...
-                if '/' in value :
-                    filter = (db.func.inet(Host.ip).op('<<')(db.func.cidr(value)))
-                else :
-                    filter = (db.func.inet(Host.ip) == db.func.inet(value))
-
-            else :
-                # preprocess
-                like = False
-
-                if value.endswith('*') :
-                    like = value.replace('*', '%')
+            filter = db.or_(*[self.filter(attr, value) for value in values])
 
-                elif attr == 'mac' :
-                    value = Host.normalize_mac(value)
-
-                # filter
-                column = self.HOST_ATTRS[attr]
+            log.debug("filter %s: %s", attr, filter)
 
-                if like :
-                    filter = (column.like(like))
-                else :
-                    filter = (column == value)
-            
             hosts = hosts.filter(filter)
-            self.filters[attr] = value
+            self.filters[attr] = values
 
-        # sort XXX: default per filter column
-        hosts = self.sort(hosts) #, column)
+        # sort XXX: default per filter column?
+        hosts = self.sort(hosts)
         
         # page?
         self.page = self.request.args.get('page')
@@ -386,7 +424,7 @@
 
     def title (self) :
         if self.filters :
-            return "DHCP Hosts: {filters}".format(filters=', '.join(self.filters.itervalues()))
+            return "DHCP Hosts: {filters}".format(filters=', '.join(value for values in self.filters.itervalues() for value in values))
         else :
             return "DHCP Hosts"
     
--- a/pvl/verkko/urls.py	Wed Oct 24 16:04:27 2012 +0300
+++ b/pvl/verkko/urls.py	Wed Oct 24 16:04:41 2012 +0300
@@ -14,8 +14,12 @@
 
         return (
             html.ul(
-                # TODO: self.url
-                html.a(href=self.url(hosts.ListHandler))("DHCP Hosts"),
+                html.li(
+                    "DHCP Hosts",
+                    html.ul(
+                        html.li(html.a(href=self.url(hosts.ListHandler, **opts))(title)) for title, opts in hosts.ListHandler.VIEWS
+                    )
+                ),
             )
         )