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

A bit of refactoring with api, gui, etc...

solved some problems with PUT methods (need to solve a few ones anyway)
Create authenticator now works, but needs more elaboration
lots of changes i event remember.. :-)
This commit is contained in:
Adolfo Gómez 2013-11-25 03:01:58 +00:00
parent a52e8dc548
commit 8c04c88d86
12 changed files with 690 additions and 504 deletions

View File

@ -37,7 +37,7 @@ from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _, activate from django.utils.translation import ugettext as _, activate
from django.conf import settings from django.conf import settings
from handlers import Handler, HandlerError, AccessDenied, NotFound from handlers import Handler, HandlerError, AccessDenied, NotFound, RequestError
import time import time
import logging import logging
@ -148,12 +148,14 @@ class Dispatcher(View):
for k, v in handler.headers().iteritems(): for k, v in handler.headers().iteritems():
response[k] = v response[k] = v
return response return response
except HandlerError as e: except RequestError as e:
return http.HttpResponseBadRequest(unicode(e)) return http.HttpResponseServerError(unicode(e))
except AccessDenied as e: except AccessDenied as e:
return http.HttpResponseForbidden(unicode(e)) return http.HttpResponseForbidden(unicode(e))
except NotFound as e: except NotFound as e:
return http.Http404(unicode(e)) return http.Http404(unicode(e))
except HandlerError as e:
return http.HttpResponseBadRequest(unicode(e))
except Exception as e: except Exception as e:
logger.exception('Error processing request') logger.exception('Error processing request')
return http.HttpResponseServerError(unicode(e)) return http.HttpResponseServerError(unicode(e))

View File

@ -32,7 +32,6 @@
''' '''
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib.sessions.backends.db import SessionStore from django.contrib.sessions.backends.db import SessionStore
from django.utils.translation import activate
from django.conf import settings from django.conf import settings
from uds.core.util.Config import GlobalConfig from uds.core.util.Config import GlobalConfig
@ -52,6 +51,9 @@ class NotFound(HandlerError):
class AccessDenied(HandlerError): class AccessDenied(HandlerError):
pass pass
class RequestError(HandlerError):
pass
class Handler(object): class Handler(object):
raw = False # If true, Handler will return directly an HttpResponse Object raw = False # If true, Handler will return directly an HttpResponse Object
name = None # If name is not used, name will be the class name in lower case name = None # If name is not used, name will be the class name in lower case

View File

@ -32,7 +32,7 @@
''' '''
from __future__ import unicode_literals from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _, ugettext
from uds.models import Transport from uds.models import Transport
from uds.core.transports import factory from uds.core.transports import factory
@ -47,6 +47,7 @@ logger = logging.getLogger(__name__)
class Transports(ModelHandlerMixin, Handler): class Transports(ModelHandlerMixin, Handler):
model = Transport model = Transport
save_fields = ['name', 'comments', 'priority', 'nets_positive']
def item_as_dict(self, item): def item_as_dict(self, item):
type_ = item.getType() type_ = item.getType()
@ -59,15 +60,26 @@ class Transports(ModelHandlerMixin, Handler):
'type': type_.type(), 'type': type_.type(),
} }
class Types(ModelTypeHandlerMixin, Handler): class Types(ModelTypeHandlerMixin, Handler):
path = 'transports' path = 'transports'
has_comments = True
def enum_types(self): def enum_types(self):
return factory().providers().values() return factory().providers().values()
def getGui(self, type_): def getGui(self, type_):
try: try:
return factory().lookup(type_).guiDescription() return self.addField(self.addDefaultFields(factory().lookup(type_).guiDescription(), ['name', 'comments']), {
'name': 'priority',
'required': True,
'value': '1',
'label': ugettext('Priority'),
'tooltip': ugettext('Priority of this transport'),
'type': 'numeric',
'order': 100, # At end
})
except: except:
raise NotFound('type not found') raise NotFound('type not found')
@ -78,5 +90,6 @@ class TableInfo(ModelTableHandlerMixin, Handler):
fields = [ fields = [
{ 'name': {'title': _('Name'), 'visible': True, 'type': 'iconType' } }, { 'name': {'title': _('Name'), 'visible': True, 'type': 'iconType' } },
{ 'comments': {'title': _('Comments')}}, { 'comments': {'title': _('Comments')}},
{ 'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '6em' }},
{ 'deployed_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}} { 'deployed_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}}
] ]

View File

@ -32,7 +32,7 @@
''' '''
from __future__ import unicode_literals from __future__ import unicode_literals
from handlers import NotFound from handlers import NotFound, RequestError
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
import logging import logging
@ -65,6 +65,8 @@ class ModelHandlerMixin(object):
needs_staff = True needs_staff = True
detail = None # Dictionary containing detail routing detail = None # Dictionary containing detail routing
model = None model = None
save_fields = []
def item_as_dict(self, item): def item_as_dict(self, item):
pass pass
@ -118,6 +120,37 @@ class ModelHandlerMixin(object):
except: except:
raise NotFound('item not found') raise NotFound('item not found')
def put(self):
logger.debug('method PUT for {0}, {1}'.format(self.__class__.__name__, self._args))
args = {}
try:
for key in self.save_fields:
args[key] = self._params[key]
del self._params[key]
except KeyError as e:
raise RequestError('needed parameter not found in data {0}'.format(unicode(e)))
try:
item = self.model.objects.create(**args);
except: # Duplicate key probably
raise RequestError('Element already exists (duplicate key error)')
try:
if self._params.has_key('data_type'): # Needs to store instance
item.data_type = self._params['data_type']
item.data = item.getInstance(self._params).serialize()
item.save()
except Exception as e:
item.delete() # Remove pre-saved element
raise RequestError(unicode(e))
return {'id': item.id }
def delete(self):
logger.debug('method DELETE for {0}, {1}'.format(self.__class__.__name__, self._args))
if len(self._args) != 1:
raise RequestError('Delete need an argument')
return 'deleted'
class ModelTypeHandlerMixin(object): class ModelTypeHandlerMixin(object):
''' '''
As With models, a lot of UDS model contains info about its class. As With models, a lot of UDS model contains info about its class.
@ -125,6 +158,7 @@ class ModelTypeHandlerMixin(object):
''' '''
authenticated = True authenticated = True
needs_staff = True needs_staff = True
has_comments = False
def enum_types(self): def enum_types(self):
pass pass
@ -140,6 +174,46 @@ class ModelTypeHandlerMixin(object):
for type_ in self.enum_types(): for type_ in self.enum_types():
yield self.type_as_dict(type_) yield self.type_as_dict(type_)
def addField(self, gui, field):
gui.append({
'name': field.get('name', ''),
'value': '',
'gui': {
'required': field.get('required', False),
'defvalue': field.get('value', ''),
'value': field.get('value', ''),
'label': field.get('label', ''),
'length': field.get('length', 128),
'multiline': field.get('multiline', 0),
'tooltip': field.get('tooltip', ''),
'rdonly': field.get('rdonly', False),
'type': field.get('type', 'text'),
'order': field.get('order', 0),
'values': field.get('values', [])
}
})
return gui
def addDefaultFields(self, gui, flds):
if 'name' in flds:
self.addField(gui, {
'name': 'name',
'required': True,
'label': _('Name'),
'tooltip': _('Name of this element'),
'order': -2,
})
# And maybe comments (only if model has this field)
if 'comments' in flds:
self.addField(gui, {
'name': 'comments',
'label': _('Comments'),
'tooltip': _('Comments for this element'),
'length': 256,
'order': -1,
})
return gui
def get(self): def get(self):
logger.debug(self._args) logger.debug(self._args)
nArgs = len(self._args) nArgs = len(self._args)
@ -162,41 +236,6 @@ class ModelTypeHandlerMixin(object):
if self._args[1] == 'gui': if self._args[1] == 'gui':
gui = self.getGui(self._args[0]) gui = self.getGui(self._args[0])
# Add name default description, at top of form # Add name default description, at top of form
gui.append({
'name': 'name',
'value':'',
'gui': {
'required':True,
'defvalue':'',
'value':'',
'label': _('Name'),
'length': 128,
'multiline': 0,
'tooltip': _('Name of this element'),
'rdonly': False,
'type': 'text',
'order': -2
}
})
# And comments
gui.append({
'name': 'comments',
'value':'',
'gui': {
'required':False,
'defvalue':'',
'value':'',
'label': _('Comments'),
'length': 256,
'multiline': 0,
'tooltip': _('Comments for this element'),
'rdonly': False,
'type': 'text',
'order': -1
}
})
logger.debug("GUI: {0}".format(gui)) logger.debug("GUI: {0}".format(gui))
return sorted(gui, key=lambda f: f['gui']['order']); return sorted(gui, key=lambda f: f['gui']['order']);

View File

@ -125,7 +125,9 @@ class gui(object):
Returns: Returns:
True if the string is "true" (case insensitive), False else. True if the string is "true" (case insensitive), False else.
''' '''
if str_.lower() == gui.TRUE: if isinstance(str_, bool):
return str_
if unicode(str_).lower() == gui.TRUE:
return True return True
return False return False

View File

@ -40,9 +40,15 @@
} }
}; };
// Default fail function
api.defaultFail = function(jqXHR, textStatus, errorThrown) {
api.doLog(jqXHR, ', ', textStatus, ', ', errorThrown);
};
api.getJson = function(path, options) { api.getJson = function(path, options) {
options = options || {}; options = options || {};
var success_fnc = options.success || function(){}; var success_fnc = options.success || function(){};
var fail_fnc = options.fail || api.defaultFail;
var url = api.url_for(path); var url = api.url_for(path);
api.doLog('Ajax GET Json for "' + url + '"'); api.doLog('Ajax GET Json for "' + url + '"');
@ -51,10 +57,41 @@
type : "GET", type : "GET",
dataType : "json", dataType : "json",
success : function(data) { success : function(data) {
api.doLog('Success on "' + url + '".'); api.doLog('Success on GET "' + url + '".');
api.doLog('Received ' + JSON.stringify(data)); api.doLog('Received ', data);
success_fnc(data); success_fnc(data);
}, },
error: function(jqXHR, textStatus, errorThrown) {
api.doLog('Error on GET "' + url + '". ', textStatus, ', ', errorThrown);
fail_fnc(jqXHR, textStatus, errorThrown);
},
beforeSend : function(request) {
request.setRequestHeader(api.config.auth_header, api.config.token);
},
});
};
api.putJson = function(path, data, options) {
options = options || {};
var success_fnc = options.success || function(){};
var fail_fnc = options.fail || api.defaultFail;
var url = api.url_for(path);
api.doLog('Ajax PUT Json for "' + url + '"');
$.ajax({
url : url,
type : "PUT",
dataType : "json",
data: JSON.stringify(data),
success: function(data) {
api.doLog('Success on PUT "' + url + '".');
api.doLog('Received ', data);
success_fnc(data);
},
error: function(jqXHR, textStatus, errorThrown) {
api.doLog('Error on PUT "' + url + '". ', textStatus, ', ', errorThrown);
fail_fnc(jqXHR, textStatus, errorThrown);
},
beforeSend : function(request) { beforeSend : function(request) {
request.setRequestHeader(api.config.auth_header, api.config.token); request.setRequestHeader(api.config.auth_header, api.config.token);
}, },
@ -62,7 +99,7 @@
}; };
// Public attributes // Public attributes
api.debug = false; api.debug = true;
}(window.api = window.api || {}, jQuery)); }(window.api = window.api || {}, jQuery));
@ -104,6 +141,7 @@ function BasicModelRest(path, options) {
// Requests paths // Requests paths
this.path = path; this.path = path;
this.getPath = options.getPath || path; this.getPath = options.getPath || path;
this.putPath = options.putPath || path;
this.typesPath = options.typesPath || (path + '/types'); this.typesPath = options.typesPath || (path + '/types');
this.tableInfoPath = options.tableInfoPath || (path + '/tableinfo'); this.tableInfoPath = options.tableInfoPath || (path + '/tableinfo');
this.cache = api.cache('bmr'+path); this.cache = api.cache('bmr'+path);
@ -117,7 +155,8 @@ BasicModelRest.prototype = {
_requestPath: function(path, options) { _requestPath: function(path, options) {
"use strict"; "use strict";
options = options || {}; options = options || {};
var success_fnc = options.success || function(){api.doLog('success not provided for '+path);}; var success_fnc = options.success || function(){api.doLog('success function not provided for '+path);};
var fail_fnc = options.fail;
var cacheKey = options.cacheKey || path; var cacheKey = options.cacheKey || path;
if( path == '.' ) { if( path == '.' ) {
@ -136,10 +175,11 @@ BasicModelRest.prototype = {
} }
success_fnc(data); success_fnc(data);
}, },
fail: fail_fnc,
}); });
} }
}, },
get : function(success_fnc, options) { get: function(options) {
"use strict"; "use strict";
options = options || {}; options = options || {};
@ -148,61 +188,103 @@ BasicModelRest.prototype = {
path += '/' + options.id; path += '/' + options.id;
return this._requestPath(path, { return this._requestPath(path, {
cacheKey: '.', // Right now, do not cache any "get" method cacheKey: '.', // Right now, do not cache any "get" method
success: success_fnc, success: options.success,
fail: options.fail
}); });
}, },
list: function(success_fnc, options) { // This is "almost" an alias for get list: function(success_fnc, fail_fnc) { // This is "almost" an alias for get
"use strict"; "use strict";
options = options || {}; return this.get({
return this.get(success_fnc, {
id: '', id: '',
success: success_fnc,
fail: fail_fnc
}); });
}, },
overview: function(success_fnc, options) { overview: function(success_fnc, fail_fnc) {
"use strict"; "use strict";
options = options || {}; return this.get({
return this.get(success_fnc, {
id: 'overview', id: 'overview',
success: success_fnc,
fail: fail_fnc
}); });
}, },
item: function(itemId, success_fnc, options) { item: function(itemId, success_fnc, fail_fnc) {
"use strict"; "use strict";
options = options || {}; return this.get({
return this.get(success_fnc, {
id: itemId, id: itemId,
success: success_fnc,
fail: fail_fnc
}); });
}, },
types : function(success_fnc, options) {
// -------------
// Put methods
// -------------
put: function(data, options) {
"use strict"; "use strict";
options = options || {}; options = options || {};
var path = this.putPath;
if ( options.id )
path += '/' + options.id;
api.putJson(path, data, {
success: options.success,
fail: options.fail
});
},
create: function(data, success_fnc, fail_fnc) {
"use strict";
return this.put(data, {
success: success_fnc,
fail: fail_fnc
});
},
save: function(data, success_fnc, fail_fnc) {
"use strict";
return this.put(data, {
id: data.id,
success: success_fnc,
fail: fail_fnc
});
},
// --------------
// Types methods
// --------------
types : function(success_fnc, fail_fnc) {
"use strict";
return this._requestPath(this.typesPath, { return this._requestPath(this.typesPath, {
cacheKey: 'type', cacheKey: 'type',
success: success_fnc, success: success_fnc,
}); });
}, },
gui: function(typeName, success_fnc, options) { gui: function(typeName, success_fnc, fail_fnc) {
// GUI returns a dict, that contains: // GUI returns a dict, that contains:
// name: Name of the field // name: Name of the field
// value: value of the field (selected element in choice, text for inputs, etc....) // value: value of the field (selected element in choice, text for inputs, etc....)
// gui: Description of the field (type, value or values, defvalue, .... // gui: Description of the field (type, value or values, defvalue, ....
"use strict"; "use strict";
options = options || {};
var path = [this.typesPath, typeName, 'gui'].join('/'); var path = [this.typesPath, typeName, 'gui'].join('/');
return this._requestPath(path, { return this._requestPath(path, {
cacheKey: typeName + '-gui', cacheKey: typeName + '-gui',
success: success_fnc, success: success_fnc,
fail: fail_fnc,
}); });
}, },
tableInfo : function(success_fnc, options) { tableInfo : function(success_fnc, fail_fnc) {
"use strict"; "use strict";
options = options || {};
success_fnc = success_fnc || function(){api.doLog('success not provided for tableInfo');}; success_fnc = success_fnc || function(){api.doLog('success not provided for tableInfo');};
var path = this.tableInfoPath; var path = this.tableInfoPath;
this._requestPath(path, { this._requestPath(path, {
success: success_fnc, success: success_fnc,
fail: fail_fnc,
}); });
}, },

View File

@ -153,12 +153,20 @@ gui.connectivity.link = function(event) {
}); });
}); });
}, },
onNew: function(type) { onNew: function(type, table, refreshFnc) {
gui.connectivity.transports.rest.gui(type, function(itemGui) { gui.connectivity.transports.rest.gui(type, function(itemGui) {
var form = gui.fields(itemGui); var form = gui.fields(itemGui);
gui.launchModalForm(gettext('New transport'), form, function(form_selector) { gui.launchModalForm(gettext('New transport'), form, function(form_selector, closeFnc) {
var fields = gui.fields.read(form_selector); var fields = gui.fields.read(form_selector);
return false; // Append "own" fields, in this case data_type
fields.data_type = type;
fields.nets_positive = false;
gui.connectivity.transports.rest.create(fields, function(data) { // Success on put
closeFnc();
refreshFnc();
}, function(jqXHR, textStatus, errorThrown) { // fail on put
gui.launchModal(gettext('Error creating transport'), jqXHR.responseText, ' ');
});
}); });
}); });
}, },

View File

@ -0,0 +1,428 @@
/* jshint strict: true */
function BasicGuiElement(name) {
"use strict";
this.name = name;
}
function GuiElement(restItem, name) {
"use strict";
this.rest = restItem;
this.name = name;
this.types = {};
this.init();
}
// all gui elements has, at least, name && type
// Types must include, at least: type, icon
GuiElement.prototype = {
init : function() {
"use strict";
gui.doLog('Initializing ' + this.name);
var $this = this;
this.rest.types(function(data) {
var styles = '';
$.each(data, function(index, value) {
var className = $this.name + '-' + value.type;
$this.types[value.type] = {
css : className,
name : value.name || '',
description : value.description || ''
};
gui.doLog('Creating style for ' + className);
var style = '.' + className + ' { display:inline-block; background: url(data:image/png;base64,' +
value.icon + '); ' + 'width: 16px; height: 16px; vertical-align: middle; } ';
styles += style;
});
if (styles !== '') {
styles = '<style media="screen">' + styles + '</style>';
$(styles).appendTo('head');
}
});
},
// Options: dictionary
// container: container ID of parent for the table. If undefined, table will be appended to workspace
// buttons: array of visible buttons (strings), valid are [ 'new', 'edit', 'refresh', 'delete', 'xls' ],
// rowSelect: type of allowed row selection, valid values are 'single' and 'multi'
// scrollToTable: if True, will scroll page to show table
//
// onLoad: Event (function). If defined, will be invoked when table is fully loaded.
// Receives 1 parameter, that is the gui element (GuiElement) used to render table
// onRowSelect: Event (function). If defined, will be invoked when a row of table is selected
// Receives 3 parameters:
// 1.- the array of selected items data (objects, as got from api...get)
// 2.- the DataTable that raised the event
// 3.- the DataTableTools that raised the event
// onRowDeselect: Event (function). If defined, will be invoked when a row of table is deselected
// Receives 3 parameters:
// 1.- the array of selected items data (objects, as got from api...get)
// 2.- the DataTable that raised the event
// onNew: Event (function). If defined, will be invoked when "new" button is pressed
// Receives 4 parameters:
// 1.- the selected item data (single object, as got from api...get)
// 2.- the event that fired this (new, delete, edit, ..)
// 3.- the DataTable that raised the event
// onEdit: Event (function). If defined, will be invoked when "edit" button is pressed
// Receives 4 parameters:
// 1.- the selected item data (single object, as got from api...get)
// 2.- the event that fired this (new, delete, edit, ..)
// 3.- the DataTable that raised the event
// onDelete: Event (function). If defined, will be invoked when "delete" button is pressed
// Receives 4 parameters:
// 1.- the selected item data (single object, as got from api...get)
// 2.- the event that fired this (new, delete, edit, ..)
// 4.- the DataTable that raised the event
table : function(options) {
"use strict";
gui.doLog('Types: ', this.types);
options = options || {};
gui.doLog('Composing table for ' + this.name);
var tableId = this.name + '-table';
var $this = this; // Store this for child functions
// Empty cells transform
var renderEmptyCell = function(data) {
if( data === '' )
return '-';
return data;
};
// Datetime renderer (with specified format)
var renderDate = function(format) {
return function(data, type, full) {
return api.tools.strftime(format, new Date(data*1000));
};
};
// Icon renderer, based on type (created on init methods in styles)
var renderTypeIcon = function(data, type, value){
if( type == 'display' ) {
var css = $this.types[value.type].css;
return '<span class="' + css + '"></span> ' + renderEmptyCell(data);
} else {
return renderEmptyCell(data);
}
};
// Custom icon renderer, in fact span with defined class
var renderIcon = function(icon) {
return function(data, type, full) {
if( type == 'display' ) {
return '<span class="' + icon + '"></span> ' + renderEmptyCell(data);
} else {
return renderEmptyCell(data);
}
};
};
// Text transformation, dictionary based
var renderTextTransform = function(dict) {
return function(data, type, full) {
return dict[data] || renderEmptyCell('');
};
};
this.rest.tableInfo(function(data) {
var title = data.title;
var columns = [];
$.each(data.fields, function(index, value) {
for ( var v in value) {
var opts = value[v];
var column = {
mData : v,
};
column.sTitle = opts.title;
column.mRender = renderEmptyCell;
if (opts.width)
column.sWidth = opts.width;
column.bVisible = opts.visible === undefined ? true : opts.visible;
if (opts.sortable !== undefined)
column.bSortable = opts.sortable;
if (opts.searchable !== undefined)
column.bSearchable = opts.searchable;
if (opts.type && column.bVisible ) {
switch(opts.type) {
case 'date':
column.sType = 'date';
column.mRender = renderDate(api.tools.djangoFormat(get_format('SHORT_DATE_FORMAT')));
break;
case 'datetime':
column.sType = 'date';
column.mRender = renderDate(api.tools.djangoFormat(get_format('SHORT_DATETIME_FORMAT')));
break;
case 'time':
column.mRender = renderDate(api.tools.djangoFormat(get_format('TIME_FORMAT')));
break;
case 'iconType':
//columnt.sType = 'html'; // html is default, so this is not needed
column.mRender = renderTypeIcon;
break;
case 'icon':
if( opts.icon !== undefined ) {
column.mRender = renderIcon(opts.icon);
}
break;
case 'dict':
if( opts.dict !== undefined ) {
column.mRender = renderTextTransform(opts.dict);
}
break;
default:
column.sType = opts.type;
}
}
columns.push(column);
}
});
// Responsive style for tables, using tables.css and this code generates the "titles" for vertical display on small sizes
$('#style-' + tableId).remove(); // Remove existing style for table before adding new one
$(api.templates.evaluate('tmpl_responsive_table', {
tableId: tableId,
columns: columns,
})).appendTo('head');
$this.rest.overview(function(data) {
var table = gui.table(title, tableId);
if (options.container === undefined) {
gui.appendToWorkspace('<div class="row"><div class="col-lg-12">' + table.text + '</div></div>');
} else {
$('#' + options.container).empty();
$('#' + options.container).append(table.text);
}
// What execute on refresh button push
var onRefresh = options.onRefresh || function(){};
var refreshFnc = function() {
// Refreshes table content
var tbl = $('#' + tableId).dataTable();
// Clears selection first
TableTools.fnGetInstance(tableId).fnSelectNone();
if( data.length > 1000 )
api.tools.blockUI();
$this.rest.overview(function(data) {
/*$(btn).removeClass('disabled').width('').html(saved);*/
setTimeout( function() {
tbl.fnClearTable();
tbl.fnAddData(data);
onRefresh($this);
api.tools.unblockUI();
}, 0);
});
return false; // This may be used on button or href, better disable execution of it
};
var btns = [];
if (options.buttons) {
var clickHandlerFor = function(handler, action, newHandler) {
var handleFnc = handler || function(val, action, tbl) {gui.doLog('Default handler called for ', action);};
return function(btn) {
var tbl = $('#' + tableId).dataTable();
var val = this.fnGetSelectedData()[0];
setTimeout(function() {
if( newHandler ) {
handleFnc(action, tbl, refreshFnc);
} else {
handleFnc(val, action, tbl, refreshFnc);
}
}, 0);
};
};
// methods for buttons on row select
var editSelected = function(btn, obj, node) {
var sel = this.fnGetSelectedData();
if (sel.length == 1) {
$(btn).removeClass('disabled').addClass('btn3d-success');
} else {
$(btn).removeClass('btn3d-success').addClass('disabled');
}
};
var deleteSelected = function(btn, obj, node) {
var sel = this.fnGetSelectedData();
if (sel.length > 0) {
$(btn).removeClass('disabled').addClass('btn3d-warning');
} else {
$(btn).removeClass('btn3d-warning').addClass('disabled');
}
};
$.each(options.buttons, function(index, value) {
var btn;
switch (value) {
case 'new':
if(Object.keys($this.types).length === 0) {
btn = {
"sExtends" : "text",
"sButtonText" : gui.config.dataTableButtons['new'].text,
"fnClick" : clickHandlerFor(options.onNew, 'new'),
"sButtonClass" : gui.config.dataTableButtons['new'].css,
};
} else {
// This table has "types, so we create a dropdown with Types
var newButtons = [];
// Order buttons by name, much more easy for users... :-)
var order = [];
$.each($this.types, function(k, v){
order.push({
type: k,
css: v.css,
name: v.name,
description: v.description,
});
});
$.each(order.sort(function(a,b){return a.name.localeCompare(b.name);}), function(i, val){
newButtons.push({
"sExtends" : "text",
"sButtonText" : '<span class="' + val.css + '"></span> <span data-toggle="tooltip" data-title="' + val.description + '">' + val.name + '</span>',
"fnClick" : clickHandlerFor(options.onNew, val.type, true),
});
});
btn = {
"sExtends" : "collection",
"aButtons": newButtons,
"sButtonText" : gui.config.dataTableButtons['new'].text,
"sButtonClass" : gui.config.dataTableButtons['new'].css,
};
}
break;
case 'edit':
btn = {
"sExtends" : "text",
"sButtonText" : gui.config.dataTableButtons.edit.text,
"fnSelect" : editSelected,
"fnClick" : clickHandlerFor(options.onEdit, 'edit'),
"sButtonClass" : gui.config.dataTableButtons.edit.css,
};
break;
case 'delete':
btn = {
"sExtends" : "text",
"sButtonText" : gui.config.dataTableButtons['delete'].text,
"fnSelect" : deleteSelected,
"fnClick" : clickHandlerFor(options.onDelete, 'delete'),
"sButtonClass" : gui.config.dataTableButtons['delete'].css,
};
break;
case 'refresh':
btn = {
"sExtends" : "text",
"sButtonText" : gui.config.dataTableButtons.refresh.text,
"fnClick" : refreshFnc,
"sButtonClass" : gui.config.dataTableButtons.refresh.css,
};
break;
case 'xls':
btn = {
"sExtends" : "text",
"sButtonText" : gui.config.dataTableButtons.xls.text,
"fnClick" : function(){
api.templates.get('spreadsheet', function(tmpl) {
var styles = { 'bold': 's21', };
var uri = 'data:application/vnd.ms-excel;base64,',
base64 = function(s) { return window.btoa(unescape(encodeURIComponent(s))); };
var headings = [], rows = [];
$.each(columns, function(index, heading){
if( heading.bVisible === false ) {
return;
}
headings.push(api.spreadsheet.cell(heading.sTitle, 'String', styles.bold));
});
rows.push(api.spreadsheet.row(headings));
$.each(data, function(index, row) {
var cells = [];
$.each(columns, function(index, col){
if( col.bVisible === false ) {
return;
}
var type = col.sType == 'numeric' ? 'Number':'String';
cells.push(api.spreadsheet.cell(row[col.mData], type));
});
rows.push(api.spreadsheet.row(cells));
});
var ctx = {
creation_date: (new Date()).toISOString(),
worksheet: title,
columns_count: headings.length,
rows_count: rows.length,
rows: rows.join('\n')
};
setTimeout( function() {
saveAs(new Blob([api.templates.evaluate(tmpl, ctx)],
{type: 'application/vnd.ms-excel'} ), title + '.xls');
}, 20);
});
},
"sButtonClass" : gui.config.dataTableButtons.xls.css,
};
}
if(btn) {
btns.push(btn);
}
});
}
// Initializes oTableTools
var oTableTools = {
"aButtons" : btns
};
// Type of row selection
if (options.rowSelect) {
oTableTools.sRowSelect = options.rowSelect;
}
if (options.onRowSelect) {
var rowSelectedFnc = options.onRowSelect;
oTableTools.fnRowSelected = function() {
rowSelectedFnc(this.fnGetSelectedData(), $('#' + tableId).dataTable(), this);
};
}
if (options.onRowDeselect) {
var rowDeselectedFnc = options.onRowDeselect;
oTableTools.fnRowDeselected = function() {
rowDeselectedFnc(this.fnGetSelectedData(), $('#' + tableId).dataTable(), this);
};
}
$('#' + tableId).dataTable({
"aaData" : data,
"aoColumns" : columns,
"oLanguage" : gui.config.dataTablesLanguage,
"oTableTools" : oTableTools,
// First is upper row,
// second row is lower
// (pagination) row
"sDom" : "<'row'<'col-xs-8'T><'col-xs-4'f>r>t<'row'<'col-xs-5'i><'col-xs-7'p>>",
});
// Fix 3dbuttons
api.tools.fix3dButtons('#' + tableId + '_wrapper .btn-group-3d');
// Fix form
$('#' + tableId + '_filter input').addClass('form-control');
// Add refresh action to panel
$(table.refreshSelector).click(refreshFnc);
// Add tooltips to "new" buttons
$('.DTTT_dropdown [data-toggle="tooltip"]').tooltip({
container:'body',
delay: { show: 1000, hide: 100},
placement: 'auto right',
});
if (options.scrollToTable === true ) {
var tableTop = $('#' + tableId).offset().top;
$('html, body').scrollTop(tableTop);
}
// if table rendered event
if( options.onLoad ) {
options.onLoad($this);
}
});
});
return '#' + tableId;
}
};

View File

@ -97,11 +97,24 @@
}); });
}; };
gui.modal = function(id, title, content) { gui.modal = function(id, title, content, actionButton, closeButton) {
return api.templates.evaluate('tmpl_modal', { return api.templates.evaluate('tmpl_modal', {
id: id, id: id,
title: title, title: title,
content: content content: content,
button1: closeButton,
button2: actionButton
});
};
gui.launchModal = function(title, content, actionButton, closeButton) {
var id = Math.random().toString().split('.')[1]; // Get a random ID for this modal
gui.appendToWorkspace(gui.modal(id, title, content, actionButton, closeButton));
id = '#' + id; // for jQuery
$(id).modal()
.on('hidden.bs.modal', function () {
$(id).remove();
}); });
}; };
@ -143,10 +156,12 @@
if( !$form.valid() ) if( !$form.valid() )
return; return;
if( onSuccess ) { if( onSuccess ) {
if( onSuccess(id + ' form') === false ) // Some error may have ocurred, do not close dialog onSuccess(id + ' form', function(){$(id).modal('hide');}); // Delegate close to to onSuccess
return; return;
} } else {
$(id).modal('hide'); $(id).modal('hide');
}
}); });
// Launch modal // Launch modal
@ -156,6 +171,16 @@
}); });
}; };
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.clearWorkspace = function() { gui.clearWorkspace = function() {
$('#content').empty(); $('#content').empty();
$('#minimized').empty(); $('#minimized').empty();
@ -233,432 +258,3 @@
gui.debug = true; gui.debug = true;
}(window.gui = window.gui || {}, jQuery)); }(window.gui = window.gui || {}, jQuery));
function BasicGuiElement(name) {
"use strict";
this.name = name;
}
function GuiElement(restItem, name) {
"use strict";
this.rest = restItem;
this.name = name;
this.types = {};
this.init();
}
// all gui elements has, at least, name && type
// Types must include, at least: type, icon
GuiElement.prototype = {
init : function() {
"use strict";
gui.doLog('Initializing ' + this.name);
var $this = this;
this.rest.types(function(data) {
var styles = '';
$.each(data, function(index, value) {
var className = $this.name + '-' + value.type;
$this.types[value.type] = {
css : className,
name : value.name || '',
description : value.description || ''
};
gui.doLog('Creating style for ' + className);
var style = '.' + className + ' { display:inline-block; background: url(data:image/png;base64,' +
value.icon + '); ' + 'width: 16px; height: 16px; vertical-align: middle; } ';
styles += style;
});
if (styles !== '') {
styles = '<style media="screen">' + styles + '</style>';
$(styles).appendTo('head');
}
});
},
// Options: dictionary
// container: container ID of parent for the table. If undefined, table will be appended to workspace
// buttons: array of visible buttons (strings), valid are [ 'new', 'edit', 'refresh', 'delete', 'xls' ],
// rowSelect: type of allowed row selection, valid values are 'single' and 'multi'
// scrollToTable: if True, will scroll page to show table
//
// onLoad: Event (function). If defined, will be invoked when table is fully loaded.
// Receives 1 parameter, that is the gui element (GuiElement) used to render table
// onRowSelect: Event (function). If defined, will be invoked when a row of table is selected
// Receives 3 parameters:
// 1.- the array of selected items data (objects, as got from api...get)
// 2.- the DataTable that raised the event
// 3.- the DataTableTools that raised the event
// onRowDeselect: Event (function). If defined, will be invoked when a row of table is deselected
// Receives 3 parameters:
// 1.- the array of selected items data (objects, as got from api...get)
// 2.- the DataTable that raised the event
// onNew: Event (function). If defined, will be invoked when "new" button is pressed
// Receives 4 parameters:
// 1.- the selected item data (single object, as got from api...get)
// 2.- the event that fired this (new, delete, edit, ..)
// 3.- the DataTable that raised the event
// onEdit: Event (function). If defined, will be invoked when "edit" button is pressed
// Receives 4 parameters:
// 1.- the selected item data (single object, as got from api...get)
// 2.- the event that fired this (new, delete, edit, ..)
// 3.- the DataTable that raised the event
// onDelete: Event (function). If defined, will be invoked when "delete" button is pressed
// Receives 4 parameters:
// 1.- the selected item data (single object, as got from api...get)
// 2.- the event that fired this (new, delete, edit, ..)
// 4.- the DataTable that raised the event
table : function(options) {
"use strict";
gui.doLog('Types: ', this.types);
options = options || {};
gui.doLog('Composing table for ' + this.name);
var tableId = this.name + '-table';
var $this = this; // Store this for child functions
// Empty cells transform
var renderEmptyCell = function(data) {
if( data === '' )
return '-';
return data;
};
// Datetime renderer (with specified format)
var renderDate = function(format) {
return function(data, type, full) {
return api.tools.strftime(format, new Date(data*1000));
};
};
// Icon renderer, based on type (created on init methods in styles)
var renderTypeIcon = function(data, type, value){
if( type == 'display' ) {
var css = $this.types[value.type].css;
return '<span class="' + css + '"></span> ' + renderEmptyCell(data);
} else {
return renderEmptyCell(data);
}
};
// Custom icon renderer, in fact span with defined class
var renderIcon = function(icon) {
return function(data, type, full) {
if( type == 'display' ) {
return '<span class="' + icon + '"></span> ' + renderEmptyCell(data);
} else {
return renderEmptyCell(data);
}
};
};
// Text transformation, dictionary based
var renderTextTransform = function(dict) {
return function(data, type, full) {
return dict[data] || renderEmptyCell('');
};
};
this.rest.tableInfo(function(data) {
var title = data.title;
var columns = [];
$.each(data.fields, function(index, value) {
for ( var v in value) {
var opts = value[v];
var column = {
mData : v,
};
column.sTitle = opts.title;
column.mRender = renderEmptyCell;
if (opts.width)
column.sWidth = opts.width;
column.bVisible = opts.visible === undefined ? true : opts.visible;
if (opts.sortable !== undefined)
column.bSortable = opts.sortable;
if (opts.searchable !== undefined)
column.bSearchable = opts.searchable;
if (opts.type && column.bVisible ) {
switch(opts.type) {
case 'date':
column.sType = 'date';
column.mRender = renderDate(api.tools.djangoFormat(get_format('SHORT_DATE_FORMAT')));
break;
case 'datetime':
column.sType = 'date';
column.mRender = renderDate(api.tools.djangoFormat(get_format('SHORT_DATETIME_FORMAT')));
break;
case 'time':
column.mRender = renderDate(api.tools.djangoFormat(get_format('TIME_FORMAT')));
break;
case 'iconType':
//columnt.sType = 'html'; // html is default, so this is not needed
column.mRender = renderTypeIcon;
break;
case 'icon':
if( opts.icon !== undefined ) {
column.mRender = renderIcon(opts.icon);
}
break;
case 'dict':
if( opts.dict !== undefined ) {
column.mRender = renderTextTransform(opts.dict);
}
break;
default:
column.sType = opts.type;
}
}
columns.push(column);
}
});
// Responsive style for tables, using tables.css and this code generates the "titles" for vertical display on small sizes
$('#style-' + tableId).remove(); // Remove existing style for table before adding new one
$(api.templates.evaluate('tmpl_responsive_table', {
tableId: tableId,
columns: columns,
})).appendTo('head');
$this.rest.overview(function(data) {
var table = gui.table(title, tableId);
if (options.container === undefined) {
gui.appendToWorkspace('<div class="row"><div class="col-lg-12">' + table.text + '</div></div>');
} else {
$('#' + options.container).empty();
$('#' + options.container).append(table.text);
}
// What execute on refresh button push
var onRefresh = options.onRefresh || function(){};
var refreshFnc = function() {
// Refreshes table content
var tbl = $('#' + tableId).dataTable();
// Clears selection first
TableTools.fnGetInstance(tableId).fnSelectNone();
if( data.length > 1000 )
api.tools.blockUI();
$this.rest.overview(function(data) {
/*$(btn).removeClass('disabled').width('').html(saved);*/
setTimeout( function() {
tbl.fnClearTable();
tbl.fnAddData(data);
onRefresh($this);
api.tools.unblockUI();
}, 0);
});
return false; // This may be used on button or href, better disable execution of it
};
var btns = [];
if (options.buttons) {
var clickHandlerFor = function(handler, action, newHandler) {
var handleFnc = handler || function(val, action, tbl) {gui.doLog('Default handler called for ', action);};
return function(btn) {
var tbl = $('#' + tableId).dataTable();
var val = this.fnGetSelectedData()[0];
setTimeout(function() {
if( newHandler ) {
if( handleFnc(action, tbl) === true ) // Reload table?
refreshFnc();
} else {
if( handleFnc(val, action, tbl) === true ) // Reload table?
refreshFnc();
}
}, 0);
};
};
// methods for buttons on row select
var editSelected = function(btn, obj, node) {
var sel = this.fnGetSelectedData();
if (sel.length == 1) {
$(btn).removeClass('disabled').addClass('btn3d-success');
} else {
$(btn).removeClass('btn3d-success').addClass('disabled');
}
};
var deleteSelected = function(btn, obj, node) {
var sel = this.fnGetSelectedData();
if (sel.length > 0) {
$(btn).removeClass('disabled').addClass('btn3d-warning');
} else {
$(btn).removeClass('btn3d-warning').addClass('disabled');
}
};
$.each(options.buttons, function(index, value) {
var btn;
switch (value) {
case 'new':
if(Object.keys($this.types).length === 0) {
btn = {
"sExtends" : "text",
"sButtonText" : gui.config.dataTableButtons['new'].text,
"fnClick" : clickHandlerFor(options.onNew, 'new'),
"sButtonClass" : gui.config.dataTableButtons['new'].css,
};
} else {
// This table has "types, so we create a dropdown with Types
var newButtons = [];
// Order buttons by name, much more easy for users... :-)
var order = [];
$.each($this.types, function(k, v){
order.push({
type: k,
css: v.css,
name: v.name,
description: v.description,
});
});
$.each(order.sort(function(a,b){return a.name.localeCompare(b.name);}), function(i, val){
newButtons.push({
"sExtends" : "text",
"sButtonText" : '<span class="' + val.css + '"></span> <span data-toggle="tooltip" data-title="' + val.description + '">' + val.name + '</span>',
"fnClick" : clickHandlerFor(options.onNew, val.type, true),
});
});
btn = {
"sExtends" : "collection",
"aButtons": newButtons,
"sButtonText" : gui.config.dataTableButtons['new'].text,
"sButtonClass" : gui.config.dataTableButtons['new'].css,
};
}
break;
case 'edit':
btn = {
"sExtends" : "text",
"sButtonText" : gui.config.dataTableButtons.edit.text,
"fnSelect" : editSelected,
"fnClick" : clickHandlerFor(options.onEdit, 'edit'),
"sButtonClass" : gui.config.dataTableButtons.edit.css,
};
break;
case 'delete':
btn = {
"sExtends" : "text",
"sButtonText" : gui.config.dataTableButtons['delete'].text,
"fnSelect" : deleteSelected,
"fnClick" : clickHandlerFor(options.onDelete, 'delete'),
"sButtonClass" : gui.config.dataTableButtons['delete'].css,
};
break;
case 'refresh':
btn = {
"sExtends" : "text",
"sButtonText" : gui.config.dataTableButtons.refresh.text,
"fnClick" : refreshFnc,
"sButtonClass" : gui.config.dataTableButtons.refresh.css,
};
break;
case 'xls':
btn = {
"sExtends" : "text",
"sButtonText" : gui.config.dataTableButtons.xls.text,
"fnClick" : function(){
api.templates.get('spreadsheet', function(tmpl) {
var styles = { 'bold': 's21', };
var uri = 'data:application/vnd.ms-excel;base64,',
base64 = function(s) { return window.btoa(unescape(encodeURIComponent(s))); };
var headings = [], rows = [];
$.each(columns, function(index, heading){
if( heading.bVisible === false ) {
return;
}
headings.push(api.spreadsheet.cell(heading.sTitle, 'String', styles.bold));
});
rows.push(api.spreadsheet.row(headings));
$.each(data, function(index, row) {
var cells = [];
$.each(columns, function(index, col){
if( col.bVisible === false ) {
return;
}
var type = col.sType == 'numeric' ? 'Number':'String';
cells.push(api.spreadsheet.cell(row[col.mData], type));
});
rows.push(api.spreadsheet.row(cells));
});
var ctx = {
creation_date: (new Date()).toISOString(),
worksheet: title,
columns_count: headings.length,
rows_count: rows.length,
rows: rows.join('\n')
};
setTimeout( function() {
saveAs(new Blob([api.templates.evaluate(tmpl, ctx)],
{type: 'application/vnd.ms-excel'} ), title + '.xls');
}, 20);
});
},
"sButtonClass" : gui.config.dataTableButtons.xls.css,
};
}
if(btn) {
btns.push(btn);
}
});
}
// Initializes oTableTools
var oTableTools = {
"aButtons" : btns
};
// Type of row selection
if (options.rowSelect) {
oTableTools.sRowSelect = options.rowSelect;
}
if (options.onRowSelect) {
var rowSelectedFnc = options.onRowSelect;
oTableTools.fnRowSelected = function() {
rowSelectedFnc(this.fnGetSelectedData(), $('#' + tableId).dataTable(), this);
};
}
if (options.onRowDeselect) {
var rowDeselectedFnc = options.onRowDeselect;
oTableTools.fnRowDeselected = function() {
rowDeselectedFnc(this.fnGetSelectedData(), $('#' + tableId).dataTable(), this);
};
}
$('#' + tableId).dataTable({
"aaData" : data,
"aoColumns" : columns,
"oLanguage" : gui.config.dataTablesLanguage,
"oTableTools" : oTableTools,
// First is upper row,
// second row is lower
// (pagination) row
"sDom" : "<'row'<'col-xs-8'T><'col-xs-4'f>r>t<'row'<'col-xs-5'i><'col-xs-7'p>>",
});
// Fix 3dbuttons
api.tools.fix3dButtons('#' + tableId + '_wrapper .btn-group-3d');
// Fix form
$('#' + tableId + '_filter input').addClass('form-control');
// Add refresh action to panel
$(table.refreshSelector).click(refreshFnc);
// Add tooltips to "new" buttons
$('.DTTT_dropdown [data-toggle="tooltip"]').tooltip({
container:'body',
delay: { show: 1000, hide: 100},
placement: 'auto right',
});
if (options.scrollToTable === true ) {
var tableTop = $('#' + tableId).offset().top;
$('html, body').scrollTop(tableTop);
}
// if table rendered event
if( options.onLoad ) {
options.onLoad($this);
}
});
});
return '#' + tableId;
}
};

View File

@ -51,7 +51,7 @@
<script src="{% get_static_prefix %}js/bootstrap.min.js"></script> <script src="{% get_static_prefix %}js/bootstrap.min.js"></script>
<script src="{% get_static_prefix %}js/bootstrap-switch.min.js"></script> <script src="{% get_static_prefix %}js/bootstrap-switch.min.js"></script>
<script src="{% get_static_prefix %}js/bootstrap-select.min.js"></script> <script src="{% get_static_prefix %}js/bootstrap-select.min.js"></script>
<script src="{% get_static_prefix %}adm/js/jquery.validate.js"></script> <script src="{% get_static_prefix %}adm/js/jquery.validate.min.js"></script>
<script src="{% get_static_prefix %}adm/js/jquery.blockUI.js"></script> <script src="{% get_static_prefix %}adm/js/jquery.blockUI.js"></script>
<script src="{% get_static_prefix %}adm/js/jquery.dataTables.min.js"></script> <script src="{% get_static_prefix %}adm/js/jquery.dataTables.min.js"></script>
@ -99,15 +99,17 @@
<script src="{% get_static_prefix %}adm/js/gui.js"></script> <script src="{% get_static_prefix %}adm/js/gui.js"></script>
<script src="{% get_static_prefix %}adm/js/gui-fields.js"></script> <script src="{% get_static_prefix %}adm/js/gui-fields.js"></script>
<script src="{% get_static_prefix %}adm/js/gui-element.js"></script>
<!-- user interface management --> <!-- user interface management -->
<script src="{% get_static_prefix %}adm/js/gui-elements.js"></script> <script src="{% get_static_prefix %}adm/js/gui-definition.js"></script>
<script> <script>
$(function() { $(function() {
// Initialize gui // Initialize gui
gui.init(); gui.init();
// set default error function
api.defaultFail = gui.failRequestMessageFnc;
}); });
</script> </script>
{% block js %}{% endblock %} {% block js %}{% endblock %}
@ -116,6 +118,8 @@
<!-- page contents --> <!-- page contents -->
{% js_template 'dashboard' %} {% js_template 'dashboard' %}
{% js_template 'authenticators' %} {% js_template 'authenticators' %}
<!-- utility pages -->
{% js_template 'request_failed' %}
<!-- components --> <!-- components -->
{% js_template 'table' %} {% js_template 'table' %}
{% js_template 'modal' %} {% js_template 'modal' %}

View File

@ -19,7 +19,7 @@
{% verbatim %} {% verbatim %}
{{/ if }} {{/ if }}
{{# if button2 }} {{# if button2 }}
{{{ button1 }}} {{{ button2 }}}
{{ else }} {{ else }}
{% endverbatim %} {% endverbatim %}
<button type="button" class="btn btn-primary button-accept">{% trans 'Save' %}</button> <button type="button" class="btn btn-primary button-accept">{% trans 'Save' %}</button>

View File

@ -0,0 +1,10 @@
{% load i18n %}
<div class="jumbotron text-center">
<h1> {% trans 'Error on request' %}</h1>
<br />
{% verbatim %}<p>{{ error }}</p>{% endverbatim %}
<h5>{% trans 'There was an error requesting data from server, please, try again' %}</h5>
<br />
<a href="#" class="btn btn-lg btn-info lnk-dashboard"><i class="fa fa-dashboard"></i> {% trans "Dashboard" %}</a>
</div>