From 69482802ee8c2bef1323ba7b95ba5e629d11fa91 Mon Sep 17 00:00:00 2001 From: Thierry Parmentelat Date: Sat, 14 Dec 2013 10:39:44 +0100 Subject: [PATCH] =?utf8?q?A=20first=20stab=20at=20the=20=E2=80=98validateb?= =?utf8?q?utton=E2=80=99=20plugin=20This=20is=20not=20yet=20integrated=20i?= =?utf8?q?n=20the=20sliceview=20and=20others,=20but=20works=20in=20a=20sta?= =?utf8?q?ndalone=20test=20view=20like=20this=20http://localhost:8080/tras?= =?utf8?q?h/simplevalidatebutton/ple.inria.thierry=5Fparmentelat=20?= =?utf8?q?=E2=80=94=20in=20a=20nutshell,=20we=20have=20topmenu=20display?= =?utf8?q?=20a=20disabled=20link=20=E2=80=98validation=E2=80=99=20and=20se?= =?utf8?q?nd=20a=20query=20in=20background=20if=20anything=20gets=20return?= =?utf8?q?ed=20by=20this=20query=20-=20meaning=20the=20user=20is=20PI=20at?= =?utf8?q?=20some=20authority=20-=20we=20then=20re-enable=20the=20menu=20b?= =?utf8?q?utton=20=E2=80=94=20There=20is=20one=20catch=20at=20this=20point?= =?utf8?q?,=20which=20is=20that=20the=20DOM=20element=20attached=20to=20th?= =?utf8?q?e=20plugin=20-=20the=20menu=20button=20-=20needs=20to=20have=20t?= =?utf8?q?he=20=E2=80=98plugin=E2=80=99=20class=20because=20this=20is=20wh?= =?utf8?q?at=20is=20used=20to=20trigger=20query=20events=20So=20in=20this?= =?utf8?q?=20version=20we=20add=20this=20plugin=20class=20to=20the=20topme?= =?utf8?q?nu=20buttons,=20which=20is=20awkward=20=E2=80=94=20The=20reason?= =?utf8?q?=20why=20this=20plugin=20is=20special=20is,=20it=20does=20not=20?= =?utf8?q?come=20with=20its=20own=20DOM=20element=20but=20piggybacks=20on?= =?utf8?q?=20an=20existing=20one=20-=20which=20had=20no=20reason=20to=20ha?= =?utf8?q?ve=20this=20plugin=20class=20attached=20The=20plan=20to=20get=20?= =?utf8?q?this=20right=20is=20to=20have=20the=20plugin.js=20code=20manage?= =?utf8?q?=20an=20extra=20class=20(for=20example=20named=20pubsub)=20indep?= =?utf8?q?endantly=20from=20the=20DOM=20building=20code=20and=20convention?= =?utf8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- manifold/static/js/manifold.js | 18 +++-- myslice/settings.py | 2 +- plugins/validatebutton/__init__.py | 33 ++++++++ .../static/js/validatebutton.js | 33 ++++++++ trash/simplevalidatebutton.py | 65 ++++++++++++++++ trash/urls.py | 2 + ui/templates/widget-topmenu.html | 3 +- ui/topmenu.py | 78 ++++++++++--------- unfold/page.py | 2 +- 9 files changed, 191 insertions(+), 45 deletions(-) create mode 100644 plugins/validatebutton/__init__.py create mode 100644 plugins/validatebutton/static/js/validatebutton.js create mode 100644 trash/simplevalidatebutton.py diff --git a/manifold/static/js/manifold.js b/manifold/static/js/manifold.js index 1bb5b975..58f28485 100644 --- a/manifold/static/js/manifold.js +++ b/manifold/static/js/manifold.js @@ -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]); } }); diff --git a/myslice/settings.py b/myslice/settings.py index ff355e40..33588d40 100644 --- a/myslice/settings.py +++ b/myslice/settings.py @@ -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 index 00000000..ab143fc4 --- /dev/null +++ b/plugins/validatebutton/__init__.py @@ -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 index 00000000..dd987cde --- /dev/null +++ b/plugins/validatebutton/static/js/validatebutton.js @@ -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 index 00000000..ff77ee2f --- /dev/null +++ b/trash/simplevalidatebutton.py @@ -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'] = '

Some title

' + + # 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 diff --git a/trash/urls.py b/trash/urls.py index 5e14d977..32e3ad4b 100644 --- a/trash/urls.py +++ b/trash/urls.py @@ -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[\w\.]+)/?$', trash.simpletableview.SimpleTableView.as_view()), url(r'^simplegrid/(?P[\w\.]+)/?$', trash.simplegridview.SimpleGridView.as_view()), + url(r'^simplevalidatebutton/(?P[\w\._]+)/?$', trash.simplevalidatebutton.SimpleValidateButtonView.as_view()), ) diff --git a/ui/templates/widget-topmenu.html b/ui/templates/widget-topmenu.html index dbcbe1fc..79f1bf83 100644 --- a/ui/templates/widget-topmenu.html +++ b/ui/templates/widget-topmenu.html @@ -28,7 +28,8 @@ {% else %} - {% if d.is_active %}
  • {% else %}
  • {% endif %} +
  • {{ d.label }}
  • {% endif %} {% endfor %} diff --git a/ui/topmenu.py b/ui/topmenu.py index 6fbede50..d0238788 100644 --- a/ui/topmenu.py +++ b/ui/topmenu.py @@ -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/'}) diff --git a/unfold/page.py b/unfold/page.py index 0d3a81e6..07ce237a 100644 --- a/unfold/page.py +++ b/unfold/page.py @@ -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() -- 2.43.0