1
0
mirror of https://github.com/dkmstr/openuds.git synced 2024-12-23 17:34:17 +03:00

Implemented callbacks for gui "fillers", that is, choices that calls server for information about how to fill other fields with returned values...

This commit is contained in:
Adolfo Gómez 2013-12-04 01:57:33 +00:00
parent 36bdbbfd37
commit 61c9f4f975
10 changed files with 209 additions and 26 deletions

View File

@ -11,6 +11,7 @@ encoding//src/server/urls.py=utf-8
encoding//src/uds/REST/__init__.py=utf-8
encoding//src/uds/REST/handlers.py=utf-8
encoding//src/uds/REST/methods/authenticators.py=utf-8
encoding//src/uds/REST/methods/gui_callback.py=utf-8
encoding//src/uds/REST/methods/login_logout.py=utf-8
encoding//src/uds/REST/methods/networks.py=utf-8
encoding//src/uds/REST/methods/osmanagers.py=utf-8
@ -151,6 +152,7 @@ encoding//src/uds/migrations/0012_auto__add_field_authenticator_small_name.py=ut
encoding//src/uds/migrations/0013_auto__add_field_group_is_meta__add_field_uniqueid_stamp.py=utf-8
encoding//src/uds/migrations/0014_auto__add_field_network_net_string.py=utf-8
encoding//src/uds/migrations/0016_auto__add_field_userservice_cluster_node.py=utf-8
encoding//src/uds/migrations/0017_change_tables.py=utf-8
encoding//src/uds/models.py=utf-8
encoding//src/uds/osmanagers/LinuxOsManager/LinuxOsManager.py=utf-8
encoding//src/uds/osmanagers/LinuxOsManager/__init__.py=utf-8

View File

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from uds.core.ui.UserInterface import gui
from uds.REST import Handler, RequestError, NotFound
import logging
logger = logging.getLogger(__name__)
# Enclosed methods under /auth path
class Callback(Handler):
path = 'gui'
authenticated = True # Public method
needs_staff = True #
def get(self):
'''
This login uses parameters to generate auth token
The alternative is to use the template tag inside "REST" that is called auth_token, that extracts an auth token from an user session
We can use any of this forms due to the fact that the auth token is in fact a session key
Parameters:
mandatory:
username:
password:
auth:
optional:
locale: (defaults to "en")
Result:
on success: { 'result': 'ok', 'auth': [auth_code] }
on error: { 'result: 'error', 'error': [error string] }
'''
logger.debug('Params: {0}'.format(self._params))
if len(self._args) != 1:
raise RequestError('Invalid Request')
if gui.callbacks.has_key(self._args[0]):
return gui.callbacks[self._args[0]](self._params)
raise NotFound('callback {0} not found'.format(self._args[0]))

View File

@ -50,6 +50,13 @@ class ContentProcessor(object):
def __init__(self, request):
self._request = request
def processGetParameters(self):
if self._request.method != 'GET':
return {}
return self._request.GET.copy()
def processParameters(self):
return ''
@ -70,7 +77,7 @@ class JsonProcessor(ContentProcessor):
def processParameters(self):
try:
if len(self._request.body) == 0:
return {}
return self.processGetParameters()
res = json.loads(self._request.body)
logger.debug(res)
return res

View File

@ -66,7 +66,7 @@ table.dataTable tr.even td.sorting_3 { background-color: blue; }*/
/* modal dialogs & related*/
.modal-dialog {
/* new custom width */
width: 60%;
width: 90%;
}
@ -76,6 +76,7 @@ table.dataTable tr.even td.sorting_3 { background-color: blue; }*/
.tooltip {
z-index: 2014;
}
/* Edit Below to Customize Widths > 768px */
@media (min-width:768px) {
@ -142,6 +143,11 @@ table.dataTable tr.even td.sorting_3 { background-color: blue; }*/
.label-tbl-button {
display: inline-block;
}
.modal-dialog {
/* new custom width */
width: 60%;
}
}
}

View File

@ -179,7 +179,7 @@ function BasicModelRest(path, options) {
BasicModelRest.prototype = {
// options:
// cacheKey: '.' --> do not cache
// cacheKey: '.' --> do not cache (undefined will set cacheKey to current path)
// undefined -- > use path as key
// success: success fnc to execute in case of success
_requestPath: function(path, options) {
@ -322,7 +322,7 @@ BasicModelRest.prototype = {
path = this.guiPath;
}
return this._requestPath(path, {
cacheKey: path,
cacheKey: '.', // Gui is not cacheable, it's dynamic and can change from call to call
success: success_fnc,
fail: fail_fnc,
});

View File

@ -4,9 +4,24 @@
gui.forms = {};
gui.forms.callback = function(formSelector, method, params, success_fnc) {
var path = 'gui/callback/' + method;
var p = [];
$.each(params, function(index, val) {
p.push(val.name + '=' + encodeURIComponent(val.value));
});
path = path + '?' + p.join('&');
api.getJson(path, {
success: success_fnc,
});
};
// Returns form fields that will manage a gui description (new or edit)
gui.forms.fieldsToHtml = function(itemGui, item, editing) {
var html = '';
var fillers = []; // Fillers (callbacks)
var originalValues = {}; // Initial stored values (defaults to "reset" form and also used on fillers callback to try to restore previous value)
// itemGui is expected to have fields sorted by .gui.order (REST api returns them sorted)
$.each(itemGui, function(index, f){
gui.doLog(f);
@ -15,8 +30,19 @@
if( f.gui.type == 'text' && f.gui.multiline ) {
f.gui.type = 'textbox';
}
var value = item[f.name] || f.gui.value || f.gui.defvalue;
// We need to convert "array" values for multichoices to single list of ids (much more usable right here)
if( f.gui.type == 'multichoice') {
var newValue = [];
$.each(value, function(undefined, val) {
newValue.push(val.id);
});
value = newValue;
}
originalValues[f.name] = value; // Store original value
html += api.templates.evaluate('tmpl_fld_'+f.gui.type, {
value: item[f.name] || f.gui.value || f.gui.defvalue, // If no value present, use default value
value: value, // If no value present, use default value
values: f.gui.values,
label: f.gui.label,
length: f.gui.length,
@ -28,31 +54,106 @@
name: f.name,
css: 'modal_field_data',
});
// if this field has a filler (callback to get data)
if( f.gui.fills ) {
gui.doLog('This field has a filler');
fillers.push({ name: f.name, callbackName: f.gui.fills.callbackName, parameters: f.gui.fills.parameters });
}
});
return html;
return { html: html, fillers: fillers, originalValues: originalValues };
};
gui.forms.fromFields = function(fields, item) {
var editing = item !== undefined; // Locate real Editing
item = item || {id:''};
var form = '<form class="form-horizontal" role="form">' +
'<input type="hidden" name="id" class="modal_field_data" value="' + item.id + '">';
var fillers = [];
var originalValues = {};
if( fields.tabs ) {
var id = 'tab-' + Math.random().toString().split('.')[1]; // Get a random base ID for tab entries
var tabs = [];
var tabsContent = [];
var active = ' active in' ;
$.each(fields.tabs, function(index, tab){
tabsContent.push('<div class="tab-pane fade' + active + '" id="' + id + index + '">' + gui.forms.fieldsToHtml(tab.fields, item) + '</div>' );
var h = gui.forms.fieldsToHtml(tab.fields, item);
tabsContent.push('<div class="tab-pane fade' + active + '" id="' + id + index + '">' + h.html + '</div>' );
tabs.push('<li><a href="#' + id + index + '" data-toggle="tab">' + tab.title + '</a></li>' );
active = '';
fillers = fillers.concat(h.fillers); // Fillers (callback based)
$.extend(originalValues, h.originalValues); // Original values
gui.doLog('Fillers:', h.fillers);
});
form += '<ul class="nav nav-tabs">' + tabs.join('\n') + '</ul><div class="tab-content">' + tabsContent.join('\n') + '</div>';
} else {
form += gui.forms.fieldsToHtml(fields, item, editing);
var h = gui.forms.fieldsToHtml(fields, item, editing);
form += h.html;
fillers = fillers.concat(h.fillers);
$.extend(originalValues, h.originalValues);
}
form += '</form>';
return form;
gui.doLog('Original values: ', originalValues);
// Init function for callbacks.
// Callbacks can only be attached to "Selects", but it's parameters can be got from any field
// This needs the "form selector" as base for setting callbacks, etc..
var init = function(formSelector) {
gui.doLog(formSelector, fillers);
/*var pos = 0;
var triggerChangeSequentially = function() {
if( pos >= fillers.length )
return;
$(formSelector + ' [name="' + fillers[pos].name + '"]').trigger('change');
pos = pos + 1;
};*/
var onChange = function(filler) {
return function() {
gui.doLog('Onchange invoked for ', filler);
// Attach on change method to each filler, and after that, all
var params = [];
$.each(filler.parameters, function(undefined, p){
var val = $(formSelector + ' [name="' + p + '"]').val();
params.push({name: p, value: val});
});
gui.forms.callback(formSelector, filler.callbackName, params, function(data){
$.each(data, function(undefined, sel){
// Update select contents with returned values
var $select = $(formSelector + ' [name="' + sel.name + '"]');
$select.empty();
$.each(sel.values, function(undefined, value){
$select.append('<option value="' + value.id + '">' + value.text + '</option>');
});
$select.val(originalValues[sel.name]);
// Refresh selectpicker updated
if($select.hasClass('selectpicker'))
$select.selectpicker('refresh');
// Trigger change for the changed item
$select.trigger('change');
});
//triggerChangeSequentially();
});
};
};
// Sets the "on change" event for select with fillers (callbacks that fills other fields)
$.each(fillers, function(undefined, f) {
$(formSelector + ' [name="' + f.name + '"]').on('change', onChange(f));
});
if( fillers.length )
$(formSelector + ' [name="' + fillers[0].name + '"]').trigger('change');
};
return { 'html': form, 'init': init }; // Returns the form and a initialization function for the form, that must be invoked to start it
};
// Reads fields from a form
@ -74,9 +175,13 @@
gui.forms.launchModal = function(title, fields, item, onSuccess) {
var id = 'modal-' + Math.random().toString().split('.')[1]; // Get a random ID for this modal
gui.appendToWorkspace(gui.modal(id, title, gui.forms.fromFields(fields, item)));
var ff = gui.forms.fromFields(fields, item);
gui.appendToWorkspace(gui.modal(id, title, ff.html));
id = '#' + id; // for jQuery
if( ff.init )
ff.init(id);
// Get form
var $form = $(id + ' form');

View File

@ -128,22 +128,12 @@
});
};
gui.failRequestMessageFnc = function(jqXHR, textStatus, errorThrown) {
api.templates.get('request_failed', function(tmpl) {
gui.clearWorkspace();
gui.appendToWorkspace(api.templates.evaluate(tmpl, {
error: jqXHR.responseText,
}));
});
gui.setLinksEvents();
};
gui.failRequestModalFnc = function(title) {
return function(jqXHR, textStatus, errorThrown) { // fail on put
gui.launchModal(title, jqXHR.responseText, ' ');
};
};
gui.clearWorkspace = function() {
$('#content').empty();
$('#minimized').empty();

View File

@ -110,7 +110,7 @@
// Initialize gui
gui.init();
// set default error function
api.defaultFail = gui.failRequestMessageFnc;
api.defaultFail = gui.failRequestModalFnc(gettext('Error on request'));
});
</script>
{% block js %}{% endblock %}

View File

@ -1,5 +1,6 @@
{% extends "uds/admin/tmpl/fld/form-group.html" %}
{% load i18n %}
{% comment %}The choice item MUST be a Select{% endcomment %}
{% block field %}
{% verbatim %}
<select class="selectpicker show-menu-arrow {{ css }}" name="{{ name }}" id="{{ name }}_field" {{# if readonly }} disabled{{/ if }}>

View File

@ -62,9 +62,6 @@
{% endblock %}
{% block body %}
{% ifbrowser webkit %}
webkit!!!
{% endifbrowser %}
<h2>{% trans "Services" %}</h2>
<div class="clearfix">
{% for ser in services %}