diff --git a/server/src/uds/REST/methods/providers.py b/server/src/uds/REST/methods/providers.py index 9777bec39..33c304e92 100644 --- a/server/src/uds/REST/methods/providers.py +++ b/server/src/uds/REST/methods/providers.py @@ -37,7 +37,7 @@ from uds.models import Provider from services import Services from uds.core import services -from uds.REST import NotFound +from uds.REST import NotFound, RequestError from uds.REST.model import ModelHandler import logging @@ -77,6 +77,10 @@ class Providers(ModelHandler): 'comments': provider.comments, } + def checkDelete(self, item): + if item.services.count() > 0: + raise RequestError('Can\'t delete providers with services already associated') + # Types related def enum_types(self): return services.factory().providers().values() diff --git a/server/src/uds/REST/methods/services.py b/server/src/uds/REST/methods/services.py index 20a6dbb66..8f69d078d 100644 --- a/server/src/uds/REST/methods/services.py +++ b/server/src/uds/REST/methods/services.py @@ -37,7 +37,8 @@ from django.utils.translation import ugettext as _ from uds.core.Environment import Environment from uds.REST.model import DetailHandler -from uds.REST import NotFound, ResponseError +from uds.REST import NotFound, ResponseError, RequestError +from django.db import IntegrityError import logging @@ -59,19 +60,51 @@ class Services(DetailHandler): 'deployed_services_count' : k.deployedServices.count(), } for k in parent.services.all() ] else: - with parent.get(pk=item) as k: - return { - 'id':k.id, - 'name': k.name, - 'comments': k.comments, - 'type': k.data_type, - 'typeName' : _(k.getType().name()), - 'deployed_services_count' : k.deployedServices.count(), - } + k = parent.services.get(pk=item) + return { + 'id':k.id, + 'name': k.name, + 'comments': k.comments, + 'type': k.data_type, + 'typeName' : _(k.getType().name()), + 'deployed_services_count' : k.deployedServices.count(), + } except: - logger.exception('En services') return { 'error': 'not found' } + def saveItem(self, parent, item): + # Extract item db fields + # We need this fields for all + fields = self.readFieldsFromParams(['name', 'comments', 'data_type']) + try: + if item is None: # Create new + service = parent.services.create(**fields) + else: + service = parent.services.get(pk=item) + service.__dict__.update(fields) + service.save() + except self.model.DoesNotExist: + raise NotFound('Item not found') + except IntegrityError: # Duplicate key probably + raise RequestError('Element already exists (duplicate key error)') + except Exception: + raise RequestError('incorrect invocation to PUT') + + return self.getItems(parent, service.id) + + def deleteItem(self, parent, item): + try: + service = parent.services.get(pk=item) + + if service.deployedServices.count() != 0: + raise RequestError('Item has associated deployed services') + + service.delete() + except: + raise NotFound('service not found') + + return 'deleted' + def getTitle(self, parent): try: return _('Services of {0}').format(parent.name) diff --git a/server/src/uds/REST/model.py b/server/src/uds/REST/model.py index dd618c52b..bc9b71ce8 100644 --- a/server/src/uds/REST/model.py +++ b/server/src/uds/REST/model.py @@ -111,32 +111,51 @@ class BaseModelHandler(Handler): processedFields.append({k1: dct}) return { 'title': unicode(title), 'fields': processedFields }; + def readFieldsFromParams(self, fldList): + args = {} + try: + for key in fldList: + 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))) + + return args + + # Exceptions + def invalidRequestException(self): + raise RequestError('Invalid Request') # Details do not have types at all # so, right now, we only process details petitions for Handling & tables info class DetailHandler(BaseModelHandler): ''' Detail handler (for relations such as provider-->services, authenticators-->users,groups, deployed services-->cache,assigned, groups, transports - Urls treated are: + Urls recognized for GET are: [path] --> get Items (all hopefully, this call is delegated to getItems) [path]/overview [path]/ID [path]/gui - [path]/TYPE/gui + [path]/gui/TYPE [path]/types [path]/types/TYPE - [path]/tableinfo - [path].... --> + [path]/tableinfo + For PUT: + [path] --> create NEW item + [path]/ID --> Modify existing item + For DELETE: + [path]/ID ''' - def __init__(self, parentHandler, path, *args, **kwargs): + def __init__(self, parentHandler, path, params, *args, **kwargs): self._parent = parentHandler self._path = path + self._params = params self._args = args self._kwargs = kwargs def get(self): # Process args - logger.debug("Detail args: {0}".format(self._args)) + logger.debug("Detail args for GET: {0}".format(self._args)) nArgs = len(self._args) parent = self._kwargs['parent'] if nArgs == 0: @@ -163,15 +182,54 @@ class DetailHandler(BaseModelHandler): return self.fallbackGet() + def put(self): + ''' + Put is delegated to specific implementation + ''' + logger.debug("Detail args for PUT: {0}, {1}".format(self._args, self._params)) + + parent = self._kwargs['parent'] + + if len(self._args) == 0: + # Create new + item = None + elif len(self._args) == 1: + item = self._args[0] + else: + self.invalidRequestException() + + return self.saveItem(parent, item) + + def delete(self): + ''' + Put is delegated to specific implementation + ''' + logger.debug("Detail args for DELETE: {0}".format(self._args)) + + parent = self._kwargs['parent'] + + if len(self._args) != 1: + self.invalidRequestException() + + return self.deleteItem(parent, self._args[0]) + # Invoked if default get can't process request def fallbackGet(self): - raise RequestError('Invalid request') + raise self.invalidRequestException() # Default (as sample) getItems def getItems(self, parent, item): - if item is None: + if item is None: # Returns ALL detail items return [] - return {} + return {} # Returns one item + + # Default save + def saveItem(self, parent, item): + self.invalidRequestException() + + # Default delete + def deleteItem(self, parent, item): + self.invalidRequestException() # A detail handler must also return title & fields for tables def getTitle(self, parent): @@ -181,7 +239,7 @@ class DetailHandler(BaseModelHandler): return [] def getGui(self, parent, forType): - raise RequestError('Gui not provided') + raise RequestError('Gui not provided for this type of object') def getTypes(self, parent, forType): return [] # Default is that details do not have types @@ -214,24 +272,12 @@ class ModelHandler(BaseModelHandler): table_fields = [] table_title = '' - def __fillIntanceFields(self, item, res): - if hasattr(item, 'getInstance'): - for key, value in item.getInstance().valuesDict().iteritems(): - value = {"true":True, "false":False}.get(value, value) - logger.debug('{0} = {1}'.format(key, value)) - res[key] = value - + # This method must be override, depending on what is provided + # Data related def item_as_dict(self, item): pass - def getItems(self, *args, **kwargs): - for item in self.model.objects.filter(*args, **kwargs): - try: - yield self.item_as_dict(item) - except: - logger.exception('Exception getting item from {0}'.format(self.model)) - # types related def enum_types(self): # override this return [] @@ -255,7 +301,14 @@ class ModelHandler(BaseModelHandler): # gui related def getGui(self, type_): - raise RequestError('invalid request') + self.invalidRequestException() + + # Delete related, checks if the item can be deleted + # If it can't be so, raises an exception + def checkDelete(self, item): + pass + + # End overridable # Helper to process detail def processDetail(self): @@ -265,11 +318,25 @@ class ModelHandler(BaseModelHandler): detailCls = self.detail[self._args[1]] args = list(self._args[2:]) path = self._path + '/'.join(args[:2]) - detail = detailCls(self, path, *args, parent = item) + detail = detailCls(self, path, self._params, *args, parent = item) return getattr(detail, self._operation)() except AttributeError: raise NotFound('method not found') - + + def getItems(self, *args, **kwargs): + for item in self.model.objects.filter(*args, **kwargs): + try: + yield self.item_as_dict(item) + except: + logger.exception('Exception getting item from {0}'.format(self.model)) + + def fillIntanceFields(self, item, res): + if hasattr(item, 'getInstance'): + for key, value in item.getInstance().valuesDict().iteritems(): + value = {"true":True, "false":False}.get(value, value) + logger.debug('{0} = {1}'.format(key, value)) + res[key] = value + def get(self): logger.debug('method GET for {0}, {1}'.format(self.__class__.__name__, self._args)) nArgs = len(self._args) @@ -277,7 +344,7 @@ class ModelHandler(BaseModelHandler): result = [] for val in self.model.objects.all(): res = self.item_as_dict(val) - self.__fillIntanceFields(val, res) + self.fillIntanceFields(val, res) result.append(res) return result @@ -293,7 +360,7 @@ class ModelHandler(BaseModelHandler): try: val = self.model.objects.get(pk=self._args[0]) res = self.item_as_dict(val) - self.__fillIntanceFields(val, res) + self.fillIntanceFields(val, res) return res except: raise NotFound('item not found') @@ -318,59 +385,56 @@ class ModelHandler(BaseModelHandler): 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))) - + + if len(self._args) > 1: # Detail? + return self.processDetail() try: + # Extract fields + args = self.readFieldsFromParams(self.save_fields) + deleteOnError = False if len(self._args) == 0: # create new - item = self.model.objects.create(**args); - elif len(self._args) == 1: + item = self.model.objects.create(**args) + deleteOnError = True + else: # Must have 1 arg # We have to take care with this case, update will efectively update records on db item = self.model.objects.get(pk=self._args[0]); item.__dict__.update(args) # Update fields from args - else: # TODO: Maybe a detail put request - raise Exception() # Incorrect invocation except self.model.DoesNotExist: - raise NotFound('Element do not exists') + raise NotFound('Item not found') except IntegrityError: # Duplicate key probably raise RequestError('Element already exists (duplicate key error)') - except Exception as e: + except Exception: raise RequestError('incorrect invocation to PUT') # Store associated object if needed - 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() - - res = self.item_as_dict(item) - - self.__fillIntanceFields(item, res) - - item.save() + 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() + + res = self.item_as_dict(item) + self.fillIntanceFields(item, res) + except: + if deleteOnError: + item.delete() + raise return res def delete(self): logger.debug('method DELETE for {0}, {1}'.format(self.__class__.__name__, self._args)) - if len(self._args) == 2: # TODO: Detail request - raise Exception() + if len(self._args) > 1: + return self.processDetail() + if len(self._args) != 1: raise RequestError('Delete need one and only one argument') try: item = self.model.objects.get(pk=self._args[0]); + self.checkDelete(item) item.delete() except self.model.DoesNotExist: raise NotFound('Element do not exists') - except Exception: - logger.exception('delete') - raise RequestError('incorrect invocation to DELETE') return 'deleted' - diff --git a/server/src/uds/static/adm/js/api-templates.js b/server/src/uds/static/adm/js/api-templates.js index 1e7a1a151..742b9dbd9 100644 --- a/server/src/uds/static/adm/js/api-templates.js +++ b/server/src/uds/static/adm/js/api-templates.js @@ -43,7 +43,7 @@ api.templates.get = function(name, success_fnc) { var $this = this; success_fnc = success_fnc || function(){}; - api.doLog('Getting tempkate ' + name); + api.doLog('Getting template ' + name); if (name.indexOf('?') == -1) { if ($this.cache.get(name) ) { success_fnc($this.cache.get(name)); @@ -64,7 +64,6 @@ $this.cache.put('_' + cachedId, $this.evaluate(data)); $this.cache.put(name, cachedId); api.doLog('Success getting template "' + name + '".'); - api.doLog('Received: ' + data); success_fnc(cachedId); }, fail: function( jqXHR, textStatus, errorThrown ) { diff --git a/server/src/uds/static/adm/js/api.js b/server/src/uds/static/adm/js/api.js index 151758cd1..52c6da3fe 100644 --- a/server/src/uds/static/adm/js/api.js +++ b/server/src/uds/static/adm/js/api.js @@ -359,6 +359,34 @@ DetailModelRestApi.prototype = { "use strict"; return this.base.get(success_fnc, fail_fnc); }, + put: function(data, options) { + "use strict"; + return this.base.put(data, options); + }, + 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 + }); + }, + // -------------- + // Delete + // -------------- + del: function(id, success_fnc, fail_fnc) { + "use strict"; + return this.base.del(id, success_fnc, fail_fnc); + }, tableInfo: function(success_fnc, fail_fnc) { "use strict"; return this.base.tableInfo(success_fnc, fail_fnc); diff --git a/server/src/uds/static/adm/js/gui-definition.js b/server/src/uds/static/adm/js/gui-definition.js index 03f016f30..212c5f2c2 100644 --- a/server/src/uds/static/adm/js/gui-definition.js +++ b/server/src/uds/static/adm/js/gui-definition.js @@ -41,13 +41,13 @@ gui.providers.link = function(event) { container : 'providers-placeholder', rowSelect : 'single', onCheck : function(check, items) { // Check if item can be deleted - if( check == 'delete' ) { + /*if( check == 'delete' ) { for( var i in items ) { if( items[i].services_count > 0) return false; } return true; - } + }*/ return true; }, onRowSelect : function(selected) { @@ -71,7 +71,9 @@ gui.providers.link = function(event) { return true; }, buttons : [ 'new', 'edit', 'delete', 'xls' ], - onEdit : services.typedEdit(gettext('Edit service'), gettext('Error processing service')), + onEdit : gui.methods.typedEdit(services, gettext('Edit service'), gettext('Error processing service')), + onNew : gui.methods.typedNew(services, gettext('New service'), gettext('Error creating service')), + onDelete: gui.methods.del(services, gettext('Delete service'), gettext('Error deleting service')), scrollToTable : false, onLoad: function(k) { api.tools.unblockUI(); @@ -80,7 +82,9 @@ gui.providers.link = function(event) { return false; }, buttons : [ 'new', 'edit', 'delete', 'xls' ], - onEdit: gui.providers.typedEdit(gettext('Edit provider'), gettext('Error processing provider')), + onNew : gui.methods.typedNew(gui.providers, gettext('New provider'), gettext('Error creating provider')), + onEdit: gui.methods.typedEdit(gui.providers, gettext('Edit provider'), gettext('Error processing provider')), + onDelete: gui.methods.del(gui.providers, gettext('Delete provider'), gettext('Error deleting provider')), }); }); @@ -107,7 +111,7 @@ gui.authenticators.link = function(event) { gui.authenticators.table({ container : 'auths-placeholder', rowSelect : 'single', - buttons : [ 'edit', 'delete', 'xls' ], + buttons : [ 'new', 'edit', 'delete', 'xls' ], onRowSelect : function(selected) { api.tools.blockUI(); var id = selected[0].id; @@ -135,7 +139,9 @@ gui.authenticators.link = function(event) { onRefresh : function() { $('#users-placeholder').empty(); // Remove detail on parent refresh }, - onEdit: gui.authenticators.typedEdit(gettext('Edit authenticator'), gettext('Error processing authenticator')), + onNew : gui.methods.typedNew(gui.authenticators, gettext('New authenticator'), gettext('Error creating authenticator')), + onEdit: gui.methods.typedEdit(gui.authenticators, gettext('Edit authenticator'), gettext('Error processing authenticator')), + onDelete: gui.methods.del(gui.authenticators, gettext('Delete authenticator'), gettext('Error deleting authenticator')), }); }); @@ -241,8 +247,7 @@ gui.connectivity.link = function(event) { // TODO: Add confirmation to deletion gui.connectivity.transports.rest.del(value.id, function(){ refreshFnc(); - }, gui.failRequestModalFnc(gettext('Error removing transport')) - ); + }, gui.failRequestModalFnc(gettext('Error removing transport')) ); }, }); gui.connectivity.networks.table({ diff --git a/server/src/uds/static/adm/js/gui-element.js b/server/src/uds/static/adm/js/gui-element.js index 8317d9486..863d7d79f 100644 --- a/server/src/uds/static/adm/js/gui-element.js +++ b/server/src/uds/static/adm/js/gui-element.js @@ -352,9 +352,9 @@ GuiElement.prototype = { headings.push(api.spreadsheet.cell(heading.sTitle, 'String', styles.bold)); }); rows.push(api.spreadsheet.row(headings)); - $.each(data, function(index, row) { + $.each(data, function(index1, row) { var cells = []; - $.each(columns, function(index, col){ + $.each(columns, function(index2, col){ if( col.bVisible === false ) { return; } @@ -363,7 +363,6 @@ GuiElement.prototype = { }); rows.push(api.spreadsheet.row(cells)); }); - var ctx = { creation_date: (new Date()).toISOString(), worksheet: title, @@ -371,6 +370,7 @@ GuiElement.prototype = { rows_count: rows.length, rows: rows.join('\n') }; + gui.doLog(ctx); setTimeout( function() { saveAs(new Blob([api.templates.evaluate(tmpl, ctx)], {type: 'application/vnd.ms-excel'} ), title + '.xls'); @@ -446,28 +446,4 @@ GuiElement.prototype = { }); // End Tableinfo data return '#' + tableId; }, - // "Generic" edit method to set onEdit table - typedEdit: function(modalTitle, modalErrorMsg, guiProcessor, fieldsProcessor) { - "use strict"; - var self = this; - return function(value, event, table, refreshFnc) { - self.rest.gui(value.type, function(guiDefinition) { - var tabs = guiProcessor ? guiProcessor(guiDefinition) : guiDefinition; - self.rest.item(value.id, function(item) { - gui.forms.launchModal(modalTitle+' '+value.name+'', tabs, item, function(form_selector, closeFnc) { - var fields = gui.forms.read(form_selector); - fields.data_type = value.type; - fields = fieldsProcessor ? fieldsProcessor(fields) : fields; // Preprocess fields (probably generate tabs...) - self.rest.save(fields, function(data) { // Success on put - closeFnc(); - refreshFnc(); - }, gui.failRequestModalFnc(modalErrorMsg)); // Fail on put, show modal message - return false; - }); - }); - - }, gui.failRequestModalFnc(modalErrorMsg)); - }; - }, - }; diff --git a/server/src/uds/static/adm/js/gui.js b/server/src/uds/static/adm/js/gui.js index 858735bcb..786687394 100644 --- a/server/src/uds/static/adm/js/gui.js +++ b/server/src/uds/static/adm/js/gui.js @@ -116,8 +116,17 @@ .on('hidden.bs.modal', function () { $(id).remove(); }); + return id; }; + gui.alert = function(message, type) { + api.templates.get('alert', function(tmpl) { + $(api.templates.evaluate(tmpl, { + type: type, + message: message + })).appendTo('#alerts'); + }); + }; gui.failRequestMessageFnc = function(jqXHR, textStatus, errorThrown) { api.templates.get('request_failed', function(tmpl) { @@ -209,7 +218,71 @@ gui.setLinksEvents(); gui.dashboard.link(); }; + + // Generic "methods" for editing, creating, etc... + + gui.methods = {}; + + // "Generic" edit method to set onEdit table + gui.methods.typedEdit = function(parent, modalTitle, modalErrorMsg, guiProcessor, fieldsProcessor) { + var self = parent; + return function(value, event, table, refreshFnc) { + self.rest.gui(value.type, function(guiDefinition) { + var tabs = guiProcessor ? guiProcessor(guiDefinition) : guiDefinition; // Preprocess fields (probably generate tabs...) + self.rest.item(value.id, function(item) { + gui.forms.launchModal(modalTitle+' '+value.name+'', tabs, item, function(form_selector, closeFnc) { + var fields = gui.forms.read(form_selector); + fields.data_type = value.type; + fields = fieldsProcessor ? fieldsProcessor(fields) : fields; + self.rest.save(fields, function(data) { // Success on put + closeFnc(); + refreshFnc(); + gui.alert(gettext('Edition successfully done'), 'success'); + }, gui.failRequestModalFnc(modalErrorMsg)); // Fail on put, show modal message + return false; + }); + }); + + }, gui.failRequestModalFnc(modalErrorMsg)); + }; + }; + // "Generic" new method to set onNew table + gui.methods.typedNew = function(parent, modalTitle, modalErrorMsg, guiProcessor, fieldsProcessor) { + var self = parent; + return function(type, table, refreshFnc) { + self.rest.gui(type, function(guiDefinition) { + var tabs = guiProcessor ? guiProcessor(guiDefinition) : guiDefinition; // Preprocess fields (probably generate tabs...) + gui.forms.launchModal(modalTitle, tabs, undefined, function(form_selector, closeFnc) { + var fields = gui.forms.read(form_selector); + fields.data_type = type; + fields = fieldsProcessor ? fieldsProcessor(fields) : fields; // P + self.rest.create(fields, function(data) { // Success on put + closeFnc(); + refreshFnc(); + gui.alert(gettext('Creation successfully done'), 'success'); + }, gui.failRequestModalFnc(modalErrorMsg) // Fail on put, show modal message + ); + }); + }); + }; + }; + + gui.methods.del = function(parent, modalTitle, modalErrorMsg) { + var self = parent; + return function(value, event, table, refreshFnc) { + var content = gettext('Are you sure do you want to delete ') + '' + value.name + ''; + var modalId = gui.launchModal(modalTitle, content, ''); + $(modalId + ' .button-accept').click(function(){ + $(modalId).modal('hide'); + self.rest.del(value.id, function(){ + refreshFnc(); + gui.alert(gettext('Deletion successfully done'), 'success'); + }, gui.failRequestModalFnc(modalErrorMsg) ); + }); + }; + }; + // Public attributes gui.debug = true; }(window.gui = window.gui || {}, jQuery)); diff --git a/server/src/uds/templates/uds/admin/index.html b/server/src/uds/templates/uds/admin/index.html index 49c46db60..c54ef632d 100644 --- a/server/src/uds/templates/uds/admin/index.html +++ b/server/src/uds/templates/uds/admin/index.html @@ -35,8 +35,9 @@
+
+
- {% block body %}{% endblock %}
@@ -124,6 +125,7 @@ {% js_template 'table' %} {% js_template 'modal' %} {% js_template 'responsive_table' %} + {% js_template 'alert' %} {% js_template 'fld/checkbox' %} {% js_template 'fld/choice' %} diff --git a/server/src/uds/templates/uds/admin/tmpl/alert.html b/server/src/uds/templates/uds/admin/tmpl/alert.html new file mode 100644 index 000000000..0a86b996f --- /dev/null +++ b/server/src/uds/templates/uds/admin/tmpl/alert.html @@ -0,0 +1,6 @@ +{% verbatim %} +
+ + {{ message }} +
+{% endverbatim %} \ No newline at end of file