forked from shaba/openuds
Refactorized REST api (server) to be much more "coherent". URLs are much more coherent, we have fewer handlers to implement on models, fixed to register correctly methods, even when deriving from Handler, etc...
This gives better options to implement handlers based on models also
This commit is contained in:
parent
7ea7086ba9
commit
c98e964bba
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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'}}
|
||||
]
|
||||
|
@ -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'}}
|
||||
]
|
||||
|
@ -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'}}
|
||||
]
|
||||
|
@ -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'}},
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
@ -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'])
|
||||
|
@ -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(),
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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);
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 '<span class="' + css + '"></span> ' + renderEmptyCell(data);
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user