From: Jordan Augé Date: Wed, 10 Jul 2013 14:01:54 +0000 (+0200) Subject: fixed hazelnut checkbox management X-Git-Tag: myslice-0.2-1~96 X-Git-Url: http://git.onelab.eu/?p=myslice.git;a=commitdiff_plain;h=49892eb0999c008ee142457f1a6e05ebd03f4d27 fixed hazelnut checkbox management --- diff --git a/manifold/core/query.py b/manifold/core/query.py index 4aba14b6..e45844c9 100644 --- a/manifold/core/query.py +++ b/manifold/core/query.py @@ -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) diff --git a/manifold/metadata.py b/manifold/metadata.py index 1b0e2708..41a16f31 100644 --- a/manifold/metadata.py +++ b/manifold/metadata.py @@ -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 diff --git a/plugins/hazelnut/hazelnut.js b/plugins/hazelnut/hazelnut.js index 84be66ed..d1a470fe 100644 --- a/plugins/hazelnut/hazelnut.js +++ b/plugins/hazelnut/hazelnut.js @@ -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'; @@ -134,6 +139,11 @@ 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 */ @@ -362,10 +372,10 @@ // xxx problem is, we don't get this 'sliver' thing set apparently if (typeof(row['sliver']) != 'undefined') { /* It is equal to null when 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); @@ -382,10 +392,12 @@ this.checkbox = function (plugin_uuid, header, field, selected_str, disabled_str) { var result=""; + if (header === null) + header = ''; // Prefix id with plugin_uuid result += " 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 ? @@ -449,10 +461,47 @@ }; + 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: @@ -462,7 +511,18 @@ 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; } }; diff --git a/plugins/hazelnut/hazelnut.py b/plugins/hazelnut/hazelnut.py index 9dfebf11..fa8fae5c 100644 --- a/plugins/hazelnut/hazelnut.py +++ b/plugins/hazelnut/hazelnut.py @@ -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'] diff --git a/trash/sliceview.py b/trash/sliceview.py index 77588454..d462e3e6 100644 --- a/trash/sliceview.py +++ b/trash/sliceview.py @@ -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 diff --git a/unfold/js/unfold-helper.js b/unfold/js/unfold-helper.js index c104ea3d..8d75cc3e 100644 --- a/unfold/js/unfold-helper.js +++ b/unfold/js/unfold-helper.js @@ -25,13 +25,17 @@ var unfold = { }, get_value: function (value) { - //if(typeof(jQuery(value).attr('value'))!="undefined"){ - if (/.*<\/span>/i.test(value)) { - return jQuery(value).attr('value'); - } else { - return value; + //if(typeof(jQuery(value).attr('value'))!="undefined"){ + if (/.*<\/span>/i.test(value)) { + return jQuery(value).attr('value'); + } else { + return value; + } + }, + + escape_id: function(id) { + return id.replace( /(:|\.|\[|\])/g, "\\$1" ); } -} } // global unfold