fixed hazelnut checkbox management
authorJordan Augé <jordan.auge@lip6.fr>
Wed, 10 Jul 2013 14:01:54 +0000 (16:01 +0200)
committerJordan Augé <jordan.auge@lip6.fr>
Wed, 10 Jul 2013 14:01:54 +0000 (16:01 +0200)
manifold/core/query.py
manifold/metadata.py
plugins/hazelnut/hazelnut.js
plugins/hazelnut/hazelnut.py
trash/sliceview.py
unfold/js/unfold-helper.js

index 4aba14b..e45844c 100644 (file)
@@ -90,13 +90,13 @@ class Query(object):
                 self.filters = kwargs["filters"]
                 del kwargs["filters"]
             else:
-                self.filters = Filter([])
+                self.filters = Filter()
 
             if "fields" in kwargs:
                 self.fields = set(kwargs["fields"])
                 del kwargs["fields"]
             else:
-                self.fields = set([])
+                self.fields = set()
 
             # "update table set x = 3" => params == set
             if "params" in kwargs:
@@ -116,14 +116,14 @@ class Query(object):
         #else:
         #        raise ParameterError, "No valid constructor found for %s : args = %r" % (self.__class__.__name__, args)
 
-        if not self.filters: self.filters = Filter([])
+        if not self.filters: self.filters = Filter()
         if not self.params:  self.params  = {}
-        if not self.fields:  self.fields  = set([])
+        if not self.fields:  self.fields  = set()
         if not self.timestamp:      self.timestamp      = "now" 
 
         if isinstance(self.filters, list):
             f = self.filters
-            self.filters = Filter([])
+            self.filters = Filter()
             for x in f:
                 pred = Predicate(x)
                 self.filters.add(pred)
@@ -145,28 +145,41 @@ class Query(object):
     def clear(self):
         self.action = 'get'
         self.object = None
-        self.filters = Filter([])
+        self.filters = Filter()
         self.params  = {}
-        self.fields  = set([])
+        self.fields  = set()
         self.timestamp  = "now" 
         self.timestamp  = 'now' # ignored for now
 
+
+    def to_sql(self, platform='', multiline=False):
+        get_params_str = lambda : ', '.join(['%s = %r' % (k, v) for k, v in self.get_params().items()])
+        get_select_str = lambda : ', '.join(self.get_select()) 
+
+        table  = self.get_from()
+        select = 'SELECT %s' % (get_select_str()    if self.get_select()    else '*')
+        where  = 'WHERE %s'  % self.get_where()     if self.get_where()     else ''
+        at     = 'AT %s '    % self.get_timestamp() if self.get_timestamp() else ''
+        params = 'SET %s'    % get_params_str()     if self.get_params()    else ''
+
+        sep = ' ' if not multiline else '\n  '
+        if platform: platform = "%s:" % platform
+        strmap = {
+            'get'   : '%(select)s%(sep)s%(at)sFROM %(platform)s%(table)s%(sep)s%(where)s%(sep)s',                                           
+            'update': 'UPDATE %(platform)s%(table)s%(sep)s%(params)s%(sep)s%(where)s%(sep)s%(select)s',       
+            'create': 'INSERT INTO %(platform)s%(table)s%(sep)s%(params)s%(sep)s%(select)s',
+            'delete': 'DELETE FROM %(platform)s%(table)s%(sep)s%(where)s'
+        }
+
+        return strmap[self.action] % locals()
+
     @returns(StringTypes)
     def __str__(self):
-        return "SELECT %(select)s%(from)s%(where)s%(at)s" % {
-            "select": ", ".join(self.get_select())          if self.get_select()    else "*",
-            "from"  : "\n  FROM  %s" % self.get_from(),
-            "where" : "\n  WHERE %s" % self.get_where()     if self.get_where()     else "",
-            "at"    : "\n  AT    %s" % self.get_timestamp() if self.get_timestamp() else ""
-        }
+        return self.to_sql(multiline=True)
 
     @returns(StringTypes)
     def __repr__(self):
-        return "SELECT %s FROM %s WHERE %s" % (
-            ", ".join(self.get_select()) if self.get_select() else '*',
-            self.get_from(),
-            self.get_where()
-        )
+        return self.to_sql()
 
     def __key(self):
         return (self.action, self.object, self.filters, frozendict(self.params), frozenset(self.fields))
@@ -268,34 +281,101 @@ class Query(object):
     #--------------------------------------------------------------------------- 
 
     @classmethod
+    #@returns(Query)
     def action(self, action, object):
+        """
+        (Internal usage). Craft a Query according to an action name 
+        See methods: get, update, delete, execute.
+        Args:
+            action: A String among {"get", "update", "delete", "execute"}
+            object: The name of the queried object (String)
+        Returns:
+            The corresponding Query instance
+        """
         query = Query()
         query.action = action
         query.object = object
         return query
 
     @classmethod
-    def get(self, object): return self.action('get', object)
+    #@returns(Query)
+    def get(self, object):
+        """
+        Craft the Query which fetches the records related to a given object
+        Args:
+            object: The name of the queried object (String)
+        Returns:
+            The corresponding Query instance
+        """
+        return self.action("get", object)
 
     @classmethod
-    def update(self, object): return self.action('update', object)
+    #@returns(Query)
+    def update(self, object):
+        """
+        Craft the Query which updates the records related to a given object
+        Args:
+            object: The name of the queried object (String)
+        Returns:
+            The corresponding Query instance
+        """
+        return self.action("update", object)
     
     @classmethod
-    def create(self, object): return self.action('create', object)
+    #@returns(Query)
+    def create(self, object):
+        """
+        Craft the Query which create the records related to a given object
+        Args:
+            object: The name of the queried object (String)
+        Returns:
+            The corresponding Query instance
+        """
+        return self.action("create", object)
     
     @classmethod
-    def delete(self, object): return self.action('delete', object)
+    #@returns(Query)
+    def delete(self, object):
+        """
+        Craft the Query which delete the records related to a given object
+        Args:
+            object: The name of the queried object (String)
+        Returns:
+            The corresponding Query instance
+        """
+        return self.action("delete", object)
     
     @classmethod
-    def execute(self, object): return self.action('execute', object)
-
+    #@returns(Query)
+    def execute(self, object):
+        """
+        Craft the Query which execute a processing related to a given object
+        Args:
+            object: The name of the queried object (String)
+        Returns:
+            The corresponding Query instance
+        """
+        return self.action("execute", object)
+
+    #@returns(Query)
     def at(self, timestamp):
+        """
+        Set the timestamp carried by the query
+        Args:
+            timestamp: The timestamp (it may be a python timestamp, a string
+                respecting the "%Y-%m-%d %H:%M:%S" python format, or "now")
+        Returns:
+            The self Query instance
+        """
         self.timestamp = timestamp
         return self
 
     def filter_by(self, *args):
         if len(args) == 1:
             filters = args[0]
+            if filters == None:
+                self.filters = Filter()
+                return self
             if not isinstance(filters, (set, list, tuple, Filter)):
                 filters = [filters]
             for predicate in filters:
@@ -307,13 +387,18 @@ class Query(object):
             raise Exception, 'Invalid expression for filter'
         return self
             
-    def select(self, fields=None):
+    def select(self, *fields):
         if not fields:
             # Delete all fields
             self.fields = set()
             return self
-        if not isinstance(fields, (set, list, tuple)):
-            fields = [fields]
+
+        # Accept passing iterables
+        if len(fields) == 1:
+            tmp, = fields
+            if isinstance(tmp, (list, tuple, set, frozenset)):
+                fields = tuple(tmp)
+
         for field in fields:
             self.fields.add(field)
         return self
@@ -322,6 +407,31 @@ class Query(object):
         self.params.update(params)
         return self
 
+    def __or__(self, query):
+        assert self.action == query.action
+        assert self.object == query.object
+        assert self.timestamp == query.timestamp # XXX
+        filter = self.filters | query.filters
+        # fast dict union
+        # http://my.safaribooksonline.com/book/programming/python/0596007973/python-shortcuts/pythoncook2-chp-4-sect-17
+        params = dict(self.params, **query.params)
+        fields = self.fields | query.fields
+        return Query.action(self.action, self.object).filter_by(filter).select(fields)
+
+    def __and__(self, query):
+        assert self.action == query.action
+        assert self.object == query.object
+        assert self.timestamp == query.timestamp # XXX
+        filter = self.filters & query.filters
+        # fast dict intersection
+        # http://my.safaribooksonline.com/book/programming/python/0596007973/python-shortcuts/pythoncook2-chp-4-sect-17
+        params =  dict.fromkeys([x for x in self.params if x in query.params])
+        fields = self.fields & query.fields
+        return Query.action(self.action, self.object).filter_by(filter).select(fields)
+
+    def __le__(self, query):
+        return ( self == self & query ) or ( query == self | query )
+
 class AnalyzedQuery(Query):
 
     # XXX we might need to propagate special parameters sur as DEBUG, etc.
@@ -361,12 +471,9 @@ class AnalyzedQuery(Query):
         if not method in self._subqueries:
             analyzed_query = AnalyzedQuery(metadata=self.metadata)
             analyzed_query.action = self.action
-            if self.metadata:
-                try:
-                    type = self.metadata.get_field_type(self.object, method)
-                except ValueError ,e: # backwards 1..N
-                    type = method
-            else:
+            try:
+                type = self.metadata.get_field_type(self.object, method)
+            except ValueError ,e: # backwards 1..N
                 type = method
             analyzed_query.object = type
             self._subqueries[method] = analyzed_query
@@ -381,30 +488,39 @@ class AnalyzedQuery(Query):
     def get_subquery_names(self):
         return set(self._subqueries.keys())
 
+    def get_subqueries(self):
+        return self._subqueries
+
     def subqueries(self):
         for method, subquery in self._subqueries.iteritems():
             yield (method, subquery)
 
     def filter_by(self, filters):
-        if not filters: return self
         if not isinstance(filters, (set, list, tuple, Filter)):
             filters = [filters]
         for predicate in filters:
-            if '.' in predicate.key:
-                method, subkey = pred.key.split('.', 1)
+            if predicate and '.' in predicate.key:
+                method, subkey = predicate.key.split('.', 1)
                 # Method contains the name of the subquery, we need the type
                 # XXX type = self.metadata.get_field_type(self.object, method)
-                sub_pred = Predicate(subkey, pred.op, pred.value)
+                sub_pred = Predicate(subkey, predicate.op, predicate.value)
                 self.subquery(method).filter_by(sub_pred)
             else:
                 super(AnalyzedQuery, self).filter_by(predicate)
         return self
 
-    def select(self, fields):
-        if not isinstance(fields, (set, list, tuple)):
-            fields = [fields]
+    def select(self, *fields):
+
+        # XXX passing None should reset fields in all subqueries
+
+        # Accept passing iterables
+        if len(fields) == 1:
+            tmp, = fields
+            if isinstance(tmp, (list, tuple, set, frozenset)):
+                fields = tuple(tmp)
+
         for field in fields:
-            if '.' in field:
+            if field and '.' in field:
                 method, subfield = field.split('.', 1)
                 # Method contains the name of the subquery, we need the type
                 # XXX type = self.metadata.get_field_type(self.object, method)
index 1b0e270..41a16f3 100644 (file)
@@ -62,3 +62,7 @@ class MetaData:
 
     def sorted_fields_by_object (self, object):
         return self.hash_by_object[object]['column'].sort()
+
+    def get_field_type(self, object, field):
+        print "Temp fix for metadata::get_field_type() -> consider moving to manifold.core.metadata soon"
+        return field
index 84be66e..d1a470f 100644 (file)
@@ -9,6 +9,10 @@
  * Expression) that maps it to the dollar sign so it can't be overwritten by
  * another library in the scope of its execution.
  */
+
+// TEMP
+var ELEMENT_KEY = 'resource_hrn';
+
 (function($){
 
     var debug=false;
@@ -58,6 +62,7 @@
                 // This is the new plugin API meant to replace the weird publish_subscribe mechanism
                 $this.set_query_handler(options.query_uuid, hazelnut.query_handler);
                 $this.set_record_handler(options.query_uuid, hazelnut.record_handler); 
+                $this.set_record_handler(options.query_all_uuid, hazelnut.record_handler_all); 
 
 //                /* Subscriptions */
 //                var query_channel   = '/query/' + options.query_uuid + '/changed';
         this.query_update = null;
         this.current_resources = Array();
 
+        // query status
+        this.received_all = false;
+        this.received_set = false;
+        this.in_set_buffer = Array();
+
         var object = this;
 
         /* Transforms the table into DataTable, and keep a pointer to it */
                     // xxx problem is, we don't get this 'sliver' thing set apparently
                     if (typeof(row['sliver']) != 'undefined') { /* It is equal to null when <sliver/> is present */
                         checked = 'checked ';
-                        hazelnut.current_resources.push(row['urn']);
+                        hazelnut.current_resources.push(row[ELEMENT_KEY]);
                     }
                     // Use a key instead of hostname (hard coded...)
-                    line.push(hazelnut.checkbox(options.plugin_uuid, row['urn'], row['type'], checked, false));
+                    line.push(hazelnut.checkbox(options.plugin_uuid, row[ELEMENT_KEY], row['type'], checked, false));
                 }
     
                 lines.push(line);
         this.checkbox = function (plugin_uuid, header, field, selected_str, disabled_str)
         {
             var result="";
+            if (header === null)
+                header = '';
             // Prefix id with plugin_uuid
             result += "<input";
             result += " class='hazelnut-checkbox-" + plugin_uuid + "'";
-            result += " id='hazelnut-checkbox-" + plugin_uuid + "-" + unfold.get_value(header) + "'";
+            result += " id='hazelnut-checkbox-" + plugin_uuid + "-" + unfold.get_value(header).replace(/\\/g, '')  + "'";
             result += " name='" + unfold.get_value(field) + "'";
             result += " type='checkbox'";
             result += selected_str;
                 // xxx problem is, we don't get this 'sliver' thing set apparently
                 if (typeof(record['sliver']) != 'undefined') { /* It is equal to null when <sliver/> is present */
                     checked = 'checked ';
-                    hazelnut.current_resources.push(record['urn']);
+                    hazelnut.current_resources.push(record[ELEMENT_KEY]);
                 }
                 // Use a key instead of hostname (hard coded...)
-                line.push(object.checkbox(options.plugin_uuid, record['urn'], record['type'], checked, false));
+                line.push(object.checkbox(options.plugin_uuid, record[ELEMENT_KEY], record['type'], checked, false));
             }
     
             // XXX Is adding an array of lines more efficient ?
 
         };
 
+        this.set_checkbox = function(record)
+        {
+            // XXX urn should be replaced by the key
+            // XXX we should enforce that both queries have the same key !!
+            checkbox_id = "#hazelnut-checkbox-" + object.options.plugin_uuid + "-" + unfold.escape_id(record[ELEMENT_KEY].replace(/\\/g, ''))
+            $(checkbox_id, object.table.fnGetNodes()).attr('checked', true);
+        }
+
         this.record_handler = function(e, event_type, record)
         {
+            // elements in set
+            switch(event_type) {
+                case NEW_RECORD:
+                    /* NOTE in fact we are doing a join here */
+                    if (object.received_all)
+                        // update checkbox for record
+                        object.set_checkbox(record);
+                    else
+                        // store for later update of checkboxes
+                        object.in_set_buffer.push(record);
+                    break;
+                case CLEAR_RECORDS:
+                    // nothing to do here
+                    break;
+                case IN_PROGRESS:
+                    manifold.spin($(this));
+                    break;
+                case DONE:
+                    if (object.received_all)
+                        manifold.spin($(this), false);
+                    object.received_set = true;
+                    break;
+            }
+        };
+
+        this.record_handler_all = function(e, event_type, record)
+        {
+            // all elements
             switch(event_type) {
                 case NEW_RECORD:
+                    // Add the record to the table
                     object.new_record(record);
                     break;
                 case CLEAR_RECORDS:
                     manifold.spin($(this));
                     break;
                 case DONE:
-                    manifold.spin($(this), false);
+                    if (object.received_set) {
+                        /* XXX needed ? XXX We uncheck all checkboxes ... */
+                        $("[id^='datatables-checkbox-" + object.options.plugin_uuid +"']").attr('checked', false);
+
+                        /* ... and check the ones specified in the resource list */
+                        $.each(object.in_set_buffer, function(i, record) {
+                            object.set_checkbox(record);
+                        });
+
+                        manifold.spin($(this), false);
+                    }
+                    object.received_all = true;
                     break;
             }
         };
index 9dfebf1..fa8fae5 100644 (file)
@@ -5,9 +5,10 @@ class Hazelnut (Plugin):
     # set checkboxes if a final column with checkboxes is desired
     # pass columns as the initial set of columns
     #   if None then this is taken from the query's fields
-    def __init__ (self, query, checkboxes=False, columns=None, datatables_options={}, **settings):
+    def __init__ (self, query, query_all_uuid=None, checkboxes=False, columns=None, datatables_options={}, **settings):
         Plugin.__init__ (self, **settings)
-        self.query=query
+        self.query          = query
+        self.query_all_uuid = query_all_uuid
         self.checkboxes=checkboxes
         if columns is not None:
             self.columns=columns
@@ -39,4 +40,5 @@ class Hazelnut (Plugin):
         return reqs
 
     # the list of things passed to the js plugin
-    def json_settings_list (self): return ['plugin_uuid', 'domid', 'query_uuid','checkboxes','datatables_options']
+    def json_settings_list (self):
+        return ['plugin_uuid', 'domid', 'query_uuid', 'query_all_uuid', 'checkboxes','datatables_options']
index 7758845..d462e3e 100644 (file)
@@ -54,6 +54,7 @@ def _slice_view (request, slicename):
 
     # TODO The query to run is embedded in the URL
     main_query = Query.get('slice').filter_by('slice_hrn', '=', slicename)
+    query_resource_all = Query.get('resource').select('resource_hrn', 'hostname', 'type', 'authority')
 
     # Get default fields from metadata unless specified
     if not main_query.fields:
@@ -62,16 +63,17 @@ def _slice_view (request, slicename):
         if debug:
             print "METADATA", md_fields
         # TODO Get default fields
-        main_query.fields = [
+        main_query.select(
                 'slice_hrn',
                 'resource.resource_hrn', 'resource.hostname', 'resource.type', 'resource.authority',
                 'lease.urn',
                 'user.user_hrn',
 #                'application.measurement_point.counter'
-        ]
+        )
 
-    aq = AnalyzedQuery(main_query)
+    aq = AnalyzedQuery(main_query, metadata=metadata)
     page.enqueue_query(main_query, analyzed_query=aq)
+    page.enqueue_query(query_resource_all)
 
     # Prepare the display according to all metadata
     # (some parts will be pending, others can be triggered by users).
@@ -134,12 +136,13 @@ def _slice_view (request, slicename):
     )
 
     tab_resource_plugins.insert(Hazelnut( 
-        page        = page,
-        title       = 'List',
-        domid       = 'checkboxes',
+        page           = page,
+        title          = 'List',
+        domid          = 'checkboxes',
         # this is the query at the core of the slice list
-        query       = sq_resource,
-        checkboxes  = True,
+        query          = sq_resource,
+        query_all_uuid = query_resource_all.query_uuid,
+        checkboxes     = True,
         datatables_options = { 
             # for now we turn off sorting on the checkboxes columns this way
             # this of course should be automatic in hazelnut
index c104ea3..8d75cc3 100644 (file)
@@ -25,13 +25,17 @@ var unfold = {
     },
 
     get_value: function (value) {
-    //if(typeof(jQuery(value).attr('value'))!="undefined"){
-    if (/<span value=['"].*['"]>.*<\/span>/i.test(value)) {
-        return jQuery(value).attr('value');
-    } else {
-        return value;
+        //if(typeof(jQuery(value).attr('value'))!="undefined"){
+        if (/<span value=['"].*['"]>.*<\/span>/i.test(value)) {
+            return jQuery(value).attr('value');
+        } else {
+            return value;
+        }
+    },
+
+    escape_id: function(id) {
+        return id.replace( /(:|\.|\[|\])/g, "\\$1" );
     }
-}
 
 } // global unfold