From b05d66e4ee795d1e13956c6dded20980442fc8e4 Mon Sep 17 00:00:00 2001 From: Steve Nyemba Date: Sat, 4 Jul 2026 01:16:39 -0500 Subject: [PATCH] bug fixes: context, parent context, new css nomanclature --- cms/index.py | 47 +++++----- cms/meta/__init__.py | 2 +- cms/sites/__init__.py | 179 +++++++++++++++++++++++++++++------- cms/static/js/qcms/utils.js | 2 + cms/templates/header.html | 1 + cms/templates/libs.html | 6 +- cms/templates/menu.html | 2 +- 7 files changed, 182 insertions(+), 57 deletions(-) diff --git a/cms/index.py b/cms/index.py index 56f5e63..8890711 100644 --- a/cms/index.py +++ b/cms/index.py @@ -48,7 +48,7 @@ def favicon (app): _site = _qcms.get (request) # 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("/robots.txt",defaults={'_id':None}) 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', methods=['POST','GET'], strict_slashes = False) - +@_app.route('//dialog/', methods=['POST','GET'], strict_slashes = False) def _dialog(app): + global _qcms # _site = _qcms.get(app) _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={}, methods=['POST','GET','PUT'], strict_slashes = False) + # @_app.route('//files/', defaults=[{'module':None}]) # @_app.route('///files/') def _read(file): @@ -218,7 +220,7 @@ def _read(file): # apply security here (on the top level site only) # @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) : return _qcms.delegate(request) else: @@ -252,27 +254,27 @@ def _version (): return _site.get('system.version') -@_app.route('/page',methods=['POST'],defaults={'app_id':None,'key':None}) -@_app.route('//page',methods=['POST'],defaults={'key':None}) -@_app.route('///page',methods=['POST']) -def getPostedPage(app_id,key): - """ - This function will return a page given a location parameter - """ - global _qcms - _id = _getId(app_id,key) - _site = _qcms.get(_id) - _uri = request.headers['uri'] +# @_app.route('/page',methods=['POST'],defaults={'app_id':None,'key':None}) +# @_app.route('//page',methods=['POST'],defaults={'key':None}) +# @_app.route('///page',methods=['POST']) +# def getPostedPage(app_id,key): +# """ +# This function will return a page given a location parameter +# """ +# global _qcms +# _id = _getId(app_id,key) +# _site = _qcms.get(_id) +# _uri = request.headers['uri'] - if 'dom' not in request.headers : - _id = _uri.split('/')[-1].split('.')[0] - else: - _id = request.headers['dom'] +# if 'dom' not in request.headers : +# _id = _uri.split('/')[-1].split('.')[0] +# else: +# _id = request.headers['dom'] - if 'read?uri=' in _uri or 'download?doc=' in _uri : - _uri = _uri.split('=')[1] - _args = _qcms.render(_uri,_id,_getId(app_id,key)) #session.get(app_id,'main')) - return _args[_id],200 +# if 'read?uri=' in _uri or 'download?doc=' in _uri : +# _uri = _uri.split('=')[1] +# _args = _qcms.render(_uri,_id,_getId(app_id,key)) #session.get(app_id,'main')) +# return _args[_id],200 @_app.route('/page',defaults={'app_id':None,'resource':None}) @_app.route('//page',defaults={'resource':None},methods=['GET']) @@ -303,6 +305,7 @@ def start ( _args['location'] = path _args['shared'] = True + _qcms = cms.sites.QCMS(**_args) _args = _qcms.get().get('system.app') diff --git a/cms/meta/__init__.py b/cms/meta/__init__.py index 59deace..cc45a5f 100644 --- a/cms/meta/__init__.py +++ b/cms/meta/__init__.py @@ -1,5 +1,5 @@ __author__ = "Steve L. Nyemba" -__version__= "3.0" +__version__= "3.1" __email__ = "steve@the-phi.com" __license__=""" Copyright 2010 - 2024, Steve L. Nyemba, Vanderbilt University Medical Center diff --git a/cms/sites/__init__.py b/cms/sites/__init__.py index 11d6ce5..6497e58 100644 --- a/cms/sites/__init__.py +++ b/cms/sites/__init__.py @@ -20,28 +20,58 @@ class RequestController : def __init__(self,_route): # self._plugins = _plugins 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): # # call self.exists to determin if this is an actual file or not return '.' in request.path def isroute(self,request): - # return '/'.join(request.path[1:].split('/')[:2]) in self._routes - return self.get_route(request) in self._routes - - return False + # return '/'.join(request.path[1:].split('/')[:2]) in self._routes + return self.get_route(request) + # p = self.get_route(request) in self._routes + # 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): return 'api' in request.path def get_route(self,request): - _items = request.path[1:].split('/') - _items = _items if _items[-1] != '' else _items[:-1] - N = len(_items) + 1 - _names = [] - for i in range(1, len(_items) + 1): - - _names.append('/'.join(_items[:i])) - - _names=[_item for _item in _names if _item in self._routes] - return _names[-1] if _names else None + # _items = request.path[1:].split('/') + + # if not '.' in request.path : + # print (self._routes) + # print (_items) + # if _items[0] in self._routes : + # return _items[0] + # else: + # return 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: """ @@ -51,7 +81,7 @@ class IOConfig: self._config = {'system':{},'layout':{},'plugins':{}} self._caller = None self._location= _args['location'] if 'location' in _args else None - + self._logs = [] def get(self,_key) : # @@ -103,9 +133,12 @@ class Initialization (IOConfig): # in case the path provided does NOT have the manifest 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" # # Invoke initialization + self.reload() # if self._caller and self._caller.secure : @@ -129,7 +162,7 @@ class Initialization (IOConfig): self._config['layout']['location'] = None if not self.get('layout.menu') : self._config['layout']['menu'] = {} - + self.context(**_args) self.menu() @@ -152,6 +185,53 @@ class Initialization (IOConfig): self.log(action='init.config',module='read_config',input={'caller':_name}) return _config 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 """ @@ -180,6 +260,8 @@ class Initialization (IOConfig): 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')}) @@ -199,6 +281,12 @@ class Initialization (IOConfig): # self._config['system']['context'] = _context.strip() self._config['system']['parentContext'] = _parentContext 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) # # loosly context to a certain extent involves locations of icons and document root @@ -225,13 +313,16 @@ class Initialization (IOConfig): # else: # _icon = f'{_context}/api/disk/read?uri={_logo}' # _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.logo',_icon) + # self.set('system.icon',_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 _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 """ - _context = self.get('system.context') + # _context = self.get('system.context') _plugins = self.get('plugins') for _key in ['debug','log'] : _pointer = getattr(self,_key) - _uri = f'{_context}/api/system/{_key}' - if _uri[0] == '/' : - _uri = _uri[1:] + _uri = f'api/system/{_key}' + # if _uri[0] == '/' : + # _uri = _uri[1:] _plugins[_uri] = _pointer self.set('plugins',_plugins) @@ -365,10 +456,10 @@ class Initialization (IOConfig): _pointer = disk.plugins(path=_path,name=_module,context=_context) if _pointer : _uri = f"api/{_filename}/{_module}" - _uri = f"{_context}/{_uri}" if _context else _uri + # _uri = f"{_context}/{_uri}" if _context else _uri if (_uri.startswith("/")) : _uri = _uri[1:] - + _uri.replace('//','/') _map[_uri] = _pointer if _parentContext : # _uri = f"{_parentContext}/{_context}" @@ -402,6 +493,7 @@ class Site(Initialization) : self._routes = [] if self.get('system.routes') : self._routes = list(self.get('system.routes').keys()) + self.log(action='init.routes',modules='site',input=self._routes) if self._caller : self._routes = self._caller._routes self.inspect = RequestController(self._routes) @@ -422,6 +514,7 @@ class Site(Initialization) : # path.append(uri) # return os.path.exists( os.sep.join(path)) + return os.path.exists(uri) def mimeType(self,uri): @@ -439,13 +532,13 @@ class Site(Initialization) : return _mimeType def path(self,_uri): path = [] - if self.get('layout.location'): path.append(self.get('layout.location')) if self.get('layout.root') not in _uri : path.append(self.get('layout.root')) path.append(_uri) + return os.sep.join(path) def html (self,_request): _uri = self.uri(_request) @@ -532,6 +625,9 @@ class Site(Initialization) : # _content = io.BytesIO(f.read()) # f.close() + # + # maybe the path needs some massage ... + _content = self.open(uri=self.path(_uri),mode='rb') _content = io.BytesIO( _content ) @@ -565,6 +661,17 @@ class Site(Initialization) : _kwargs = {'layout':self.get('layout')} _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'] : if k in _system : del _system[k] @@ -593,10 +700,15 @@ class Site(Initialization) : _data = f"
404
{_request.path} Not Found
" _mimeType = 'text/html' _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: _mimeType = 'application/octet-stream' + _pointer = _plugins.get(_key) + if hasattr(_pointer,'mimetype'): _mimeType = _pointer.mimetype _data = _pointer(request=_request,config=self.get(None)) @@ -612,6 +724,7 @@ class Site(Initialization) : elif type(_data) in [dict,list] : _data = json.dumps(_data) + # # # # return the ata _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' # if self._id == '' : # self._id = '/' - - self._sites = {self._id:_app, '/':_app} + self._sites = { self._id:_app, '':_app,'/':_app} self._routes = [] + if _app.get('system.routes') : _routes = _app.get('system.routes') @@ -638,13 +751,14 @@ class QCMS: _path = _routes[_name]['path'] 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) def _render(self,request): _site = self.get(request) + _args = _site.render(request=request,id='index') return render_template('index.html',**_args) def _read(self,request): @@ -666,7 +780,10 @@ class QCMS: return _content,200,{'Content-Type':_mimeType} if isapi : _site = self.get(request) - return _site.run(request) + + + + return _site.run(request) def get(self,request=None) : if request : diff --git a/cms/static/js/qcms/utils.js b/cms/static/js/qcms/utils.js index dd77162..41a5265 100644 --- a/cms/static/js/qcms/utils.js +++ b/cms/static/js/qcms/utils.js @@ -103,3 +103,5 @@ qcms.code.documentation= function(_uri,_id) { $(this._id).find('*.child').show() //css({display:'block'}) } } + + diff --git a/cms/templates/header.html b/cms/templates/header.html index 79798b9..632f483 100644 --- a/cms/templates/header.html +++ b/cms/templates/header.html @@ -4,6 +4,7 @@
+
{{layout.header.title}}
{{layout.header.subtitle}}
diff --git a/cms/templates/libs.html b/cms/templates/libs.html index 4338a59..4decc10 100644 --- a/cms/templates/libs.html +++ b/cms/templates/libs.html @@ -1,4 +1,6 @@ - + + + @@ -41,4 +43,4 @@ - \ No newline at end of file + diff --git a/cms/templates/menu.html b/cms/templates/menu.html index c854105..7987ba0 100644 --- a/cms/templates/menu.html +++ b/cms/templates/menu.html @@ -3,7 +3,7 @@ {% if system.parentContext == ""%} {% set _backURI = "/" %} {% else %} - {% set _backURI = system.parentContext%} + {% set _backURI = system.caller.context %} {% endif %}