mirror of http://localhost:9400/cloud/cms
parent
cbf63d3f56
commit
d9bf641a24
@ -0,0 +1,352 @@
|
|||||||
|
"""
|
||||||
|
This file will describe how a site is loaded
|
||||||
|
"""
|
||||||
|
from cms.engine.config.structure import Layout, System
|
||||||
|
from cms import disk, cloud
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
from jinja2 import Environment, BaseLoader, FileSystemLoader
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class IOConfig:
|
||||||
|
"""
|
||||||
|
This class encapsulates read/write for configuration
|
||||||
|
"""
|
||||||
|
def __init__(self,**_args):
|
||||||
|
self._config = {'system':{},'layout':{},'plugins':{}}
|
||||||
|
self._caller = None
|
||||||
|
self._location= _args['location'] if 'location' in _args else None
|
||||||
|
self._logs = []
|
||||||
|
def get(self,_key) :
|
||||||
|
if not _key :
|
||||||
|
return self._config
|
||||||
|
_keys = _key.split('.') if '.' in _key else [_key]
|
||||||
|
_value= None
|
||||||
|
_object = copy.deepcopy(self._config)
|
||||||
|
for _key in _keys :
|
||||||
|
if _key in _object :
|
||||||
|
_object = _object[_key]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return _object
|
||||||
|
def set(self,_expr,value):
|
||||||
|
if len(_expr.split('.')) == 1 :
|
||||||
|
self._config[_expr] = value
|
||||||
|
else:
|
||||||
|
_object = self._config
|
||||||
|
for _attr in _expr.split('.')[:-1] :
|
||||||
|
if _attr not in _object :
|
||||||
|
_object[_attr] = {}
|
||||||
|
_object = _object[_attr]
|
||||||
|
_attr = _expr.split('.')[-1]
|
||||||
|
_object[_attr] = value
|
||||||
|
|
||||||
|
def log(self,**_args):
|
||||||
|
|
||||||
|
_row = dict({'_date':datetime.now().strftime('%Y-%m-%d'),'time':datetime.now().strftime('%H:%M:%S'), 'context':self.get('system.context')},**_args)
|
||||||
|
self._logs.append(_row)
|
||||||
|
|
||||||
|
def debug(self,**_args):
|
||||||
|
return self._logs,'application/json'
|
||||||
|
|
||||||
|
|
||||||
|
class Initialization (IOConfig):
|
||||||
|
def __init__(self,**_args):
|
||||||
|
super().__init__(**_args)
|
||||||
|
self._config = self.read_config(**_args)
|
||||||
|
self._args = _args
|
||||||
|
#
|
||||||
|
# Invoke initialization
|
||||||
|
self.reload()
|
||||||
|
|
||||||
|
def reload (self):
|
||||||
|
_args = self._args
|
||||||
|
|
||||||
|
self._caller = _args['caller'] if 'caller' in _args else None
|
||||||
|
if (self._caller):
|
||||||
|
self._config['system']['parentContext'] = ''
|
||||||
|
if not self.get('system.icon') :
|
||||||
|
self._config['system']['icon'] = None
|
||||||
|
|
||||||
|
self._config['layout']['location'] = None
|
||||||
|
if not self.get('layout.menu') :
|
||||||
|
self._config['layout']['menu'] = {}
|
||||||
|
|
||||||
|
# self.context(**_args)
|
||||||
|
self.locations(**_args)
|
||||||
|
self.menu()
|
||||||
|
self.plugins()
|
||||||
|
def read_config(self,**_args) :
|
||||||
|
_config = {}
|
||||||
|
if 'path' in _args :
|
||||||
|
f = open(_args['path'])
|
||||||
|
_config = json.loads(f.read())
|
||||||
|
f.close()
|
||||||
|
elif 'stream' in _args :
|
||||||
|
_stream = _args['stream']
|
||||||
|
if type(_stream) == 'str' :
|
||||||
|
_config = json.loads(_stream)
|
||||||
|
elif type(_stream) == io.StringIO :
|
||||||
|
_config = json.loads( _stream.read())
|
||||||
|
_name = self._caller.__name__ if self._caller else None
|
||||||
|
self.log(action='init.config',module='read_config',input={'caller':_name})
|
||||||
|
return _config
|
||||||
|
def context(self,**_args):
|
||||||
|
"""
|
||||||
|
Updating the context so that we can have a consistent representation
|
||||||
|
"""
|
||||||
|
if ('context' in _args):
|
||||||
|
self.set('system.context',_args['context'])
|
||||||
|
|
||||||
|
pass
|
||||||
|
_context = self.get('system.context')
|
||||||
|
|
||||||
|
if self._caller :
|
||||||
|
#
|
||||||
|
# There is a parent context we need to account and we are updating the current context to reflect the caller context
|
||||||
|
_parentContext = self._caller.get('system.context')
|
||||||
|
_context = '/'.join([self._caller.get('system.context'),_context])
|
||||||
|
#
|
||||||
|
# updating the configuratioin
|
||||||
|
_iconURI = '/'.join(["",_parentContext,self._caller.get('system.icon')])
|
||||||
|
|
||||||
|
# print ([self._caller.get('system.context'), _parentContext,_context])
|
||||||
|
if self._caller.get('system.context') != '':
|
||||||
|
_parentContext = "/"+_parentContext
|
||||||
|
_context = "/"+_context
|
||||||
|
self.set('system.onport',0)
|
||||||
|
self._config['system']['onport'] = 0
|
||||||
|
else:
|
||||||
|
_iconURI = _iconURI.replace("///","/")
|
||||||
|
self.set('system.onport',1)
|
||||||
|
self._config['system']['onport'] = 1
|
||||||
|
self.set('system.caller',{'icon':_iconURI,'context':self._caller.get('system.context')})
|
||||||
|
|
||||||
|
# _context = _context.replace('/','')
|
||||||
|
else:
|
||||||
|
#
|
||||||
|
# If we have no parent site, there should be no parent context (or empty string)
|
||||||
|
_parentContext = _context #@TODO: should be none ?
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Updating context so it is usable throughout the code base
|
||||||
|
if self._caller:
|
||||||
|
self.set('system.parentContext', _parentContext.strip())
|
||||||
|
self.set('system.context',_context.strip())
|
||||||
|
# self._config['system']['context'] = _context.strip()
|
||||||
|
self._config['system']['parentContext'] = _parentContext
|
||||||
|
p = {'has_caller':self._caller != None,'context':_context}
|
||||||
|
self.log(action='init.context',module='context',input=p)
|
||||||
|
#
|
||||||
|
# loosly context to a certain extent involves locations of icons and document root
|
||||||
|
#
|
||||||
|
|
||||||
|
self.locations(**_args)
|
||||||
|
|
||||||
|
#
|
||||||
|
# submit log of status
|
||||||
|
|
||||||
|
def locations(self,**_args):
|
||||||
|
"""
|
||||||
|
We are updating the icons, project location file and security key (if any), we need to make sure that context is updated first
|
||||||
|
"""
|
||||||
|
#
|
||||||
|
# updating location & path of the application
|
||||||
|
# if self.get('system.caller') :
|
||||||
|
# return
|
||||||
|
_context = self.get('system.context')
|
||||||
|
_logo = self.get('system.logo')
|
||||||
|
_root = self.get('layout.root')
|
||||||
|
if self.get('system.source.id') == 'cloud' :
|
||||||
|
_icon = f'{_context}/api/cloud/download?doc={_logo}'
|
||||||
|
else:
|
||||||
|
_icon = f'{_context}/api/disk/read?uri={_logo}'
|
||||||
|
# _root = f'{_context}/api/disk/read?uri={_root}'
|
||||||
|
|
||||||
|
# self.set('layout.root',_root)
|
||||||
|
self.set('system.icon',_icon)
|
||||||
|
self.set('system.logo',_icon)
|
||||||
|
#
|
||||||
|
# from the path provided we can determine the location of the project
|
||||||
|
_homefolder = os.sep.join(_args['path'].split(os.sep)[:-1])
|
||||||
|
if _homefolder and os.path.exists(os.sep.join([_homefolder,_root])) :
|
||||||
|
self.set('layout.location',_homefolder)
|
||||||
|
|
||||||
|
#
|
||||||
|
# updating the security key and trying to determine the location
|
||||||
|
if self.get('system.source.key') :
|
||||||
|
_path = self.get('system.source.key')
|
||||||
|
f = None
|
||||||
|
if os.path.exists(_path) :
|
||||||
|
f = open(_path)
|
||||||
|
elif os.path.exists(os.sep.join([self.get('layout.location'),_path])) :
|
||||||
|
f = open(os.sep.join([self.get('layout.location'),_path]))
|
||||||
|
if f :
|
||||||
|
self.set('system.source.key',f.read())
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def menu (self,**_args):
|
||||||
|
"""
|
||||||
|
This function will build the menu of the site (if need be)
|
||||||
|
"""
|
||||||
|
_handler = cloud if self.get('system.source.id') == 'cloud' else disk
|
||||||
|
_object = _handler.build(self._config)
|
||||||
|
|
||||||
|
_overwrite = self.get('layout.overwrite')
|
||||||
|
for _name in _object :
|
||||||
|
_submenu = _object[_name]
|
||||||
|
_index = 0
|
||||||
|
for _item in _submenu :
|
||||||
|
_text = _item['text'].strip()
|
||||||
|
if _text in _overwrite :
|
||||||
|
if 'uri' in _item and 'url' in _overwrite :
|
||||||
|
del _item['uri']
|
||||||
|
_item = dict(_item,**_overwrite[_text])
|
||||||
|
_submenu[_index] = _item
|
||||||
|
_index += 1
|
||||||
|
self.set('layout.menu',_object)
|
||||||
|
# self._config['layout']['menu'] = _object
|
||||||
|
self.routes() #-- other apps/sites integrated in case we are operating in portal mode
|
||||||
|
#
|
||||||
|
# At this point the entire menu is build and we need to have it sorted
|
||||||
|
self.order()
|
||||||
|
# _labels = list(self.get('layout.menu').keys())
|
||||||
|
# self.log(action='init.menu',module='menu',input=_labels)
|
||||||
|
# print (self.get('layout.menu'))
|
||||||
|
# print (_object)
|
||||||
|
def order(self,**_args):
|
||||||
|
if self.get('layout.order.menu') :
|
||||||
|
_sorted = {}
|
||||||
|
_menu = self.get('layout.menu')
|
||||||
|
_ordered = self.get('layout.order.menu')
|
||||||
|
for _name in _ordered :
|
||||||
|
if _name in _menu :
|
||||||
|
_sorted[_name] = _menu[_name]
|
||||||
|
if _sorted :
|
||||||
|
_menu = _sorted
|
||||||
|
_missing = list(set(_menu.keys()) - set(_sorted))
|
||||||
|
if _missing :
|
||||||
|
for _name in _missing :
|
||||||
|
_menu[_name] = _menu[_name]
|
||||||
|
self.set('layout.menu',_menu)
|
||||||
|
def routes (self,**_args):
|
||||||
|
"""
|
||||||
|
This method will update menu with dependent sites/apps and add them to the menu
|
||||||
|
"""
|
||||||
|
_overwrite = self.get('layout.overwrite')
|
||||||
|
_routes = self.get('system.routes')
|
||||||
|
_context = self.get('system.context')
|
||||||
|
_menu = self.get('layout.menu')
|
||||||
|
|
||||||
|
if _routes :
|
||||||
|
for _text in _routes :
|
||||||
|
_item = _routes[_text]
|
||||||
|
if 'menu' in _item :
|
||||||
|
_uri = f'{_context}/{_text}'
|
||||||
|
_label = _item['menu']
|
||||||
|
if _text in _overwrite :
|
||||||
|
_text = _overwrite[_text]['text']
|
||||||
|
if _label not in _menu :
|
||||||
|
_menu[_label] = []
|
||||||
|
_menu[_label].append({"text":_text,"uri":_uri,"type":"open"})
|
||||||
|
#
|
||||||
|
# updating menu ...
|
||||||
|
self.set('layout.menu',_menu)
|
||||||
|
# self._config['layout']['menu'] = _menu
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
def plugins (self,**_args):
|
||||||
|
"""
|
||||||
|
This function will load the plugins from disk (only)
|
||||||
|
"""
|
||||||
|
_folder = os.sep.join([self.get('layout.location'),self.get('layout.root'),'_plugins'])
|
||||||
|
_context = self.get('system.context')
|
||||||
|
_parentContext = self.get('system.parentContext')
|
||||||
|
_map = {}
|
||||||
|
_plugins = {}
|
||||||
|
if self.get('system.source.id') == 'cloud' :
|
||||||
|
_plugins = cloud.plugins(_context)
|
||||||
|
else:
|
||||||
|
_plugins = disk.plugins(context=_context)
|
||||||
|
_uri = 'api/system/debug'
|
||||||
|
_uri = _uri if not _context else f'{_context}/{_uri}'
|
||||||
|
_plugins[_uri] = self.debug
|
||||||
|
if os.path.exists(_folder) and self.get('plugins'):
|
||||||
|
|
||||||
|
_items = self.get('plugins')
|
||||||
|
|
||||||
|
for _filename in _items:
|
||||||
|
_path = os.sep.join([_folder,_filename+'.py'])
|
||||||
|
|
||||||
|
if os.path.exists(_path) :
|
||||||
|
for _module in _items[_filename] :
|
||||||
|
_pointer = disk.plugins(path=_path,name=_module,context=_context)
|
||||||
|
if _pointer :
|
||||||
|
_uri = f"api/{_filename}/{_module}"
|
||||||
|
_uri = f"{_context}/{_uri}" if _context else _uri
|
||||||
|
_map[_uri] = _pointer
|
||||||
|
if _parentContext :
|
||||||
|
# _uri = f"{_parentContext}/{_context}"
|
||||||
|
_map[_uri] = _pointer
|
||||||
|
|
||||||
|
self.log(action='load.plugin',module='plugins',input={'uri':_uri,'path':_path,'name':_module})
|
||||||
|
else:
|
||||||
|
self.log(action='missing.function',module='plugins',input={'name':_module,'path':_path})
|
||||||
|
|
||||||
|
else:
|
||||||
|
#
|
||||||
|
# log the error ...
|
||||||
|
self.log(action='missing.plugins',module='plugins',input=_path)
|
||||||
|
#
|
||||||
|
# Updating plugins from disk/cloud
|
||||||
|
_plugins = _map if not _plugins else dict(_plugins,**_map)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
self.set('plugins',_plugins)
|
||||||
|
self.log(action='init.plugins',module='plugins',input=list(_plugins.keys()))
|
||||||
|
|
||||||
|
class Site(Initialization) :
|
||||||
|
def __init__(self,**_args):
|
||||||
|
super().__init__(**_args)
|
||||||
|
self._config['system']['portal'] = (self.get('system.routes')) == None
|
||||||
|
def html(self,_uri,_id) :
|
||||||
|
_handler = cloud if self.get('system.source.id') == 'cloud' else disk
|
||||||
|
|
||||||
|
_html = _handler.html(_uri, self.get(None))
|
||||||
|
|
||||||
|
return " ".join([f'<div id="{_id}"> ',_html,"</div>"])
|
||||||
|
|
||||||
|
class QCMS:
|
||||||
|
def __init__(self,**_args):
|
||||||
|
_app = Site(**_args)
|
||||||
|
self._id = _app.get('system.context') #if _app.get('system.context') else 'main'
|
||||||
|
self._sites = {self._id:_app}
|
||||||
|
if _app.get('system.routes') :
|
||||||
|
_routes = _app.get('system.routes')
|
||||||
|
|
||||||
|
for _name in _routes :
|
||||||
|
_path = _routes[_name]['path']
|
||||||
|
self._sites[_name] = Site(context=_name,path=_path,caller=_app)
|
||||||
|
def render(self,_uri,_id,_appid=None):
|
||||||
|
_site = self._sites[_appid] if _appid else self._sites[self._id]
|
||||||
|
_args = {'layout':_site.get('layout')}
|
||||||
|
_system = _site.get('system')
|
||||||
|
for k in ['source','app'] :
|
||||||
|
if k in _system :
|
||||||
|
del _system[k]
|
||||||
|
_args['system'] = _system
|
||||||
|
_html = _site.html(_uri,_id)
|
||||||
|
|
||||||
|
_env = Environment(loader=BaseLoader()).from_string(_html)
|
||||||
|
_args[_id] = str(_env.render(**_args))
|
||||||
|
return _args
|
||||||
|
def set(self,_id):
|
||||||
|
self._id = _id
|
||||||
|
def get(self,_id=None):
|
||||||
|
|
||||||
|
return self._sites[self._id] if not _id else self._sites[_id]
|
Loading…
Reference in new issue