bug fixes: context, parent context, new css nomanclature

v3.0
Steve Nyemba 2 months ago
parent 72272abd3f
commit b05d66e4ee

@ -48,7 +48,7 @@ def favicon (app):
_site = _qcms.get (request) _site = _qcms.get (request)
# return _read(app,None,_site.get('system.icon')) # return _read(app,None,_site.get('system.icon'))
return redirect(_site.get('system.icon')[1:]) #_site.read(request,uri=_site.get('system.icon')) return redirect(_site.get('system.icon')) #_site.read(request,uri=_site.get('system.icon'))
@_app.route("/<_id>/robots.txt") @_app.route("/<_id>/robots.txt")
@_app.route("/robots.txt",defaults={'_id':None}) @_app.route("/robots.txt",defaults={'_id':None})
def robots_txt(_id): def robots_txt(_id):
@ -178,8 +178,9 @@ def logout():
@_app.route('/dialog',defaults={'app':None},methods=['POST','GET'], strict_slashes = False) @_app.route('/dialog',defaults={'app':None},methods=['POST','GET'], strict_slashes = False)
@_app.route('/<path:app>/dialog', methods=['POST','GET'], strict_slashes = False) @_app.route('/<path:app>/dialog', methods=['POST','GET'], strict_slashes = False)
@_app.route('/<path:app>/dialog/', methods=['POST','GET'], strict_slashes = False)
def _dialog(app): def _dialog(app):
global _qcms global _qcms
# _site = _qcms.get(app) # _site = _qcms.get(app)
_site = _qcms.get(request) _site = _qcms.get(request)
@ -210,6 +211,7 @@ def reload():
@_app.route("/",defaults={'file':None}, methods=['POST','GET','PUT'], strict_slashes = False) @_app.route("/",defaults={'file':None}, methods=['POST','GET','PUT'], strict_slashes = False)
@_app.route('/<path:file>',defaults={}, methods=['POST','GET','PUT'], strict_slashes = False) @_app.route('/<path:file>',defaults={}, methods=['POST','GET','PUT'], strict_slashes = False)
# @_app.route('/<app>/files/<path:file>', defaults=[{'module':None}]) # @_app.route('/<app>/files/<path:file>', defaults=[{'module':None}])
# @_app.route('/<app>/<module>/files/<path:file>') # @_app.route('/<app>/<module>/files/<path:file>')
def _read(file): def _read(file):
@ -218,7 +220,7 @@ def _read(file):
# apply security here (on the top level site only) # apply security here (on the top level site only)
# @TODO: upon failure, need to forward this to an error page ... # @TODO: upon failure, need to forward this to an error page ...
# #
_site = _qcms.get(None) _site = _qcms.get(request)
if _site.secure.allow(request=request) : if _site.secure.allow(request=request) :
return _qcms.delegate(request) return _qcms.delegate(request)
else: else:
@ -252,27 +254,27 @@ def _version ():
return _site.get('system.version') return _site.get('system.version')
@_app.route('/page',methods=['POST'],defaults={'app_id':None,'key':None}) # @_app.route('/page',methods=['POST'],defaults={'app_id':None,'key':None})
@_app.route('/<app_id>/page',methods=['POST'],defaults={'key':None}) # @_app.route('/<app_id>/page',methods=['POST'],defaults={'key':None})
@_app.route('/<app_id>/<key>/page',methods=['POST']) # @_app.route('/<app_id>/<key>/page',methods=['POST'])
def getPostedPage(app_id,key): # def getPostedPage(app_id,key):
""" # """
This function will return a page given a location parameter # This function will return a page given a location parameter
""" # """
global _qcms # global _qcms
_id = _getId(app_id,key) # _id = _getId(app_id,key)
_site = _qcms.get(_id) # _site = _qcms.get(_id)
_uri = request.headers['uri'] # _uri = request.headers['uri']
if 'dom' not in request.headers : # if 'dom' not in request.headers :
_id = _uri.split('/')[-1].split('.')[0] # _id = _uri.split('/')[-1].split('.')[0]
else: # else:
_id = request.headers['dom'] # _id = request.headers['dom']
if 'read?uri=' in _uri or 'download?doc=' in _uri : # if 'read?uri=' in _uri or 'download?doc=' in _uri :
_uri = _uri.split('=')[1] # _uri = _uri.split('=')[1]
_args = _qcms.render(_uri,_id,_getId(app_id,key)) #session.get(app_id,'main')) # _args = _qcms.render(_uri,_id,_getId(app_id,key)) #session.get(app_id,'main'))
return _args[_id],200 # return _args[_id],200
@_app.route('/page',defaults={'app_id':None,'resource':None}) @_app.route('/page',defaults={'app_id':None,'resource':None})
@_app.route('/<app_id>/page',defaults={'resource':None},methods=['GET']) @_app.route('/<app_id>/page',defaults={'resource':None},methods=['GET'])
@ -303,6 +305,7 @@ def start (
_args['location'] = path _args['location'] = path
_args['shared'] = True _args['shared'] = True
_qcms = cms.sites.QCMS(**_args) _qcms = cms.sites.QCMS(**_args)
_args = _qcms.get().get('system.app') _args = _qcms.get().get('system.app')

@ -1,5 +1,5 @@
__author__ = "Steve L. Nyemba" __author__ = "Steve L. Nyemba"
__version__= "3.0" __version__= "3.1"
__email__ = "steve@the-phi.com" __email__ = "steve@the-phi.com"
__license__=""" __license__="""
Copyright 2010 - 2024, Steve L. Nyemba, Vanderbilt University Medical Center Copyright 2010 - 2024, Steve L. Nyemba, Vanderbilt University Medical Center

@ -20,28 +20,58 @@ class RequestController :
def __init__(self,_route): def __init__(self,_route):
# self._plugins = _plugins # self._plugins = _plugins
self._routes = _route self._routes = _route
#
# let's build a map of routes i.e ends with / or not they should reference the same objects
# This allows us to gain has map performance without writing much code
self._map = {}
for _name in self._routes :
self._map[_name] = _name
self._map[f'/{_name}/'.replace('//','/')] = _name
self._map[f'/{_name}'.replace('//','/')] = _name
def isfile(self,request): def isfile(self,request):
# #
# call self.exists to determin if this is an actual file or not # call self.exists to determin if this is an actual file or not
return '.' in request.path return '.' in request.path
def isroute(self,request): def isroute(self,request):
# return '/'.join(request.path[1:].split('/')[:2]) in self._routes # return '/'.join(request.path[1:].split('/')[:2]) in self._routes
return self.get_route(request) in self._routes return self.get_route(request)
# p = self.get_route(request) in self._routes
return False # q = request.path != '' and request.path.split('/')[1] in self._routes
# _context = '' if request.path.strip() == '' else request.path.split('/')[1]
# print ([' ## ',q, q, request.path,_context])
# return p or q
def isapi(self,request): def isapi(self,request):
return 'api' in request.path return 'api' in request.path
def get_route(self,request): def get_route(self,request):
_items = request.path[1:].split('/') # _items = request.path[1:].split('/')
_items = _items if _items[-1] != '' else _items[:-1]
N = len(_items) + 1 # if not '.' in request.path :
_names = [] # print (self._routes)
for i in range(1, len(_items) + 1): # print (_items)
# if _items[0] in self._routes :
_names.append('/'.join(_items[:i])) # return _items[0]
# else:
_names=[_item for _item in _names if _item in self._routes] # return None
return _names[-1] if _names else None #
# We should find the appropriate route for any resource access
_uri = request.path
if _uri not in self._map :
_items = _uri.split('/')
_x = [_items[0]]
for _name in _items[1:] :
if '/'.join(_x+[_name]) in self._map :
_x.append(_name)
else:
break
#
#
if '/'.join(_x) in self._map :
self._map[_uri] = self._map['/'.join(_x)]
_uri = '/'.join(_x)
return self._map.get(_uri,None)
class IOConfig: class IOConfig:
""" """
@ -51,7 +81,7 @@ class IOConfig:
self._config = {'system':{},'layout':{},'plugins':{}} self._config = {'system':{},'layout':{},'plugins':{}}
self._caller = None self._caller = None
self._location= _args['location'] if 'location' in _args else None self._location= _args['location'] if 'location' in _args else None
self._logs = [] self._logs = []
def get(self,_key) : def get(self,_key) :
# #
@ -103,9 +133,12 @@ class Initialization (IOConfig):
# in case the path provided does NOT have the manifest # in case the path provided does NOT have the manifest
if 'path' in self._args and not os.path.isfile(self._args['path']) : if 'path' in self._args and not os.path.isfile(self._args['path']) :
# #
#
self._args['path'] = f"{self._args['path']}{os.sep}qcms-manifest.json" self._args['path'] = f"{self._args['path']}{os.sep}qcms-manifest.json"
# #
# Invoke initialization # Invoke initialization
self.reload() self.reload()
# #
if self._caller and self._caller.secure : if self._caller and self._caller.secure :
@ -129,7 +162,7 @@ class Initialization (IOConfig):
self._config['layout']['location'] = None self._config['layout']['location'] = None
if not self.get('layout.menu') : if not self.get('layout.menu') :
self._config['layout']['menu'] = {} self._config['layout']['menu'] = {}
self.context(**_args) self.context(**_args)
self.menu() self.menu()
@ -152,6 +185,53 @@ class Initialization (IOConfig):
self.log(action='init.config',module='read_config',input={'caller':_name}) self.log(action='init.config',module='read_config',input={'caller':_name})
return _config return _config
def context(self,**_args): def context(self,**_args):
"""
updating context and parent context
"""
_m = {}
_context = _args['context'] if 'context' in _args else self.get('system.context')
_parentContext = self.get('system.parentContext') if not self._caller else self.get('system.parentContext')
#
# Logging what we've done ...
#
if self._caller :
_x = self._caller.get('system.context').split('/') + [_context]
_context = f'{self._caller.get("system.context")}/{_context}/'.replace('//','/')
_parentContext= f'/{self._caller.get("system.context")}'.replace('//','/')
if _parentContext == '/' :
#
# This is the case when the site is the actual website (e.g: https://foo.com)
_parentContext = ''
_parentIcon = self._caller.get('system.icon')
_parentIcon = _parentIcon if _parentIcon.startswith('/') else f'/{_parentIcon}'
self.set('system.parentContext',_parentContext)
self.set('system.caller',{'icon':_parentIcon,'context':_parentContext})
else:
_context = f'/{_context}/' if _context not in ['','/'] else _context
_parentContext = _context
_context = _context.replace('//','/')
_parentContext = _parentContext.replace('//','/') if _parentContext else ''
self.set('system.context',_context)
self.set('system.parentContext',_parentContext)
# self.set('system.parentContext',_parentContext)
#
# If for whatever reason the main page has context and ends with forward slash
# we need to update the expression of context
#
# request = _args['request']
self.log(action='init.context',module='context',input={'context':_context, 'parentContext':_parentContext})
#
# loosly context to a certain extent involves locations of icons and document root
#
self.locations(**_args)
def _context(self,**_args):
""" """
Updating the context so that we can have a consistent representation Updating the context so that we can have a consistent representation
""" """
@ -180,6 +260,8 @@ class Initialization (IOConfig):
self._config['system']['onport'] = 0 self._config['system']['onport'] = 0
else: else:
_iconURI = _iconURI.replace("///","/") _iconURI = _iconURI.replace("///","/")
self.set('system.onport',1) self.set('system.onport',1)
self._config['system']['onport'] = 1 self._config['system']['onport'] = 1
self.set('system.caller',{'icon':_iconURI,'context':self._caller.get('system.context')}) self.set('system.caller',{'icon':_iconURI,'context':self._caller.get('system.context')})
@ -199,6 +281,12 @@ class Initialization (IOConfig):
# self._config['system']['context'] = _context.strip() # self._config['system']['context'] = _context.strip()
self._config['system']['parentContext'] = _parentContext self._config['system']['parentContext'] = _parentContext
p = {'has_caller':self._caller != None,'context':_context} p = {'has_caller':self._caller != None,'context':_context}
#
# If for whatever reason the main page has context and ends with forward slash
# we need to update the expression of context
#
# request = _args['request']
self.log(action='init.context',module='context',input=p) self.log(action='init.context',module='context',input=p)
# #
# loosly context to a certain extent involves locations of icons and document root # loosly context to a certain extent involves locations of icons and document root
@ -225,13 +313,16 @@ class Initialization (IOConfig):
# else: # else:
# _icon = f'{_context}/api/disk/read?uri={_logo}' # _icon = f'{_context}/api/disk/read?uri={_logo}'
# _icon = f'{_context}/{_logo}'.replace(_root,'') # _icon = f'{_context}/{_logo}'.replace(_root,'')
_icon = f'{_context}/{_logo}' if _context else _logo _icon = f'/{_context}/{_logo}' if _context else _logo
self.set('system.icon',_icon) # self.set('system.icon',_icon)
self.set('system.logo',_icon) # self.set('system.logo',_icon)
self.set('system.icon',f'{_context}{_logo}') #f'/{_context}/{_logo}'.replace('//','/'))
self.set('system.logo',_logo)
# #
# from the path provided we can determine the location of the project # from the path provided we can determine the location of the project
_homefolder = os.sep.join(_args['path'].split(os.sep)[:-1]) _homefolder = os.sep.join(_args['path'].split(os.sep)[:-1])
@ -322,13 +413,13 @@ class Initialization (IOConfig):
""" """
This function will update public facing api provided This function will update public facing api provided
""" """
_context = self.get('system.context') # _context = self.get('system.context')
_plugins = self.get('plugins') _plugins = self.get('plugins')
for _key in ['debug','log'] : for _key in ['debug','log'] :
_pointer = getattr(self,_key) _pointer = getattr(self,_key)
_uri = f'{_context}/api/system/{_key}' _uri = f'api/system/{_key}'
if _uri[0] == '/' : # if _uri[0] == '/' :
_uri = _uri[1:] # _uri = _uri[1:]
_plugins[_uri] = _pointer _plugins[_uri] = _pointer
self.set('plugins',_plugins) self.set('plugins',_plugins)
@ -365,10 +456,10 @@ class Initialization (IOConfig):
_pointer = disk.plugins(path=_path,name=_module,context=_context) _pointer = disk.plugins(path=_path,name=_module,context=_context)
if _pointer : if _pointer :
_uri = f"api/{_filename}/{_module}" _uri = f"api/{_filename}/{_module}"
_uri = f"{_context}/{_uri}" if _context else _uri # _uri = f"{_context}/{_uri}" if _context else _uri
if (_uri.startswith("/")) : if (_uri.startswith("/")) :
_uri = _uri[1:] _uri = _uri[1:]
_uri.replace('//','/')
_map[_uri] = _pointer _map[_uri] = _pointer
if _parentContext : if _parentContext :
# _uri = f"{_parentContext}/{_context}" # _uri = f"{_parentContext}/{_context}"
@ -402,6 +493,7 @@ class Site(Initialization) :
self._routes = [] self._routes = []
if self.get('system.routes') : if self.get('system.routes') :
self._routes = list(self.get('system.routes').keys()) self._routes = list(self.get('system.routes').keys())
self.log(action='init.routes',modules='site',input=self._routes)
if self._caller : if self._caller :
self._routes = self._caller._routes self._routes = self._caller._routes
self.inspect = RequestController(self._routes) self.inspect = RequestController(self._routes)
@ -422,6 +514,7 @@ class Site(Initialization) :
# path.append(uri) # path.append(uri)
# return os.path.exists( os.sep.join(path)) # return os.path.exists( os.sep.join(path))
return os.path.exists(uri) return os.path.exists(uri)
def mimeType(self,uri): def mimeType(self,uri):
@ -439,13 +532,13 @@ class Site(Initialization) :
return _mimeType return _mimeType
def path(self,_uri): def path(self,_uri):
path = [] path = []
if self.get('layout.location'): if self.get('layout.location'):
path.append(self.get('layout.location')) path.append(self.get('layout.location'))
if self.get('layout.root') not in _uri : if self.get('layout.root') not in _uri :
path.append(self.get('layout.root')) path.append(self.get('layout.root'))
path.append(_uri) path.append(_uri)
return os.sep.join(path) return os.sep.join(path)
def html (self,_request): def html (self,_request):
_uri = self.uri(_request) _uri = self.uri(_request)
@ -532,6 +625,9 @@ class Site(Initialization) :
# _content = io.BytesIO(f.read()) # _content = io.BytesIO(f.read())
# f.close() # f.close()
#
# maybe the path needs some massage ...
_content = self.open(uri=self.path(_uri),mode='rb') _content = self.open(uri=self.path(_uri),mode='rb')
_content = io.BytesIO( _content ) _content = io.BytesIO( _content )
@ -565,6 +661,17 @@ class Site(Initialization) :
_kwargs = {'layout':self.get('layout')} _kwargs = {'layout':self.get('layout')}
_system = self.get('system') _system = self.get('system')
#
# we are going to correct an issue with context given that the initial url (portal) can end with forward slash
# This happens when using proxy-forwarding
_context = _system['context']
# if not self._caller :
# _system['context'] = f'/{_context}/'.replace('//','/')
# _system['parentContext'] = f'/{_context}/'.replace('//','/')
# else:
# _system['parentContext'] = f'/{self._caller.get("system.context")}/'
for k in ['source','app'] : for k in ['source','app'] :
if k in _system : if k in _system :
del _system[k] del _system[k]
@ -593,10 +700,15 @@ class Site(Initialization) :
_data = f"<div align='center' style='padding:20%'><div style='font-size:32px; font-weight:bold'>404</div style='font-weight:lighter'><div>{_request.path} Not Found</div></div>" _data = f"<div align='center' style='padding:20%'><div style='font-size:32px; font-weight:bold'>404</div style='font-weight:lighter'><div>{_request.path} Not Found</div></div>"
_mimeType = 'text/html' _mimeType = 'text/html'
_code = 404 _code = 404
_key = _request.path[1:] #if self.get('system.context') != '' else _request.path[1:] # _key = _request.path[1:] #if self.get('system.context') != '' else _request.path[1:]
_index = _request.path.index('api')
_key = _request.path[_index:]
if _plugins and _key in _plugins: if _plugins and _key in _plugins:
_mimeType = 'application/octet-stream' _mimeType = 'application/octet-stream'
_pointer = _plugins.get(_key) _pointer = _plugins.get(_key)
if hasattr(_pointer,'mimetype'): if hasattr(_pointer,'mimetype'):
_mimeType = _pointer.mimetype _mimeType = _pointer.mimetype
_data = _pointer(request=_request,config=self.get(None)) _data = _pointer(request=_request,config=self.get(None))
@ -612,6 +724,7 @@ class Site(Initialization) :
elif type(_data) in [dict,list] : elif type(_data) in [dict,list] :
_data = json.dumps(_data) _data = json.dumps(_data)
# # # #
# # return the ata # # return the ata
_code = _code if not _data else 200 _code = _code if not _data else 200
@ -627,9 +740,9 @@ class QCMS:
self._id = _app.get('system.context') #if _app.get('system.context') else 'main' self._id = _app.get('system.context') #if _app.get('system.context') else 'main'
# if self._id == '' : # if self._id == '' :
# self._id = '/' # self._id = '/'
self._sites = { self._id:_app, '':_app,'/':_app}
self._sites = {self._id:_app, '/':_app}
self._routes = [] self._routes = []
if _app.get('system.routes') : if _app.get('system.routes') :
_routes = _app.get('system.routes') _routes = _app.get('system.routes')
@ -638,13 +751,14 @@ class QCMS:
_path = _routes[_name]['path'] _path = _routes[_name]['path']
self._sites[_name] = Site(context=_name,path=_path,caller=_app) self._sites[_name] = Site(context=_name,path=_path,caller=_app)
# self._sites[f'{_name}/'] = self._sites[_name] # self._sites[f'/{_name}/'.replace('//','/')] = self._sites[_name]
self.inspect = RequestController(self._routes) self.inspect = RequestController(self._routes)
def _render(self,request): def _render(self,request):
_site = self.get(request) _site = self.get(request)
_args = _site.render(request=request,id='index') _args = _site.render(request=request,id='index')
return render_template('index.html',**_args) return render_template('index.html',**_args)
def _read(self,request): def _read(self,request):
@ -666,7 +780,10 @@ class QCMS:
return _content,200,{'Content-Type':_mimeType} return _content,200,{'Content-Type':_mimeType}
if isapi : if isapi :
_site = self.get(request) _site = self.get(request)
return _site.run(request)
return _site.run(request)
def get(self,request=None) : def get(self,request=None) :
if request : if request :

@ -103,3 +103,5 @@ qcms.code.documentation= function(_uri,_id) {
$(this._id).find('*.child').show() //css({display:'block'}) $(this._id).find('*.child').show() //css({display:'block'})
} }
} }

@ -4,6 +4,7 @@
</div> </div>
<div> <div>
<div class="qcms-title">{{layout.header.title}}</div> <div class="qcms-title">{{layout.header.title}}</div>
<div class="qcms-subtitle">{{layout.header.subtitle}}</div> <div class="qcms-subtitle">{{layout.header.subtitle}}</div>
</div> </div>

@ -1,4 +1,6 @@
<link href="{{system.parentContext}}/static/css/icons.css" rel="stylesheet" type="text/css">
<link href="{{system.parentContext}}/static/css/icons.css" rel="stylesheet" type="text/css">
<link href="{{system.parentContext}}/static/css/source-code.css" rel="stylesheet" type="text/css"> <link href="{{system.parentContext}}/static/css/source-code.css" rel="stylesheet" type="text/css">
<link href="{{system.parentContext}}/static/css/search.css" rel="stylesheet" type="text/css"> <link href="{{system.parentContext}}/static/css/search.css" rel="stylesheet" type="text/css">
<link href="{{system.parentContext}}/static/css/dialog.css" rel="stylesheet" type="text/css"> <link href="{{system.parentContext}}/static/css/dialog.css" rel="stylesheet" type="text/css">
@ -41,4 +43,4 @@
<!-- <script src="{{system.parentContext}}/static/js/bootup.js"></script> --> <!-- <script src="{{system.parentContext}}/static/js/bootup.js"></script> -->
<!-- <script src="{{system.parentContext}}/static/js/dialog.js"></script> --> <!-- <script src="{{system.parentContext}}/static/js/dialog.js"></script> -->
<script src="{{system.parentContext}}/static/js/apexcharts/apexcharts.min.js"></script> <script src="{{system.parentContext}}/static/js/apexcharts/apexcharts.min.js"></script>
<script src="{{system.parentContext}}/static/js/fontawesome/js/all.js"></script> <script src="{{system.parentContext}}/static/js/fontawesome/js/all.js"></script>

@ -3,7 +3,7 @@
{% if system.parentContext == ""%} {% if system.parentContext == ""%}
{% set _backURI = "/" %} {% set _backURI = "/" %}
{% else %} {% else %}
{% set _backURI = system.parentContext%} {% set _backURI = system.caller.context %}
{% endif %} {% endif %}
<div class="qcms-icon" style="margin:0px" > <div class="qcms-icon" style="margin:0px" >

Loading…
Cancel
Save