we embed the (currently raw) results from manifold API in a ManifoldResult dict
authorThierry Parmentelat <thierry.parmentelat@inria.fr>
Wed, 27 Mar 2013 08:19:33 +0000 (09:19 +0100)
committerThierry Parmentelat <thierry.parmentelat@inria.fr>
Wed, 27 Mar 2013 08:19:33 +0000 (09:19 +0100)
this needs to be analyzed after each API call

auth/manifoldbackend.py
manifold/js/manifold.js
manifold/manifoldapi.py
manifold/manifoldproxy.py
manifold/manifoldresult.py [new file with mode: 0644]
manifold/metadata.py
plugins/hazelnut/DataTables.php [new file with mode: 0644]
plugins/hazelnut/demo_page.css1 [new file with mode: 0644]

index 96697b3..2b1bde5 100644 (file)
@@ -2,7 +2,7 @@ import time
 
 from django.contrib.auth.models import User
 
-from manifold.manifoldapi import ManifoldAPI
+from manifold.manifoldapi import ManifoldAPI, ManifoldResult
 
 # Name my backend 'ManifoldBackend'
 class ManifoldBackend:
@@ -21,10 +21,13 @@ class ManifoldBackend:
             auth = {'AuthMethod': 'password', 'Username': username, 'AuthString': password}
             api = ManifoldAPI(auth)
             # Authenticate user and get session key
-            session = api.GetSession()
-            if not session : 
-                return None
+            session_result = api.GetSession()
+            session = session_result.ok_value()
+            if not session:
+                print "GetSession failed",session_result.error()
+                return
             
+            print 'DEALING with session',session
             #self.session = session
             # Change GetSession() at some point to return expires as well
             expires = time.time() + (24 * 60 * 60)
@@ -34,8 +37,12 @@ class ManifoldBackend:
             self.api = api
 
             # Get account details
-            person = api.GetPersons(auth)[0]
-            self.person = person
+            persons_result = api.GetPersons(auth)
+            persons = persons_result.ok_value()
+            if not persons:
+                print "GetPersons failed",persons_result.error()
+                return
+            person = persons[0]
 
             request.session['manifold'] = {'auth': api.auth, 'person': person, 'expires': expires}
         except:
index 6b2d728..449f735 100644 (file)
@@ -67,6 +67,17 @@ var manifold = {
            },
 
     asynchroneous_success : function (data, query, id) {
+       if (manifold.asynchroneous_debug) console.log ("received manifold result with code " + data.code);
+       // xxx should have a nicer declaration of that enum in sync with the python code somehow
+       if (data.code == 1) {
+           alert("Your session has expired, please log in again");
+           window.location="/logout/";
+           return;
+       } elif (data.code != 0) {
+           alert("Error received from manifold backend at " + MANIFOLD_URL + " [" + data.output + "]");
+           return;
+       }
+       data=data.value;
        if (data) {
             if (!!id) {
                /* Directly inform the requestor */
index 6a0e1d2..6c942b4 100644 (file)
@@ -3,13 +3,10 @@ import xmlrpclib
 
 from myslice.config import Config
 
-debug=True
+from manifoldresult import ManifoldResult, ManifoldCode
 
-class SessionExpired (Exception):
-    def __init__ (self,message):
-        self.message=message
-    def __repr__ (self): 
-        return "<SessionExpired: %s>"%self.message
+debug=False
+debug=True
 
 class ManifoldAPI:
 
@@ -25,36 +22,43 @@ class ManifoldAPI:
         self.url = config.manifold_url
         self.server = xmlrpclib.Server(self.url, verbose=False, allow_none=True)
 
+    def __repr__ (self): return "ManifoldAPI[%s]"%self.url
+
     def __getattr__(self, methodName):
         def func(*args, **kwds):
+            if (debug): 
+                print "entering ManifoldAPI.%s"%methodName,
+                print "args",args,
+                print "kwds",kwds
             try:
                 result=getattr(self.server, methodName)(self.auth, *args, **kwds)
                 if debug:
                     print '===> backend call',methodName, self.auth, self.url,'->',
-                    if not result:                        print "no/empty result"
+                    if not result:                        print "[no/empty result]"
                     elif isinstance (result,str):         print "result is '%s'"%result
                     elif isinstance (result,list):        print "result is a %d-elts list"%len(result)
-                    else:                                 print "dont know how to display result"
-                return result
+                    else:                                 print "[dont know how to display result]"
+                return ManifoldResult (code=ManifoldCode.SUCCESS, value=result)
             except xmlrpclib.Fault, error:
                 ### xxx this is very rough for now
                 # until we have some agreement about how the API calls should return error conditions
                 # in some less unpolite way than this anoanymous exception, we assume it's a problem with the session
                 # that needs to be refreshed
+                if debug: print "Session Expired"
                 if error.faultCode == 8002:
                     reason="most likely your session has expired"
                     reason += " (the manifold API has no unambiguous error reporting mechanism yet)"
-                    raise SessionExpired(reason)
+                    return ManifoldResult (code=ManifoldCode.SESSION_EXPIRED, output=reason)
             except Exception,error:
                 print "ManifoldAPI: unexpected exception",error
-                raise
+                return ManifoldResult (code=ManifoldResult.UNKNOWN_ERROR, output="%s"%error)
         return func
 
     def send_manifold_query (self, manifold_query):
         (action,subject)= (manifold_query.action,manifold_query.subject)
         if action=='get':
             # use self.Get rather than self.server.Get so we catch exceptions as per __getattr__
-            return self.Get(self.auth, subject, manifold_query.filters, {}, manifold_query.fields)
+            return self.Get(subject, manifold_query.filters, {}, manifold_query.fields)
         # xxx...
         else:
             print "WARNING: ManifoldAPI.send_manifold_query: only 'get' implemented for now"
index c444a37..d0636c8 100644 (file)
@@ -4,7 +4,7 @@ import json
 from django.http import HttpResponse, HttpResponseForbidden
 
 from manifold.manifoldquery import ManifoldQuery
-from manifold.manifoldapi import ManifoldAPI, SessionExpired
+from manifold.manifoldapi import ManifoldAPI
 
 debug=False
 debug=True
@@ -63,14 +63,8 @@ with the query passed using POST"""
         # actually forward
         manifold_api= ManifoldAPI(auth=manifold_api_session_auth)
         if debug: print 'manifoldproxy.proxy: sending to backend', manifold_query
-        # xxx we should embed the values inside a geni-like wrapper
-        try:
-            answer=manifold_api.send_manifold_query (manifold_query)
-            if debug: 
-                try:        print "received answer from backend with %d rows"%len(answer)
-                except:     print "received answer from backend - can't say len"
-        except SessionExpired,error:
-            answer=[ error.message ] 
+        answer=manifold_api.send_manifold_query (manifold_query)
+        if debug: print 'manifoldproxy.proxy: received from backend with code', answer['code']
         json_answer=json.dumps(answer)
         if (debug):
             with (file(offline_filename,"w")) as f:
diff --git a/manifold/manifoldresult.py b/manifold/manifoldresult.py
new file mode 100644 (file)
index 0000000..c99d861
--- /dev/null
@@ -0,0 +1,30 @@
+def enum(*sequential, **named):
+    enums = dict(zip(sequential, range(len(sequential))), **named)
+    return type('Enum', (), enums)
+
+ManifoldCode = enum (
+    SUCCESS=0,
+    SESSION_EXPIRED=1,
+    OTHERS=2,
+)
+
+# being a dict this can be used with json.dumps
+class ManifoldResult (dict):
+    def __init__ (self, code=ManifoldCode.SUCCESS, value=None, output=""):
+        self['code']=code
+        self['value']=value
+        self['output']=output
+
+    def from_json (self, json_string):
+        d=json.dumps(json_string)
+        for k in ['code','value','output']:
+            self[k]=d[k]
+
+    # this returns None if there's a problem, the value otherwise
+    def ok_value (self):
+        if self['code']==ManifoldCode.SUCCESS:
+            return self['value']
+
+    def error (self):
+        return "code=%s -- %s"%(self['code'],self['output'])
+    
index e8578c4..c092dfd 100644 (file)
@@ -1,7 +1,11 @@
 import json 
 
+from manifold.manifoldresult import ManifoldResult
 from manifold.manifoldapi import ManifoldAPI
 
+debug=False
+debug=True
+
 # turn this on if you want to work offline
 work_offline=False
 #work_offline=True
@@ -28,11 +32,15 @@ class MetaData:
                   'column.resource_type', 'column.value_type',
                   'column.allowed_values', 'column.platforms.platform',
                   'column.platforms.platform_url']
-        results = manifold_api.Get('metadata:table', [], [], fields)
-        self.hash_by_subject = dict ( [ (result['table'], result) for result in results ] )
+        rows_result = manifold_api.Get('metadata:table', [], [], fields)
+        rows = rows_result.ok_value()
+        if not rows:
+            print "Failed to retrieve metadata",rows_result.error()
+        self.hash_by_subject = dict ( [ (row['table'], row) for row in rows ] )
         # save for next time we use offline mode
-        with file(offline_filename,'w') as f:
-            f.write(json.dumps(self.hash_by_subject))
+        if debug:
+            with file(offline_filename,'w') as f:
+                f.write(json.dumps(self.hash_by_subject))
 
     def to_json(self):
         return json.dumps(self.hash_by_subject)
diff --git a/plugins/hazelnut/DataTables.php b/plugins/hazelnut/DataTables.php
new file mode 100644 (file)
index 0000000..67a8985
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+
+
+class DataTables extends Plugin
+{
+
+       /* Knowing the uuid of a query, we could get the results with Plugins */
+       /* Same for async */
+    public function render_content()
+    //$plugin_uuid, $options)
+    {
+
+        //Plugins::add_js('/DataTables/jquery.dataTables.js');
+        Plugins::add_js('//ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.3/jquery.dataTables.js');
+        Plugins::add_js('/DataTables/DataTables.js');
+        //Plugins::add_css('/DataTables/DataTables.css');
+        //Plugins::add_css('/DataTables/demo_table.css');
+        Plugins::add_css('/DataTables/demo_table_jui.css');
+
+        $uuid = $this->uuid;
+
+               $results = Array();
+               $async = 1;
+
+
+        /* XXX required field in options : query_uuid */
+         
+        
+        $query = Plugins::get_query_by_uuid($this->params['query_uuid']);
+        $is_unique = Plugins::get_key_filter($query);
+           $method_keys = Plugins::get_default_fields($query->method, $is_unique);
+          //$method_keys = Plugins::query_get_default_keys($options['query_uuid']);
+          //$fields = Plugins::query_get_fields($options['query_uuid']);
+        //$all_headers = $_SESSION['metadata']['nodes']['columns'];        
+        //$fields = $all_headers;
+
+       $fields = Plugins::metadata_get_fields($query->method);
+
+       /*
+        * @author: lbaron
+        * date: 2012-05-29
+        * debug columns QueryEditor to DataTables
+        *
+        */
+        // Problem: field names are differents between 
+        // $_SESSION['metadata']['nodes']['columns'] 
+        // JSON values
+        //$fields[]="arch"; // architecture in Session metadata
+        //$fields[]="astype"; // as_type in Session metadata
+        $fields['platform'] = Array('column' => 'platform');
+        $fields['platform_longname'] = Array('column' => 'platform_longname');
+
+        //----------------------------------
+        
+        $out = Array();
+        $out[] = "<table class='display' id='table-$uuid'>";
+        $out[] = "<thead><tr>";
+
+        foreach ($method_keys as $f) {
+            $out[] = "<th>$f</th>";
+        }
+
+        /* We put defaults fields (should be keys) at the beginning, and don't repeat them afterwards */
+        foreach ($fields as $key=>$f) {
+            if((array_search($f['column'], $method_keys)) === false)
+                $out[] = "<th>".$f['column']."</th>";
+        }
+
+        if (array_key_exists('checkboxes', $this->params) && ($this->params['checkboxes'])) {
+            $out[] = "<th>+/-</th>";            
+        }
+        $out[] = "</tr></thead>";
+        $out[] = "<tbody>";        
+        
+        /* This might be done asynchronously */
+        if (!$async) {
+            $query = Plugins::get_query_by_uuid($this->params['query_uuid']);
+            foreach ($results as $r) {
+                $out[] = "<tr>";
+                foreach ($fields as $f) {
+                    $out[] = "<td>";
+                    $out[] = Plugins::render_element($query, $r[$f], $f); // XXX was query->method
+                    $out[] = "</td>";
+                }
+                if (array_key_exists('checkboxes', $this->params) && ($this->params['checkboxes'])) {
+                    $out[] = "<td>[X]</td>";
+                }
+                $out[] = "</tr>";
+            }
+        }
+
+
+        if ($async) {
+            /* setup datatables */
+            /* TODO:
+             * - fixed row for key columns
+             * - uniform row height
+             * - how to make some columns disappear
+             * - how to udpate some columns based on keys
+             */
+        }
+        $out[] = "</tbody>";
+        $out[] = "</table>";
+        return implode($out);
+    }
+}
+
+Plugins::register_plugin(
+    'DataTables',   /* plugin name */
+    'DataTables',   /* class name */
+    Array(
+        'method' => '*',
+        'fields' => Array()
+    )
+    /* XXX dependencies */
+);
diff --git a/plugins/hazelnut/demo_page.css1 b/plugins/hazelnut/demo_page.css1
new file mode 100644 (file)
index 0000000..bee7b0d
--- /dev/null
@@ -0,0 +1,93 @@
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * General page setup
+ */
+#dt_example {
+       font: 80%/1.45em "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
+       margin: 0;
+       padding: 0;
+       color: #333;
+       background-color: #fff;
+}
+
+
+#dt_example #container {
+       width: 800px;
+       margin: 30px auto;
+       padding: 0;
+}
+
+
+#dt_example #footer {
+       margin: 50px auto 0 auto;
+       padding: 0;
+}
+
+#dt_example #demo {
+       margin: 30px auto 0 auto;
+}
+
+#dt_example .demo_jui {
+       margin: 30px auto 0 auto;
+}
+
+#dt_example .big {
+       font-size: 1.3em;
+       font-weight: bold;
+       line-height: 1.6em;
+       color: #4E6CA3;
+}
+
+#dt_example .spacer {
+       height: 20px;
+       clear: both;
+}
+
+#dt_example .clear {
+       clear: both;
+}
+
+#dt_example pre {
+       padding: 15px;
+       background-color: #F5F5F5;
+       border: 1px solid #CCCCCC;
+}
+
+#dt_example h1 {
+       margin-top: 2em;
+       font-size: 1.3em;
+       font-weight: normal;
+       line-height: 1.6em;
+       color: #4E6CA3;
+       border-bottom: 1px solid #B0BED9;
+       clear: both;
+}
+
+#dt_example h2 {
+       font-size: 1.2em;
+       font-weight: normal;
+       line-height: 1.6em;
+       color: #4E6CA3;
+       clear: both;
+}
+
+#dt_example a {
+       color: #0063DC;
+       text-decoration: none;
+}
+
+#dt_example a:hover {
+       text-decoration: underline;
+}
+
+#dt_example ul {
+       color: #4E6CA3;
+}
+
+.css_right {
+       float: right;
+}
+
+.css_left {
+       float: left;
+}
\ No newline at end of file