diff --git a/server/.settings/org.eclipse.core.resources.prefs b/server/.settings/org.eclipse.core.resources.prefs index 93e19444..7777ebc3 100644 --- a/server/.settings/org.eclipse.core.resources.prefs +++ b/server/.settings/org.eclipse.core.resources.prefs @@ -18,7 +18,7 @@ encoding//src/uds/REST/methods/providers.py=utf-8 encoding//src/uds/REST/methods/services.py=utf-8 encoding//src/uds/REST/methods/transports.py=utf-8 encoding//src/uds/REST/methods/users_groups.py=utf-8 -encoding//src/uds/REST/mixins.py=utf-8 +encoding//src/uds/REST/model.py=utf-8 encoding//src/uds/REST/processors.py=utf-8 encoding//src/uds/__init__.py=utf-8 encoding//src/uds/admin/urls.py=utf-8 diff --git a/server/src/uds/REST/__init__.py b/server/src/uds/REST/__init__.py index e0fda042..c0f54e25 100644 --- a/server/src/uds/REST/__init__.py +++ b/server/src/uds/REST/__init__.py @@ -153,13 +153,36 @@ class Dispatcher(View): except AccessDenied as e: return http.HttpResponseForbidden(unicode(e)) except NotFound as e: - return http.Http404(unicode(e)) + return http.HttpResponseNotFound(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)) + @staticmethod + def registerSubclasses(classes): + for cls in classes: + if len(cls.__subclasses__()) == 0: # Only classes that has not been inherited will be registered as Handlers + logger.debug('Found class {0}'.format(cls)) + if cls.name is None: + name = cls.__name__.lower() + else: + name = cls.name + logger.debug('Adding handler {0} for method {1} in path {2}'.format(cls, name, cls.path)) + service_node = Dispatcher.services + if cls.path is not None: + for k in cls.path.split('/'): + if service_node.get(k) is None: + service_node[k] = { '' : None } + service_node = service_node[k] + if service_node.get(name) is None: + service_node[name] = { '' : None } + + service_node[name][''] = cls + else: + Dispatcher.registerSubclasses(cls.__subclasses__()) + # Initializes the dispatchers @staticmethod def initialize(): @@ -170,6 +193,8 @@ class Dispatcher(View): import os.path, pkgutil import sys + logger.debug('Loading Handlers') + # Dinamycally import children of this package. The __init__.py files must register, if needed, inside ServiceProviderFactory package = 'methods' @@ -177,22 +202,6 @@ class Dispatcher(View): for _, name, _ in pkgutil.iter_modules([pkgpath]): __import__(__name__ + '.' + package + '.' + name, globals(), locals(), [], -1) - for cls in Handler.__subclasses__(): # @UndefinedVariable - # Skip ClusteredServiceProvider - if cls.name is None: - name = cls.__name__.lower() - else: - name = cls.name - logger.debug('Adding handler {0} for method {1} in path {2}'.format(cls, name, cls.path)) - service_node = Dispatcher.services - if cls.path is not None: - for k in cls.path.split('/'): - if service_node.get(k) is None: - service_node[k] = { '' : None } - service_node = service_node[k] - if service_node.get(name) is None: - service_node[name] = { '' : None } - - service_node[name][''] = cls + Dispatcher.registerSubclasses(Handler.__subclasses__()) # @UndefinedVariable Dispatcher.initialize() diff --git a/server/src/uds/REST/methods/authenticators.py b/server/src/uds/REST/methods/authenticators.py index 5d22764d..16981fff 100644 --- a/server/src/uds/REST/methods/authenticators.py +++ b/server/src/uds/REST/methods/authenticators.py @@ -39,7 +39,7 @@ from uds.core import auths from users_groups import Users, Groups from uds.REST import Handler, NotFound -from uds.REST.mixins import ModelHandlerMixin, ModelTypeHandlerMixin, ModelTableHandlerMixin +from uds.REST.model import ModelHandler import logging @@ -47,9 +47,25 @@ logger = logging.getLogger(__name__) # Enclosed methods under /auth path -class Authenticators(ModelHandlerMixin, Handler): +class Authenticators(ModelHandler): model = Authenticator detail = { 'users': Users, 'groups':Groups } + + table_title = _('Current authenticators') + table_fields = [ + { 'name': {'title': _('Name'), 'visible': True, 'type': 'iconType' } }, + { 'comments': {'title': _('Comments')}}, + { 'users_count': {'title': _('Users'), 'type': 'numeric', 'width': '5em'}} + ] + + def enum_types(self): + return auths.factory().providers().values() + + def getGui(self, type_): + try: + return auths.factory().lookup(type_).guiDescription() + except: + raise NotFound('type not found') def item_as_dict(self, auth): type_ = auth.getType() @@ -59,27 +75,4 @@ class Authenticators(ModelHandlerMixin, Handler): 'type': type_.type(), 'comments': auth.comments, } - -class Types(ModelTypeHandlerMixin, Handler): - path = 'authenticators' - def enum_types(self): - return auths.factory().providers().values() - - def getGui(self, type_): - try: - return auths.factory().lookup(type_).guiDescription() - except: - raise NotFound('type not found') - - -class TableInfo(ModelTableHandlerMixin, Handler): - path = 'authenticators' - detail = { 'users': Users, 'groups':Groups } - - title = _('Current authenticators') - fields = [ - { 'name': {'title': _('Name'), 'visible': True, 'type': 'iconType' } }, - { 'comments': {'title': _('Comments')}}, - { 'users_count': {'title': _('Users'), 'type': 'numeric', 'width': '5em'}} - ] diff --git a/server/src/uds/REST/methods/networks.py b/server/src/uds/REST/methods/networks.py index 2686d103..2919ed44 100644 --- a/server/src/uds/REST/methods/networks.py +++ b/server/src/uds/REST/methods/networks.py @@ -35,8 +35,7 @@ from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _ from uds.models import Network -from uds.REST import Handler, HandlerError -from uds.REST.mixins import ModelHandlerMixin, ModelTypeHandlerMixin, ModelTableHandlerMixin +from uds.REST.model import ModelHandler import logging @@ -44,8 +43,15 @@ logger = logging.getLogger(__name__) # Enclosed methods under /item path -class Networks(ModelHandlerMixin, Handler): +class Networks(ModelHandler): model = Network + + table_title = _('Current Networks') + table_fields = [ + { 'name': {'title': _('Name'), 'visible': True, 'type': 'icon', 'icon': 'fa fa-globe text-success' } }, + { 'net_string': {'title': _('Networks')}}, + { 'networks_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}} + ] def item_as_dict(self, item): return { 'id': item.id, @@ -53,19 +59,3 @@ class Networks(ModelHandlerMixin, Handler): 'net_string': item.net_string, 'networks_count': item.transports.count(), } - -class Types(ModelTypeHandlerMixin, Handler): - path = 'networks' - - # Fake mathods, to yield self on enum types and get a "fake" type for Network - def enum_types(self): - return [] - -class TableInfo(ModelTableHandlerMixin, Handler): - path = 'networks' - title = _('Current Networks') - fields = [ - { 'name': {'title': _('Name'), 'visible': True, 'type': 'icon', 'icon': 'fa fa-globe text-success' } }, - { 'net_string': {'title': _('Networks')}}, - { 'networks_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}} - ] diff --git a/server/src/uds/REST/methods/osmanagers.py b/server/src/uds/REST/methods/osmanagers.py index 5ad81408..b640eaef 100644 --- a/server/src/uds/REST/methods/osmanagers.py +++ b/server/src/uds/REST/methods/osmanagers.py @@ -36,8 +36,7 @@ from django.utils.translation import ugettext_lazy as _ from uds.models import OSManager from uds.core.osmanagers import factory -from uds.REST import Handler, HandlerError -from uds.REST.mixins import ModelHandlerMixin, ModelTypeHandlerMixin, ModelTableHandlerMixin +from uds.REST.model import ModelHandler import logging @@ -45,8 +44,15 @@ logger = logging.getLogger(__name__) # Enclosed methods under /osm path -class OsManagers(ModelHandlerMixin, Handler): +class OsManagers(ModelHandler): model = OSManager + + table_title = _('Current OS Managers') + table_fields = [ + { 'name': {'title': _('Name'), 'visible': True, 'type': 'iconType' } }, + { 'comments': {'title': _('Comments')}}, + { 'deployed_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}} + ] def item_as_dict(self, osm): type_ = osm.getType() @@ -56,19 +62,3 @@ class OsManagers(ModelHandlerMixin, Handler): 'type': type_.type(), 'comments': osm.comments, } - -class Types(ModelTypeHandlerMixin, Handler): - path = 'osmanagers' - model = OsManagers - - def enum_types(self): - return factory().providers().values() - -class TableInfo(ModelTableHandlerMixin, Handler): - path = 'osmanagers' - title = _('Current OS Managers') - fields = [ - { 'name': {'title': _('Name'), 'visible': True, 'type': 'iconType' } }, - { 'comments': {'title': _('Comments')}}, - { 'deployed_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}} - ] diff --git a/server/src/uds/REST/methods/providers.py b/server/src/uds/REST/methods/providers.py index 6342bccb..9777bec3 100644 --- a/server/src/uds/REST/methods/providers.py +++ b/server/src/uds/REST/methods/providers.py @@ -37,19 +37,28 @@ from uds.models import Provider from services import Services from uds.core import services -from uds.REST import Handler, NotFound -from uds.REST.mixins import ModelHandlerMixin, ModelTypeHandlerMixin, ModelTableHandlerMixin +from uds.REST import NotFound +from uds.REST.model import ModelHandler import logging logger = logging.getLogger(__name__) -class Providers(ModelHandlerMixin, Handler): +class Providers(ModelHandler): model = Provider detail = { 'services': Services } save_fields = ['name', 'comments'] + table_title = _('Service providers') + + # Table info fields + table_fields = [ + { 'name': {'title': _('Name'), 'type': 'iconType' } }, + { 'comments': {'title': _('Comments')}}, + { 'services_count': {'title': _('Services'), 'type': 'numeric', 'width': '5em'}}, + ] + def item_as_dict(self, provider): type_ = provider.getType() @@ -68,26 +77,15 @@ class Providers(ModelHandlerMixin, Handler): 'comments': provider.comments, } -class Types(ModelTypeHandlerMixin, Handler): - path = 'providers' - + # Types related def enum_types(self): return services.factory().providers().values() - + + # Gui related def getGui(self, type_): try: return self.addDefaultFields(services.factory().lookup(type_).guiDescription(), ['name', 'comments']) except: raise NotFound('type not found') - -class TableInfo(ModelTableHandlerMixin, Handler): - path = 'providers' - detail = { 'services': Services } - title = _('Current service providers') - - fields = [ - { 'name': {'title': _('Name'), 'type': 'iconType' } }, - { 'comments': {'title': _('Comments')}}, - { 'services_count': {'title': _('Services'), 'type': 'numeric', 'width': '5em'}}, - ] - + + diff --git a/server/src/uds/REST/methods/services.py b/server/src/uds/REST/methods/services.py index 8c8ee160..462540b5 100644 --- a/server/src/uds/REST/methods/services.py +++ b/server/src/uds/REST/methods/services.py @@ -33,9 +33,11 @@ from __future__ import unicode_literals from django.utils.translation import ugettext as _ -from uds.models import Provider -from uds.REST.mixins import DetailHandler +from uds.core.Environment import Environment + +from uds.REST.model import DetailHandler +from uds.REST import NotFound import logging @@ -44,21 +46,19 @@ logger = logging.getLogger(__name__) class Services(DetailHandler): - def get(self): + def getItems(self, parent, item): # Extract providerenticator - provider = self._kwargs['parent'] - try: - if len(self._args) == 0: + if item is None: return [{ 'id':k.id, 'name': k.name, 'comments': k.comments, 'type': k.data_type, 'typeName' : _(k.getType().name()) - } for k in provider.services.all() ] + } for k in parent.services.all() ] else: - with provider.get(pk=self._args[0]) as k: + with parent.get(pk=item) as k: return { 'id':k.id, 'name': k.name, @@ -70,15 +70,40 @@ class Services(DetailHandler): logger.exception('En services') return { 'error': 'not found' } - def getTitle(self): + def getTitle(self, parent): try: - return _('Services of {0}').format(Provider.objects.get(pk=self._kwargs['parent_id']).name) + return _('Services of {0}').format(parent.name) except: return _('Current services') - def getFields(self): + def getFields(self, parent): return [ { 'name': {'title': _('Service name'), 'visible': True, 'type': 'iconType' } }, { 'comments': { 'title': _('Comments') } }, { 'type': {'title': _('Type') } } - ] + ] + + def getTypes(self, parent, forType): + logger.debug('getTypes parameters: {0}, {1}'.format(parent, forType)) + if forType is None: + offers = [{ + 'name' : _(t.name()), + 'type' : t.type(), + 'description' : _(t.description()), + 'icon' : t.icon().replace('\n', '') } for t in parent.getType().getServicesTypes()] + else: + offers = None # Do we really need to get one specific type? + for t in parent.getType().getServicesTypes(): + if forType == t.type(): + offers = t + break + if offers is None: + raise NotFound('type not found') + + return offers # Default is that details do not have types + + def getGui(self, parent, forType): + logger.debug('getGui parameters: {0}, {1}'.format(parent, forType)) + serviceType = parent.getServiceByType(type) + service = serviceType( Environment.getTempEnv(), parent) # Instantiate it so it has the opportunity to alter gui description based on parent + return self.addDefaultFields( service.guiDescription(service), ['name', 'comments']) diff --git a/server/src/uds/REST/methods/transports.py b/server/src/uds/REST/methods/transports.py index 3df66471..e92153bf 100644 --- a/server/src/uds/REST/methods/transports.py +++ b/server/src/uds/REST/methods/transports.py @@ -37,7 +37,7 @@ from uds.models import Transport from uds.core.transports import factory from uds.REST import Handler, NotFound -from uds.REST.mixins import ModelHandlerMixin, ModelTypeHandlerMixin, ModelTableHandlerMixin +from uds.REST.model import ModelHandler import logging @@ -45,26 +45,18 @@ logger = logging.getLogger(__name__) # Enclosed methods under /item path -class Transports(ModelHandlerMixin, Handler): +class Transports(ModelHandler): model = Transport save_fields = ['name', 'comments', 'priority', 'nets_positive'] - - def item_as_dict(self, item): - type_ = item.getType() - return { 'id': item.id, - 'name': item.name, - 'comments': item.comments, - 'priority': item.priority, - 'nets_positive': item.nets_positive, - 'networks': [ n.id for n in item.networks.all() ], - 'deployed_count': item.deployedServices.count(), - 'type': type_.type(), - } - -class Types(ModelTypeHandlerMixin, Handler): - path = 'transports' - + table_title = _('Current Transports') + table_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'}} + ] + def enum_types(self): return factory().providers().values() @@ -82,13 +74,14 @@ class Types(ModelTypeHandlerMixin, Handler): except: raise NotFound('type not found') - -class TableInfo(ModelTableHandlerMixin, Handler): - path = 'transports' - title = _('Current Transports') - 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'}} - ] + def item_as_dict(self, item): + type_ = item.getType() + return { 'id': item.id, + 'name': item.name, + 'comments': item.comments, + 'priority': item.priority, + 'nets_positive': item.nets_positive, + 'networks': [ n.id for n in item.networks.all() ], + 'deployed_count': item.deployedServices.count(), + 'type': type_.type(), + } diff --git a/server/src/uds/REST/methods/users_groups.py b/server/src/uds/REST/methods/users_groups.py index cfdbd551..2d73a2f7 100644 --- a/server/src/uds/REST/methods/users_groups.py +++ b/server/src/uds/REST/methods/users_groups.py @@ -38,7 +38,7 @@ from uds.core.util.State import State from uds.models import Authenticator from uds.REST.handlers import HandlerError -from uds.REST.mixins import DetailHandler +from uds.REST.model import DetailHandler import logging diff --git a/server/src/uds/REST/mixins.py b/server/src/uds/REST/model.py similarity index 64% rename from server/src/uds/REST/mixins.py rename to server/src/uds/REST/model.py index 3979b40f..4443e3c4 100644 --- a/server/src/uds/REST/mixins.py +++ b/server/src/uds/REST/model.py @@ -35,169 +35,21 @@ from __future__ import unicode_literals from handlers import NotFound, RequestError from django.utils.translation import ugettext as _ from django.db import IntegrityError +from uds.REST.handlers import Handler import logging logger = logging.getLogger(__name__) -# Details do not have types at all -# so, right now, we only process details petitions for Handling & tables info -class DetailHandler(object): - def __init__(self, parentHandler, path, *args, **kwargs): - self._parent = parentHandler - self._path = path - self._args = args - self._kwargs = kwargs - - # A detail handler must also return title & fields for tables - def getTitle(self): - return '' +# a few constants +OVERVIEW = 'overview' +TYPES = 'types' +TABLEINFO = 'tableinfo' +GUI = 'gui' + +# Base for Gui Related mixins +class BaseModelHandler(Handler): - def getFields(self): - return [] - -class ModelHandlerMixin(object): - ''' - Basic Handler for a model - Basically we will need same operations for all models, so we can - take advantage of this fact to not repeat same code again and again... - ''' - authenticated = True - needs_staff = True - detail = None # Dictionary containing detail routing - model = None - save_fields = [] - - 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 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)) - - def processDetail(self): - logger.debug('Processing detail') - try: - item = self.model.objects.filter(pk=self._args[0])[0] - detailCls = self.detail[self._args[1]] - args = list(self._args[2:]) - path = self._path + '/'.join(args[:2]) - detail = detailCls(self, path, parent = item) - return getattr(detail, self._operation)() - except: - raise NotFound('method not found') - - def get(self): - logger.debug('method GET for {0}, {1}'.format(self.__class__.__name__, self._args)) - if len(self._args) == 0: - result = [] - for val in self.model.objects.all(): - res = self.item_as_dict(val) - self.__fillIntanceFields(val, res) - result.append(res) - return result - - if self._args[0] == 'overview': - return list(self.getItems()) - - # If has detail and is requesting detail - if self.detail is not None and len(self._args) > 1: - return self.processDetail() - - try: - val = self.model.objects.get(pk=self._args[0]) - res = self.item_as_dict(val) - self.__fillIntanceFields(val, res) - 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: - if len(self._args) == 0: # create new - item = self.model.objects.create(**args); - elif len(self._args) == 1: - # 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: - raise Exception() # Incorrect invocation - except self.model.DoesNotExist: - raise NotFound('Element do not exists') - except IntegrityError: # Duplicate key probably - raise RequestError('Element already exists (duplicate key error)') - except Exception as e: - 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() - - res = self.item_as_dict(item) - - self.__fillIntanceFields(item, res) - - item.save() - - return res - - 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 one and only one argument') - try: - item = self.model.objects.get(pk=self._args[0]); - item.delete() - except self.model.DoesNotExist: - raise NotFound('Element do not exists') - except Exception as e: - logger.exception('delete') - raise RequestError('incorrect invocation to DELETE') - - return 'deleted' - -class ModelTypeHandlerMixin(object): - ''' - As With models, a lot of UDS model contains info about its class. - We take advantage of this for not repeating same code (as with ModelHandlerMixin) - ''' - authenticated = True - needs_staff = True - - def enum_types(self): - pass - - def type_as_dict(self, type_): - return { 'name' : _(type_.name()), - 'type' : type_.type(), - 'description' : _(type_.description()), - 'icon' : type_.icon().replace('\n', '') - } - - def getTypes(self, *args, **kwargs): - for type_ in self.enum_types(): - yield self.type_as_dict(type_) - def addField(self, gui, field): gui.append({ 'name': field.get('name', ''), @@ -237,70 +89,15 @@ class ModelTypeHandlerMixin(object): 'order': -99, }) return gui - - def get(self): - logger.debug(self._args) - nArgs = len(self._args) - if nArgs == 0: - return list(self.getTypes()) - - found = None - for v in self.getTypes(): - if v['type'] == self._args[0]: - found = v - break - - if found is None: - raise NotFound('type not found') - - logger.debug('Found type {0}'.format(v)) - if nArgs == 1: - return found - - if self._args[1] == 'gui': - gui = self.getGui(self._args[0]) - # Add name default description, at top of form - - logger.debug("GUI: {0}".format(gui)) - return sorted(gui, key=lambda f: f['gui']['order']); - -class ModelTableHandlerMixin(object): - authenticated = True - needs_staff = True - detail = None - - # Fields should have id of the field, type and length - # All options can be ommited - # Sample fields: - # fields = [ - # { 'name': {'title': _('Name')} }, - # { 'comments': {'title': _('Comments')}}, - # { 'services_count': {'title': _('Services'), 'type': 'numeric', 'width': '5em'}} - #] - - fields = [] - title = '' - - def processDetail(self): - logger.debug('Processing detail for table') - try: - detailCls = self.detail[self._args[1]] - args = list(self._args[2:]) - path = self._path + '/'.join(args[:2]) - detail = detailCls(self, path, parent_id = self._args[0]) - return (detail.getTitle(), detail.getFields()) - except: - return ([], '') - - def get(self): - if len(self._args) > 1: - title, fields = self.processDetail() - else: - # Convert to unicode fields (ugettext_lazy needs to be rendered before passing it to Json - title = self.title - fields = self.fields # Always add id column as invisible - + def type_as_dict(self, type_): + return { 'name' : _(type_.name()), + 'type' : type_.type(), + 'description' : _(type_.description()), + 'icon' : type_.icon().replace('\n', '') + } + + def processTableFields(self, title, fields): processedFields = [{ 'id' : {'visible': False, 'sortable': False, 'searchable': False } }] for f in fields: @@ -314,3 +111,265 @@ class ModelTableHandlerMixin(object): processedFields.append({k1: dct}) return { 'title': unicode(title), 'fields': processedFields }; + +# 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: + [path] --> get Items (all hopefully, this call is delegated to getItems) + [path]/overview + [path]/ID + [path]/gui + [path]/TYPE/gui + [path]/types + [path]/types/TYPE + [path]/tableinfo + [path].... --> + ''' + def __init__(self, parentHandler, path, *args, **kwargs): + self._parent = parentHandler + self._path = path + self._args = args + self._kwargs = kwargs + + def get(self): + # Process args + logger.debug("Detail args: {0}".format(self._args)) + nArgs = len(self._args) + parent = self._kwargs['parent'] + if nArgs == 0: + return self.getItems(parent, None) + + if nArgs == 1: + if self._args[0] == OVERVIEW: + return self.getItems(parent, None) + elif self._args[0] == 'gui': + return self.getGui(parent, None) + elif self._args[0] == 'types': + return self.getTypes(parent, None) + elif self._args[0] == 'tableinfo': + return self.processTableFields(self.getTitle(parent), self.getFields(parent)) + + raise RequestError('Invalid request') + + #return self.getItems(parent, self._args[0]) + + if nArgs == 2: + if self._args[1] == 'gui': + return self.getGui(parent, self._args[0]) + elif self._args[1] == 'types': + return self.getTypes(parent, self._args[1]) + + return self.fallbackGet() + + # Invoked if default get can't process request + def fallbackGet(self): + raise RequestError('Invalid request') + + # Default (as sample) getItems + def getItems(self, parent, item): + if item is None: + return [] + return {} + + # A detail handler must also return title & fields for tables + def getTitle(self, parent): + return '' + + def getFields(self, parent): + return [] + + def getGui(self, parent, forType): + raise RequestError('Gui not provided') + + def getTypes(self, parent, forType): + return [] # Default is that details do not have types + +class ModelHandler(BaseModelHandler): + ''' + Basic Handler for a model + Basically we will need same operations for all models, so we can + take advantage of this fact to not repeat same code again and again... + + Urls treated are: + [path] --> Returns all elements for this path (including INSTANCE variables if it has it). (example: .../providers) + [path]/overview --> Returns all elements for this path, not including INSTANCE variables. (example: .../providers/overview) + [path]/ID --> Returns an exact element for this path. (example: .../providers/4) + [path/ID/DETAIL --> Delegates to Detail, if it has details. (example: .../providers/4/services/overview, .../providers/5/services/9/gui, .... + + Note: Instance variables are the variables declared and serialized by modules. + The only detail that has types within is "Service", child of "Provider" + ''' + # Authentication related + authenticated = True + needs_staff = True + # Which model does this manage + model = None + # If this model has details, which ones + detail = None # Dictionary containing detail routing + # Put needed fields + save_fields = [] + # Table info needed fields and title + 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 + + # 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 [] + + def getTypes(self, *args, **kwargs): + for type_ in self.enum_types(): + yield self.type_as_dict(type_) + + def getType(self, type_): + found = None + for v in self.getTypes(): + if v['type'] == type_: + found = v + break + + if found is None: + raise NotFound('type not found') + + logger.debug('Found type {0}'.format(v)) + return found + + # gui related + def getGui(self, type_): + raise RequestError('invalid request') + + # Helper to process detail + def processDetail(self): + logger.debug('Processing detail {0}'.format(self._path)) + try: + item = self.model.objects.filter(pk=self._args[0])[0] + detailCls = self.detail[self._args[1]] + args = list(self._args[2:]) + path = self._path + '/'.join(args[:2]) + detail = detailCls(self, path, *args, parent = item) + return getattr(detail, self._operation)() + except: + raise NotFound('method not found') + + def get(self): + logger.debug('method GET for {0}, {1}'.format(self.__class__.__name__, self._args)) + nArgs = len(self._args) + if nArgs == 0: + result = [] + for val in self.model.objects.all(): + res = self.item_as_dict(val) + self.__fillIntanceFields(val, res) + result.append(res) + return result + + if nArgs == 1: + if self._args[0] == OVERVIEW: + return list(self.getItems()) + elif self._args[0] == TYPES: + return list(self.getTypes()) + elif self._args[0] == TABLEINFO: + return self.processTableFields(self.table_title, self.table_fields) + + # get item ID + try: + val = self.model.objects.get(pk=self._args[0]) + res = self.item_as_dict(val) + self.__fillIntanceFields(val, res) + return res + except: + raise NotFound('item not found') + + # nArgs > 1 + # Request type info + if self._args[0] == TYPES: + if nArgs != 2: + raise RequestError('invalid request') + return self.getType(self._args[1]) + elif self._args[0] == GUI: + if nArgs != 2: + raise RequestError('invalid request') + gui = self.getGui(self._args[1]) + return sorted(gui, key=lambda f: f['gui']['order']) + + # If has detail and is requesting detail + if self.detail is not None: + return self.processDetail() + + raise RequestError('invalid request') + + 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: + if len(self._args) == 0: # create new + item = self.model.objects.create(**args); + elif len(self._args) == 1: + # 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') + except IntegrityError: # Duplicate key probably + raise RequestError('Element already exists (duplicate key error)') + except Exception as e: + 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() + + res = self.item_as_dict(item) + + self.__fillIntanceFields(item, res) + + item.save() + + 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: + raise RequestError('Delete need one and only one argument') + try: + item = self.model.objects.get(pk=self._args[0]); + 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.js b/server/src/uds/static/adm/js/api.js index 9adee01e..b887c414 100644 --- a/server/src/uds/static/adm/js/api.js +++ b/server/src/uds/static/adm/js/api.js @@ -172,6 +172,7 @@ function BasicModelRest(path, options) { this.putPath = options.putPath || path; this.delPath = options.delPath || path; this.typesPath = options.typesPath || (path + '/types'); + this.guiPath = options.guiPath || (path + '/gui'); this.tableInfoPath = options.tableInfoPath || (path + '/tableinfo'); this.cache = api.cache('bmr'+path); } @@ -303,7 +304,7 @@ BasicModelRest.prototype = { types : function(success_fnc, fail_fnc) { "use strict"; return this._requestPath(this.typesPath, { - cacheKey: 'type', + cacheKey: this.typesPath, success: success_fnc, }); }, @@ -313,9 +314,15 @@ BasicModelRest.prototype = { // 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"; - var path = [this.typesPath, typeName, 'gui'].join('/'); + + var path; + if( typeName !== undefined ) { + path = [this.guiPath, typeName].join('/'); + } else { + path = this.guiPath; + } return this._requestPath(path, { - cacheKey: typeName + '-gui', + cacheKey: path, success: success_fnc, fail: fail_fnc, }); @@ -345,8 +352,9 @@ function DetailModelRestApi(parentApi, parentId, model, options) { this.options = options; this.base = new BasicModelRest(undefined, { getPath: [parentApi.path, parentId, model].join('/'), - typesPath: '.', // We do not has this on details - tableInfoPath: [parentApi.path, 'tableinfo', parentId, model].join('/'), + typesPath: [parentApi.path, parentId, model, 'types'].join('/'), // Proably this will return nothing + guiPath: [parentApi.path, parentId, model].join('/'), // Proably this will return nothing + tableInfoPath: [parentApi.path, parentId, model, 'tableinfo'].join('/'), }); } @@ -380,6 +388,10 @@ DetailModelRestApi.prototype = { return this.base.types(success_fnc, fail_fnc); } }, + gui: function(typeName, success_fnc, fail_fnc) { // Typename can be "undefined" to request MAIN gui (it it exists ofc..) + "use strict"; + return this.base.gui(typeName, 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 494ac6b5..8c275044 100644 --- a/server/src/uds/static/adm/js/gui-definition.js +++ b/server/src/uds/static/adm/js/gui-definition.js @@ -46,9 +46,9 @@ gui.providers.link = function(event) { var id = selected[0].id; // Options for detail, to initialize types correctly var detail_options = { - types: function(success_fnc, fail_fnc) { + /* types: function(success_fnc, fail_fnc) { success_fnc(selected[0].offers); - } + }*/ }; // Giving the name compossed with type, will ensure that only styles will be reattached once var services = new GuiElement(api.providers.detail(id, 'services', detail_options), 'services-'+selected[0].type); diff --git a/server/src/uds/static/adm/js/gui-element.js b/server/src/uds/static/adm/js/gui-element.js index a591656a..381b1ebc 100644 --- a/server/src/uds/static/adm/js/gui-element.js +++ b/server/src/uds/static/adm/js/gui-element.js @@ -105,7 +105,7 @@ GuiElement.prototype = { // Icon renderer, based on type (created on init methods in styles) var renderTypeIcon = function(data, type, value){ if( type == 'display' ) { - self.types[value.type] = self.types[value.type] || {} + self.types[value.type] = self.types[value.type] || {}; var css = self.types[value.type].css || 'fa fa-asterisk'; return ' ' + renderEmptyCell(data); } else {