A first stab at the ‘validatebutton’ plugin
authorThierry Parmentelat <thierry.parmentelat@inria.fr>
Sat, 14 Dec 2013 09:39:44 +0000 (10:39 +0100)
committerThierry Parmentelat <thierry.parmentelat@inria.fr>
Sat, 14 Dec 2013 09:39:44 +0000 (10:39 +0100)
This is not yet integrated in the sliceview and others, but works in a standalone test view like this
http://localhost:8080/trash/simplevalidatebutton/ple.inria.thierry_parmentelat

in a nutshell, we have topmenu display a disabled link ‘validation’ and send a query in background
if anything gets returned by this query - meaning the user is PI at some authority - we then
re-enable the menu button

There is one catch at this point, which is that the DOM element attached to the plugin - the menu button - needs to
have the ‘plugin’ class because this is what is used to trigger query events
So in this version we add this plugin class to the topmenu buttons, which is awkward

The reason why this plugin is special is, it does not come with its own DOM element but piggybacks on an existing one - which had no reason to have this plugin class attached
The plan to get this right is to have the plugin.js code manage an extra class (for example named pubsub) independantly from the DOM building code and conventions

manifold/static/js/manifold.js
myslice/settings.py
plugins/validatebutton/__init__.py [new file with mode: 0644]
plugins/validatebutton/static/js/validatebutton.js [new file with mode: 0644]
trash/simplevalidatebutton.py [new file with mode: 0644]
trash/urls.py
ui/templates/widget-topmenu.html
ui/topmenu.py
unfold/page.py

index 1bb5b97..58f2848 100644 (file)
@@ -317,7 +317,7 @@ var manifold = {
     // reasonably low-noise, shows manifold requests coming in and out
     asynchroneous_debug : true,
     // print our more details on result publication and related callbacks
-    publish_result_debug : false,
+    publish_result_debug : true,
 
     /**
      * \brief We use js function closure to be able to pass the query (array)
@@ -417,19 +417,21 @@ var manifold = {
             count += 1;
         });
         if (manifold.publish_result_debug) 
-            messages.debug(".. publish_result NEW API (2) count=" + count);
+            messages.debug(".. publish_result (2) has used NEW API on " + count + " records");
         manifold.raise_record_event(query.query_uuid, DONE);
+        if (manifold.publish_result_debug) 
+            messages.debug(".. publish_result (3) has used NEW API to say DONE");
 
         // OLD PLUGIN API BELOW
         /* Publish an update announce */
         var channel="/results/" + query.query_uuid + "/changed";
         if (manifold.publish_result_debug) 
-            messages.debug(".. publish_result OLD API (3) " + channel);
+            messages.debug(".. publish_result (4) OLD API on channel" + channel);
 
         $.publish(channel, [result, query]);
 
         if (manifold.publish_result_debug) 
-            messages.debug(".. publish_result - END (4) q=" + query.__repr());
+            messages.debug(".. publish_result (5) END q=" + query.__repr());
     },
 
     /*!
@@ -736,7 +738,7 @@ var manifold = {
         }
 
         if (manifold.asynchroneous_debug) 
-            messages.debug ("========== asynchroneous_success " + query.object + " -- before process_query_records");
+            messages.debug ("========== asynchroneous_success " + query.object + " -- before process_query_records [" + query.query_uuid +"]");
 
         // once everything is checked we can use the 'value' part of the manifoldresult
         var result=data.value;
@@ -760,10 +762,12 @@ var manifold = {
      **************************************************************************/ 
 
     raise_event_handler: function(type, query_uuid, event_type, value) {
+       if (manifold.publish_result_debug)
+           messages.debug("raise_event_handler, quuid="+query_uuid+" type="+type+" event_type="+event_type);
         if ((type != 'query') && (type != 'record'))
             throw 'Incorrect type for manifold.raise_event()';
         // xxx we observe quite a lot of incoming calls with an undefined query_uuid
-        // this should be fixed upstream
+        // this should be fixed upstream in manifold I expect
         if (query_uuid === undefined) {
             messages.warning("undefined query in raise_event_handler");
             return;
@@ -775,8 +779,10 @@ var manifold = {
 
         $.each(channels, function(i, channel) {
             if (value === undefined) {
+               if (manifold.publish_result_debug) messages.debug("triggering [no value] on channel="+channel+" and event_type="+event_type);
                 $('.plugin').trigger(channel, [event_type]);
             } else {
+               if (manifold.publish_result_debug) messages.debug("triggering [value="+value+"] on channel="+channel+" and event_type="+event_type);
                 $('.plugin').trigger(channel, [event_type, value]);
             }
         });
index ff355e4..33588d4 100644 (file)
@@ -195,7 +195,7 @@ INSTALLED_APPS = (
     # 'django.contrib.admindocs',
     'portal',
     # temporary - not packaged
-    'trash',
+    'trash',
     'sample',
     'sandbox'
 # DEPRECATED #    'django.contrib.formtools',
diff --git a/plugins/validatebutton/__init__.py b/plugins/validatebutton/__init__.py
new file mode 100644 (file)
index 0000000..ab143fc
--- /dev/null
@@ -0,0 +1,33 @@
+from unfold.plugin import Plugin
+
+class ValidateButton (Plugin):
+
+    """This plugin is designed to work together with topmenu.
+    
+It will check to see if user has PI rights at least on one authority, 
+and if so will enable corresponding button in topmenu.
+
+A realistic example would have incoming query as
+
+Query.get('ple:user').filter_by('user_hrn', '==', '$user_hrn').select('pi_authorities')
+
+"""
+
+    def __init__ (self, query=None, button_domid=None, **settings):
+        Plugin.__init__ (self, **settings)
+        # set defaults
+        if query is None: 
+            query = Query.get('ple:user').filter_by('user_hrn', '==', '$user_hrn').select('pi_authorities')
+        if button_domid is None: button_domid="topmenu-validate"
+        self.query=query
+        self.button_domid=button_domid
+
+    # this does not have any materialization
+    def render_content (self, request):
+        return ""
+    
+    def requirements (self):
+        return { 'js_files': [ 'js/validatebutton.js', 'js/manifold-query.js', ], }
+
+    def json_settings_list (self):
+        return [ 'query_uuid', 'button_domid', ]
diff --git a/plugins/validatebutton/static/js/validatebutton.js b/plugins/validatebutton/static/js/validatebutton.js
new file mode 100644 (file)
index 0000000..dd987cd
--- /dev/null
@@ -0,0 +1,33 @@
+// first application is for the 'validation' button in the topmenu
+// if the subject query is non empty, then we turn on the subject button
+// that is provided through button_domid
+
+(function($){
+
+    var debug=false;
+    debug=true
+
+    var ValidateButton = Plugin.extend({
+
+        init: function(options, element) {
+            this._super(options, element);
+            this.listen_query(options.query_uuid);
+           this.triggered=false;
+       },
+
+       // we have received at least one answer: we'll do something
+       on_new_record: function (record) {
+           // we only need to act on the first record
+           if (this.triggered) return;
+           if (debug) messages.debug("validatebutton.on_query_done - turning on "+this.options.button_domid);
+           $('#'+this.options.button_domid).removeClass('disabled');
+           this.triggered=true;
+        },
+       // for reference only, since there is nothing we need to do at this point
+       on_query_done: function() {
+       },
+    });
+
+    $.plugin('ValidateButton', ValidateButton);
+
+})(jQuery);
diff --git a/trash/simplevalidatebutton.py b/trash/simplevalidatebutton.py
new file mode 100644 (file)
index 0000000..ff77ee2
--- /dev/null
@@ -0,0 +1,65 @@
+# just one instance of validator
+from django.views.generic.base import TemplateView
+from django.template import RequestContext
+from django.shortcuts import render_to_response
+
+from manifold.core.query import Query, AnalyzedQuery
+
+from unfold.page import Page
+
+from ui.topmenu import topmenu_items, the_user
+
+from plugins.validatebutton import ValidateButton
+
+class SimpleValidateButtonView (TemplateView):
+
+    # mention a user name in the URL as .../trash/simplevalidatebutton/ple.inria.thierry_parmentelat
+    def get (self, request, username='ple.inria.thierry_parmentelat'):
+
+        page=Page(request)
+        page.expose_js_metadata()
+        query_pi_auths = Query.get('ple:user').filter_by('user_hrn', '==', username ).select('pi_authorities')
+        page.enqueue_query(query_pi_auths)
+
+        # even though this plugin does not have any html materialization, the corresponding domid
+        # must exist because it is searched at init-time to create the JS plugin
+        # so we simply piggy-back the target button here
+        validatebutton = ValidateButton (page=page, 
+                                         # see above
+                                         domid='topmenu-validation',
+                                         query=query_pi_auths,
+                                         # this one is the target for a $.show() when the query comes back
+                                         button_domid="topmenu-validation")
+
+        # variables that will get passed to the view-unfold1.html template
+        template_env = {}
+        
+        # there is a need to call render() for exposing the query and creating the js plugin
+        # even though this returns an empty string
+        rendered=validatebutton.render(request)
+
+        # write something of our own instead
+        template_env ['unfold_main'] = '<h1>Some title </h1>'
+        
+        # more general variables expected in the template
+        template_env [ 'title' ] = 'simple validatebutton %(username)s'%locals()
+        # the menu items on the top
+        template_env [ 'topmenu_items' ] = topmenu_items('Slice', request) 
+        # so we can see who is logged
+        template_env [ 'username' ] = the_user (request) 
+    
+        # don't forget to run the requests
+        page.expose_queries ()
+
+        # the prelude object in page contains a summary of the requirements() for all plugins
+        # define {js,css}_{files,chunks}
+        prelude_env = page.prelude_env()
+
+#        print prelude_env.keys()
+#        for k in [ 'js_files' ] :
+#            print 'prelude_env',prelude_env,k,prelude_env[k]
+
+        template_env.update(prelude_env)
+        result=render_to_response ('view-unfold1.html',template_env,
+                                   context_instance=RequestContext(request))
+        return result
index 5e14d97..32e3ad4 100644 (file)
@@ -2,6 +2,7 @@ from django.conf.urls           import patterns, include, url
 
 import trash.simpletableview
 import trash.simplegridview
+import trash.simplevalidatebutton
 
 urlpatterns = patterns(
     '',
@@ -11,4 +12,5 @@ urlpatterns = patterns(
     url(r'^dashboard/?$',       'trash.dashboard.dashboard_view'),
     url(r'^simpletable/(?P<slicename>[\w\.]+)/?$', trash.simpletableview.SimpleTableView.as_view()),
     url(r'^simplegrid/(?P<slicename>[\w\.]+)/?$', trash.simplegridview.SimpleGridView.as_view()),
+    url(r'^simplevalidatebutton/(?P<username>[\w\._]+)/?$', trash.simplevalidatebutton.SimpleValidateButtonView.as_view()),
 )
index dbcbe1f..79f1bf8 100644 (file)
@@ -28,7 +28,8 @@
          </ul>
         </li>
         {% else %} 
-       {% if d.is_active %} <li class='active'> {% else %} <li class='other'> {% endif %}
+       <li class='{% if d.is_active %}active{% else %}other{% endif %} plugin{% if d.disabled %} disabled{%endif%}'
+       {% if d.domid %} id='{{d.domid}}'{% endif %}>
        <a href="{{ d.href }}"> {{ d.label }} </a> </li>
        {% endif %}
        {% endfor %}
index 6fbede5..d023878 100644 (file)
@@ -7,11 +7,14 @@ from manifold.core.query        import Query
 # dropdowns are kind of ad hoc for now, and limited to one level
 # [ 
 # ### a regular first-level button
-# {'label':...,'href':...}, 
+# {'label':...,'href':..., ['domid':.., 'disabled':...]}, 
 # ### a dropdown
 # { 'label': ..., 'href'=..., 'dropdown':True, 'contents': [ { 'label':.., 'href'} ] }
 # , ..]
 
+# see also templates/widget-topmenu.html for how these items are put together
+# and plugins/validatebutton for how this hident button is turned on when necessary
+
 # current: the beginning of the label in the menu that you want to outline
 def topmenu_items (current,request=None):
     has_user=request.user.is_authenticated()
@@ -20,41 +23,44 @@ def topmenu_items (current,request=None):
     if has_user:
         result.append({'label':'Dashboard', 'href': '/portal/dashboard/'})
         result.append({'label':'Request a slice', 'href': '/portal/slice_request/'})
-        # ** Where am I a PI **
-        # For this we need to ask SFA (of all authorities) = PI function
-        user_query  = Query().get('local:user').select('config','email')
-        user_details = execute_query(request, user_query)
-
-        # Required: the user must have an authority in its user.config
-        # XXX Temporary solution
-        # not always found in user_details...
-        config={}
-# Deactivated until fixed 
-#        if user_details is not None:
-#            for user_detail in user_details:
-#                #email = user_detail['email']
-#                if user_detail['config']:
-#                    config = json.loads(user_detail['config'])
-#            user_detail['authority'] = config.get('authority',"Unknown Authority")
-#            print "topmenu: %s", (user_detail['authority'])
-#            if user_detail['authority'] is not None:
-#                sub_authority = user_detail['authority'].split('.')
-#                root_authority = sub_authority[0]
-#                pi_authorities_query = Query.get(root_authority+':user').filter_by('user_hrn', '==', '$user_hrn').select('pi_authorities')
-#        else:
-#            pi_authorities_query = Query.get('user').filter_by('user_hrn', '==', '$user_hrn').select('pi_authorities')
-#        try:
-#            pi_authorities_tmp = execute_query(request, pi_authorities_query)
-#        except:
-#            pi_authorities_tmp = set()
-#        pi_authorities = set()
-#        for pa in pi_authorities_tmp:
-#            if 'pi_authorities' in pa:
-#                pi_authorities |= set(pa['pi_authorities'])
-#        print "pi_authorities =", pi_authorities
-#        if len(pi_authorities) > 0:
-#            result.append({'label':'Validation', 'href': '/portal/validate/'})
-        result.append({'label':'Validation', 'href': '/portal/validate/'})
+###        # ** Where am I a PI **
+###        # For this we need to ask SFA (of all authorities) = PI function
+###        user_query  = Query().get('local:user').select('config','email')
+###        user_details = execute_query(request, user_query)
+###
+###        # Required: the user must have an authority in its user.config
+###        # XXX Temporary solution
+###        # not always found in user_details...
+###        config={}
+#### Deactivated until fixed 
+####        if user_details is not None:
+####            for user_detail in user_details:
+####                #email = user_detail['email']
+####                if user_detail['config']:
+####                    config = json.loads(user_detail['config'])
+####            user_detail['authority'] = config.get('authority',"Unknown Authority")
+####            print "topmenu: %s", (user_detail['authority'])
+####            if user_detail['authority'] is not None:
+####                sub_authority = user_detail['authority'].split('.')
+####                root_authority = sub_authority[0]
+####                pi_authorities_query = Query.get(root_authority+':user').filter_by('user_hrn', '==', '$user_hrn').select('pi_authorities')
+####        else:
+####            pi_authorities_query = Query.get('user').filter_by('user_hrn', '==', '$user_hrn').select('pi_authorities')
+####        try:
+####            pi_authorities_tmp = execute_query(request, pi_authorities_query)
+####        except:
+####            pi_authorities_tmp = set()
+####        pi_authorities = set()
+####        for pa in pi_authorities_tmp:
+####            if 'pi_authorities' in pa:
+####                pi_authorities |= set(pa['pi_authorities'])
+####        print "pi_authorities =", pi_authorities
+####        if len(pi_authorities) > 0:
+####            result.append({'label':'Validation', 'href': '/portal/validate/'})
+###        result.append({'label':'Validation', 'href': '/portal/validate/'})
+        # always create a disabled button for validation, and let the 
+        # validatebutton plugin handle that asynchroneously, based on this domid
+        result.append({'label':'Validation', 'href': '/portal/validate/', 'domid':'topmenu-validation', 'disabled':True})
         dropdown = []
         dropdown.append({'label':'Platforms', 'href': '/portal/platforms/'})
         dropdown.append({'label':'My Account', 'href': '/portal/account/'})
index 0d3a81e..07ce237 100644 (file)
@@ -120,7 +120,7 @@ class Page:
     def expose_js_metadata (self):
         # expose global MANIFOLD_METADATA as a js variable
         # xxx this is fetched synchroneously..
-        self.add_js_init_chunks("var MANIFOLD_METADATA =" + self.get_metadata().to_json() + ";")
+        self.add_js_init_chunks("var MANIFOLD_METADATA =" + self.get_metadata().to_json() + ";\n")
 
     def expose_js_manifold_config (self):
         config=Config()