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:
Adolfo Gómez 2013-11-28 11:55:40 +00:00
parent 7ea7086ba9
commit c98e964bba
13 changed files with 439 additions and 370 deletions

View File

@ -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

View File

@ -153,32 +153,18 @@ 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))
# Initializes the dispatchers
@staticmethod
def initialize():
'''
This imports all packages that are descendant of this package, and, after that,
it register all subclases of Handler. (In fact, it looks for packages inside "methods" package, child of this)
'''
import os.path, pkgutil
import sys
# Dinamycally import children of this package. The __init__.py files must register, if needed, inside ServiceProviderFactory
package = 'methods'
pkgpath = os.path.join(os.path.dirname(sys.modules[__name__].__file__), package)
for _, name, _ in pkgutil.iter_modules([pkgpath]):
__import__(__name__ + '.' + package + '.' + name, globals(), locals(), [], -1)
for cls in Handler.__subclasses__(): # @UndefinedVariable
# Skip ClusteredServiceProvider
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:
@ -194,5 +180,28 @@ class Dispatcher(View):
service_node[name] = { '' : None }
service_node[name][''] = cls
else:
Dispatcher.registerSubclasses(cls.__subclasses__())
# Initializes the dispatchers
@staticmethod
def initialize():
'''
This imports all packages that are descendant of this package, and, after that,
it register all subclases of Handler. (In fact, it looks for packages inside "methods" package, child of this)
'''
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'
pkgpath = os.path.join(os.path.dirname(sys.modules[__name__].__file__), package)
for _, name, _ in pkgutil.iter_modules([pkgpath]):
__import__(__name__ + '.' + package + '.' + name, globals(), locals(), [], -1)
Dispatcher.registerSubclasses(Handler.__subclasses__()) # @UndefinedVariable
Dispatcher.initialize()

View File

@ -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,21 +47,16 @@ logger = logging.getLogger(__name__)
# Enclosed methods under /auth path
class Authenticators(ModelHandlerMixin, Handler):
class Authenticators(ModelHandler):
model = Authenticator
detail = { 'users': Users, 'groups':Groups }
def item_as_dict(self, auth):
type_ = auth.getType()
return { 'id': auth.id,
'name': auth.name,
'users_count': auth.users.count(),
'type': type_.type(),
'comments': auth.comments,
}
class Types(ModelTypeHandlerMixin, Handler):
path = 'authenticators'
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()
@ -72,14 +67,12 @@ class Types(ModelTypeHandlerMixin, Handler):
except:
raise NotFound('type not found')
def item_as_dict(self, auth):
type_ = auth.getType()
return { 'id': auth.id,
'name': auth.name,
'users_count': auth.users.count(),
'type': type_.type(),
'comments': auth.comments,
}
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'}}
]

View File

@ -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,28 +43,19 @@ 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,
'name': item.name,
'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'}}
]

View File

@ -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,9 +44,16 @@ 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()
return { 'id': osm.id,
@ -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'}}
]

View File

@ -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'}},
]

View File

@ -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'])

View File

@ -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,25 +45,17 @@ 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(),
}

View File

@ -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

View File

@ -35,168 +35,20 @@ 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 few constants
OVERVIEW = 'overview'
TYPES = 'types'
TABLEINFO = 'tableinfo'
GUI = 'gui'
# A detail handler must also return title & fields for tables
def getTitle(self):
return ''
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_)
# Base for Gui Related mixins
class BaseModelHandler(Handler):
def addField(self, gui, field):
gui.append({
@ -238,69 +90,14 @@ class ModelTypeHandlerMixin(object):
})
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'

View File

@ -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);
},
};

View File

@ -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);

View File

@ -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 {