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

View File

@ -32,7 +32,6 @@
'''
from __future__ import unicode_literals
from django.contrib.sessions.backends.db import SessionStore
from django.utils.translation import activate
from django.conf import settings
from uds.core.util.Config import GlobalConfig
@ -52,6 +51,9 @@ class NotFound(HandlerError):
class AccessDenied(HandlerError):
pass
class RequestError(HandlerError):
pass
class Handler(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

View File

@ -32,7 +32,7 @@
'''
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.core.transports import factory
@ -47,6 +47,7 @@ logger = logging.getLogger(__name__)
class Transports(ModelHandlerMixin, Handler):
model = Transport
save_fields = ['name', 'comments', 'priority', 'nets_positive']
def item_as_dict(self, item):
type_ = item.getType()
@ -58,16 +59,27 @@ class Transports(ModelHandlerMixin, Handler):
'deployed_count': item.deployedServices.count(),
'type': type_.type(),
}
class Types(ModelTypeHandlerMixin, Handler):
path = 'transports'
has_comments = True
def enum_types(self):
return factory().providers().values()
def getGui(self, type_):
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:
raise NotFound('type not found')
@ -78,5 +90,6 @@ class TableInfo(ModelTableHandlerMixin, Handler):
fields = [
{ 'name': {'title': _('Name'), 'visible': True, 'type': 'iconType' } },
{ 'comments': {'title': _('Comments')}},
{ 'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '6em' }},
{ 'deployed_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}}
]

View File

@ -32,7 +32,7 @@
'''
from __future__ import unicode_literals
from handlers import NotFound
from handlers import NotFound, RequestError
from django.utils.translation import ugettext as _
import logging
@ -65,6 +65,8 @@ class ModelHandlerMixin(object):
needs_staff = True
detail = None # Dictionary containing detail routing
model = None
save_fields = []
def item_as_dict(self, item):
pass
@ -117,6 +119,37 @@ class ModelHandlerMixin(object):
return res
except:
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):
'''
@ -125,6 +158,7 @@ class ModelTypeHandlerMixin(object):
'''
authenticated = True
needs_staff = True
has_comments = False
def enum_types(self):
pass
@ -140,6 +174,46 @@ class ModelTypeHandlerMixin(object):
for type_ in self.enum_types():
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):
logger.debug(self._args)
nArgs = len(self._args)
@ -162,42 +236,7 @@ class ModelTypeHandlerMixin(object):
if self._args[1] == 'gui':
gui = self.getGui(self._args[0])
# 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))
return sorted(gui, key=lambda f: f['gui']['order']);

View File

@ -125,7 +125,9 @@ class gui(object):
Returns:
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 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) {
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);
api.doLog('Ajax GET Json for "' + url + '"');
@ -51,10 +57,41 @@
type : "GET",
dataType : "json",
success : function(data) {
api.doLog('Success on "' + url + '".');
api.doLog('Received ' + JSON.stringify(data));
api.doLog('Success on GET "' + url + '".');
api.doLog('Received ', 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) {
request.setRequestHeader(api.config.auth_header, api.config.token);
},
@ -62,7 +99,7 @@
};
// Public attributes
api.debug = false;
api.debug = true;
}(window.api = window.api || {}, jQuery));
@ -104,6 +141,7 @@ function BasicModelRest(path, options) {
// Requests paths
this.path = path;
this.getPath = options.getPath || path;
this.putPath = options.putPath || path;
this.typesPath = options.typesPath || (path + '/types');
this.tableInfoPath = options.tableInfoPath || (path + '/tableinfo');
this.cache = api.cache('bmr'+path);
@ -117,7 +155,8 @@ BasicModelRest.prototype = {
_requestPath: function(path, options) {
"use strict";
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;
if( path == '.' ) {
@ -136,10 +175,11 @@ BasicModelRest.prototype = {
}
success_fnc(data);
},
fail: fail_fnc,
});
}
},
get : function(success_fnc, options) {
get: function(options) {
"use strict";
options = options || {};
@ -148,61 +188,103 @@ BasicModelRest.prototype = {
path += '/' + options.id;
return this._requestPath(path, {
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";
options = options || {};
return this.get(success_fnc, {
return this.get({
id: '',
success: success_fnc,
fail: fail_fnc
});
},
overview: function(success_fnc, options) {
overview: function(success_fnc, fail_fnc) {
"use strict";
options = options || {};
return this.get(success_fnc, {
return this.get({
id: 'overview',
success: success_fnc,
fail: fail_fnc
});
},
item: function(itemId, success_fnc, options) {
item: function(itemId, success_fnc, fail_fnc) {
"use strict";
options = options || {};
return this.get(success_fnc, {
return this.get({
id: itemId,
success: success_fnc,
fail: fail_fnc
});
},
types : function(success_fnc, options) {
// -------------
// Put methods
// -------------
put: function(data, options) {
"use strict";
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, {
cacheKey: 'type',
success: success_fnc,
});
},
gui: function(typeName, success_fnc, options) {
gui: function(typeName, success_fnc, fail_fnc) {
// GUI returns a dict, that contains:
// name: Name of the field
// value: value of the field (selected element in choice, text for inputs, etc....)
// gui: Description of the field (type, value or values, defvalue, ....
"use strict";
options = options || {};
var path = [this.typesPath, typeName, 'gui'].join('/');
return this._requestPath(path, {
cacheKey: typeName + '-gui',
success: success_fnc,
fail: fail_fnc,
});
},
tableInfo : function(success_fnc, options) {
tableInfo : function(success_fnc, fail_fnc) {
"use strict";
options = options || {};
success_fnc = success_fnc || function(){api.doLog('success not provided for tableInfo');};
var path = this.tableInfoPath;
this._requestPath(path, {
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) {
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);
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', {
id: id,
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() )
return;
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;
} else {
$(id).modal('hide');
}
$(id).modal('hide');
});
// 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() {
$('#content').empty();
$('#minimized').empty();
@ -233,432 +258,3 @@
gui.debug = true;
}(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-switch.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.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-fields.js"></script>
<script src="{% get_static_prefix %}adm/js/gui-element.js"></script>
<!-- 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>
$(function() {
// Initialize gui
gui.init();
// set default error function
api.defaultFail = gui.failRequestMessageFnc;
});
</script>
{% block js %}{% endblock %}
@ -116,6 +118,8 @@
<!-- page contents -->
{% js_template 'dashboard' %}
{% js_template 'authenticators' %}
<!-- utility pages -->
{% js_template 'request_failed' %}
<!-- components -->
{% js_template 'table' %}
{% js_template 'modal' %}

View File

@ -19,7 +19,7 @@
{% verbatim %}
{{/ if }}
{{# if button2 }}
{{{ button1 }}}
{{{ button2 }}}
{{ else }}
{% endverbatim %}
<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>