From c4b05340c1d51b90411d5df951d0571775b5fc1f Mon Sep 17 00:00:00 2001 From: Jean-Paul Smets <jp@nexedi.com> Date: Mon, 8 Jan 2007 09:40:55 +0000 Subject: [PATCH] Refactored ERP5 Web classes with abstract API. git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@11916 20353a03-c40f-0410-a6d1-a30d3c3de9de --- product/ERP5/Document/WebSection.py | 277 ++++++++++++++++++++++++-- product/ERP5/Document/WebSite.py | 165 ++------------- product/ERP5/PropertySheet/WebSite.py | 14 ++ 3 files changed, 287 insertions(+), 169 deletions(-) diff --git a/product/ERP5/Document/WebSection.py b/product/ERP5/Document/WebSection.py index a6cbff46f5..9c3db85fb9 100644 --- a/product/ERP5/Document/WebSection.py +++ b/product/ERP5/Document/WebSection.py @@ -38,14 +38,64 @@ from Globals import get_request from zLOG import LOG -from Products.ERP5.Document.WebSite import reserved_name_dict, reserved_name_dict_init -from Products.ERP5.Document.WebSite import CACHE_KEY, WEBSITE_USER, WEBSECTION_KEY, DOCUMENT_NAME_KEY +from Products.ERP5Type.Cache import getReadOnlyTransactionCache +# Global keys used for URL generation +WEBSECTION_KEY = 'web_section_value' +WEBSITE_USER = 'web_site_user' + +Domain_getattr = Domain.inheritedAttribute('__getattr__') + +# We use a request key (CACHE_KEY) to store access attributes and prevent infinite recursion +# We define a couple of reserved names for which we are not +# going to try to do acquisition +CACHE_KEY = 'web_site_aq_cache' +DOCUMENT_NAME_KEY = 'web_section_document_name' +reserved_name_dict = { 'getApplicableLayout' : 1, + 'getLayout' : 1, + 'Localizer' : 1, + 'field_render' : 1, + 'getListItemUrl' : 1, + 'getLocalPropertyManager' : 1, + 'getOrderedGlobalActionList' : 1, + 'allow_discussion' : 1, + 'im_func' : 1, + 'id' : 1, + 'method_id' : 1, + 'role_map' : 1, + 'func_defaults': 1, } +reserved_name_dict_init = 0 class WebSection(Domain): """ A Web Section is a Domain with an extended API intended to - support the creation of Web front ends to ERP5 contents. + support the creation of Web front ends to + server ERP5 contents through a pretty and configurable + user interface. + + WebSection uses the following scripts for customisation: + + - WebSection_getBreadcrumbItemList + + - WebSection_getDocumentValueList + + - WebSection_getPermanentURL + + - WebSection_getDocumentValue + + - WebSection_getDefaultDocumentValue + + - WebSection_getSectionValue + + - WebSection_getWebSiteValue + + It defines the following REQUEST global variables: + + - current_web_section + + - current_web_document + + - is_web_section_default_document """ # CMF Type Definition meta_type = 'ERP5 Web Section' @@ -66,31 +116,20 @@ class WebSection(Domain): , PropertySheet.SortIndex ) - # Draft - this is being worked on - # Due to some issues in acquisition, the implementation of getWebSectionValue - # through acquisition by containment does not work for URLs - # such as web_site_module/a/b/c/web_page_module/d - # getWebSectionValue will return web_site_module/a/b instead - # of web_site_module/a/b/c - #security.declareProtected(Permissions.AccessContentsInformation, 'getWebSectionValue') - #def getWebSectionValue(self): - #""" - #Returns the current web section (ie. self) though containment acquisition - #""" - #return self + web_section_key = WEBSECTION_KEY def _aq_dynamic(self, name): """ Try to find a suitable document based on the web site local naming policies as defined by - the WebSite_getDocumentValue script + the getDocumentValue method """ global reserved_name_dict_init global reserved_name_dict request = self.REQUEST # Register current web site physical path for later URL generation - if not request.has_key(WEBSECTION_KEY): - request[WEBSECTION_KEY] = self.getPhysicalPath() + if not request.has_key(self.web_section_key): + request[self.web_section_key] = self.getPhysicalPath() # Normalize web parameter in the request # Fix common user mistake and transform '1' string to boolean for web_param in ['ignore_layout', 'editable_mode']: @@ -105,6 +144,7 @@ class WebSection(Domain): return dynamic # Do some optimisation here for names which can not be names of documents if reserved_name_dict.has_key(name) \ + or name.endswith('_getDocumentValue') \ or name.startswith('_') or name.startswith('portal_')\ or name.startswith('aq_') or name.startswith('selection_') \ or name.startswith('sort-') or name.startswith('WebSite_') \ @@ -135,7 +175,8 @@ class WebSection(Domain): if user is not None: old_manager = getSecurityManager() newSecurityManager(get_request(), user) - document = self.WebSite_getDocumentValue(name=name, portal=portal) + LOG('Lookup', 0, str(name)) + document = self.getDocumentValue(name=name, portal=portal) request[CACHE_KEY][name] = document if user is not None: setSecurityManager(old_manager) @@ -152,6 +193,198 @@ class WebSection(Domain): editable_absolute_url=document.absolute_url())) return document - security.declareProtected(Permissions.AccessContentsInformation, 'getWebSiteValue') - def getWebSiteValue(self): - return self.getParentValue().getWebSiteValue() + security.declareProtected(Permissions.AccessContentsInformation, 'getWebSectionValue') + def getWebSectionValue(self): + """ + Returns the current web section (ie. self) though containment acquisition. + + To understand the misteries of acquisition and how the rule + containment vs. acquisition works, please look at + XXXX (Zope web site) + """ + return self + + # Default view display + security.declareProtected(Permissions.View, '__call__') + def __call__(self): + """ + If a Web Section has a default document, we render + the default document instead of rendering the Web Section + itself. + + The implementation is based on the presence of specific + variables in the REQUEST (besides editable_mode and + ignore_layout). + + current_web_section -- defines the Web Section which is + used to display the current document. + + current_web_document -- defines the Document (ex. Web Page) + which is being displayed within current_web_section. + + is_web_section_default_document -- a boolean which is + set each time we display a default document as a section. + + We use REQUEST parameters so that they are reset for every + Web transaction and can be accessed from widgets. + """ + self.REQUEST.set('current_web_section', self) + if not self.REQUEST.get('editable_mode') and not self.REQUEST.get('ignore_layout'): + document = self.getDefaultDocumentValue() + if document is not None: + self.REQUEST.set('current_web_document', document) + self.REQUEST.set('is_web_section_default_document', 1) + return document.__of__(self)() + return Domain.__call__(self) + + # Layout Selection API + security.declareProtected(Permissions.AccessContentsInformation, 'getApplicableLayout') + def getApplicableLayout(self): + """ + The applicable layout on a section is the container layout. + """ + return self.getContainerLayout() + + # WebSection API + security.declareProtected(Permissions.View, 'getDocumentValue') + def getDocumentValue(self, name=None, portal=None): + """ + Return the default document with the given + name. The name parameter may represent anything + such as a document reference, an identifier, + etc. + + If name is not provided, the method defaults + to returning the default document by calling + getDefaultDocumentValue. + + This method must be implemented through a + portal type dependent script: + WebSection_getDocumentValue + """ + if name is None: + return self.getDefaultDocumentValue() + + cache = getReadOnlyTransactionCache(self) + method = None + if cache is not None: + key = ('getDocumentValue', self) + try: + method = cache[key] + except KeyError: + pass + + if method is None: method = self._getTypeBasedMethod('getDocumentValue', + fallback_script_id='WebSection_getDocumentValue') + + if cache is not None: + if not cache.has_key(key): cache[key] = method + + return method(name, portal=portal) + + security.declareProtected(Permissions.View, 'getDefaultDocumentValue') + def getDefaultDocumentValue(self): + """ + Return the default document of the current + section. + + This method must be implemented through a + portal type dependent script: + WebSection_getDefaultDocumentValue + """ + cache = getReadOnlyTransactionCache(self) + if cache is not None: + key = ('getDefaultDocumentValue', self) + try: + return cache[key] + except KeyError: + pass + + result = self._getTypeBasedMethod('getDefaultDocumentValue', + fallback_script_id='WebSection_getDefaultDocumentValue')() + + if cache is not None: + cache[key] = result + + return result + + security.declareProtected(Permissions.View, 'getDocumentValueList') + def getDocumentValueList(self, **kw): + """ + Return the list of documents which belong to the + current section. The API is designed to + support additional parameters so that it is possible + to group documents by reference, version, language, etc. + or to implement filtering of documents. + + This method must be implemented through a + portal type dependent script: + WebSection_getDocumentValueList + """ + cache = getReadOnlyTransactionCache(self) + if cache is not None: + key = ('getDocumentValueList', self) + tuple(kw.items()) + try: + return cache[key] + except KeyError: + pass + + result = self._getTypeBasedMethod('getDocumentValueList', + fallback_script_id='WebSection_getDocumentValueList')(**kw) + + if cache is not None: + cache[key] = result + + return result + + security.declareProtected(Permissions.View, 'getPermanentURL') + def getPermanentURL(self, document): + """ + Return a permanent URL of document in the context + of the current section. + + This method must be implemented through a + portal type dependent script: + WebSection_getPermanentURL + """ + cache = getReadOnlyTransactionCache(self) + if cache is not None: + key = ('getDocumentValueList', self, document.getPath()) + try: + return cache[key] + except KeyError: + pass + + result = self._getTypeBasedMethod('getPermanentURL', + fallback_script_id='WebSection_getPermanentURL')(document) + + if cache is not None: + cache[key] = result + + return result + + security.declareProtected(Permissions.View, 'getBreadcrumbItemList') + def getBreadcrumbItemList(self, document): + """ + Return a section dependent breadcrumb in the form + of a list of (title, document) tuples. + + This method must be implemented through a + portal type dependent script: + WebSection_getBreadcrumbItemList + """ + cache = getReadOnlyTransactionCache(self) + if cache is not None: + key = ('getDocumentValueList', self, document.getPath()) + try: + return cache[key] + except KeyError: + pass + + result = self._getTypeBasedMethod('getBreadcrumbItemList', + fallback_script_id='WebSection_getBreadcrumbItemList')(document) + + if cache is not None: + cache[key] = result + + return result diff --git a/product/ERP5/Document/WebSite.py b/product/ERP5/Document/WebSite.py index 8af5da08c7..28a94d0260 100644 --- a/product/ERP5/Document/WebSite.py +++ b/product/ERP5/Document/WebSite.py @@ -26,29 +26,18 @@ ############################################################################## from Acquisition import ImplicitAcquisitionWrapper, aq_base, aq_inner - from AccessControl import ClassSecurityInfo -from AccessControl.User import emergency_user -from AccessControl.SecurityManagement import getSecurityManager, newSecurityManager, setSecurityManager -from Products.CMFCore.utils import getToolByName -from Products.ERP5.Document.Domain import Domain +from Products.ERP5.Document.WebSection import WebSection, WEBSECTION_KEY from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface, Cache -from Products.ERP5Type.Base import TempBase - -from Products.CMFCore.utils import UniqueObject, _checkPermission, _getAuthenticatedUser from Globals import get_request - from Persistence import Persistent - from ZPublisher import BeforeTraverse from zLOG import LOG WEBSITE_KEY = 'web_site_value' -WEBSECTION_KEY = 'web_section_value' -WEBSITE_USER = 'web_site_user' class WebSiteTraversalHook(Persistent): """ @@ -111,62 +100,10 @@ class WebSiteTraversalHook(Persistent): self._v_request = request request.physicalPathToVirtualPath = self._physicalPathToVirtualPath - - -Domain_getattr = Domain.inheritedAttribute('__getattr__') - -# Use a request key to store access attributes and prevent infinite recursion -CACHE_KEY = 'web_site_aq_cache' -DOCUMENT_NAME_KEY = 'web_site_document_name' -reserved_name_dict = { 'getApplicableLayout' : 1, - 'getLayout' : 1, - 'Localizer' : 1, - 'field_render' : 1, - 'getListItemUrl' : 1, - 'getLocalPropertyManager' : 1, - 'getOrderedGlobalActionList' : 1, - 'allow_discussion' : 1, - 'im_func' : 1, - 'id' : 1, - 'method_id' : 1, - 'role_map' : 1, } -reserved_name_dict_init = 0 - -class WebSite(Domain): +class WebSite(WebSection): """ - A Web Site root class. This class is used by ERP5 Commerce - to define the root of an eCommerce site. - - The main idea of the WebSite class is to provide access to - documents by leveraging aq_dynamic with a user definable - script: WebSite_getDocumentValue - - This script allows for implementing simple or - complex document lookup policies: - - - access to documents by a unique reference (ex. as - in a Wiki) - - - access to published documents only (ex. - publication_state == 'published') - - - access to most relevant document (ex. latest - version, applicable language) - - Changing this script allows for configuring completely - the behaviour of a web site and tweaking document - lookup policies to fit specific needs. - - WARNING: - - Z Catalog Search permission must be set for Anonymous - (XXX is this still true ?) - - TODO: - - accelerate document lookup by caching acceptable keys - (XXX is this still true ?) - - - fix missing REQUEST information in aq_dynamic documents - (XXX is this still true ?) + The Web Site root class is specialises WebSection + by defining a global webmaster user. """ # CMF Type Definition meta_type = 'ERP5 Web Site' @@ -186,79 +123,8 @@ class WebSite(Domain): , PropertySheet.WebSite ) - def _aq_dynamic(self, name): - """ - Try to find a suitable document based on the - web site local naming policies as defined by - the WebSite_getDocumentValue script - """ - global reserved_name_dict_init - global reserved_name_dict - request = self.REQUEST - # Register current web site physical path for later URL generation - if not request.has_key(WEBSITE_KEY): - request[WEBSITE_KEY] = self.getPhysicalPath() - # Normalize web parameter in the request - # Fix common user mistake and transform '1' string to boolean - for web_param in ['ignore_layout', 'editable_mode']: - if hasattr(request, web_param): - if getattr(request, web_param, None) in ('1', 1, True): - request.set(web_param, True) - else: - request.set(web_param, False) - # First let us call the super method - dynamic = Domain._aq_dynamic(self, name) - if dynamic is not None: - return dynamic - # Do some optimisation here for names which can not be names of documents - if reserved_name_dict.has_key(name) \ - or name.startswith('_') or name.startswith('portal_')\ - or name.startswith('aq_') or name.startswith('selection_') \ - or name.startswith('sort-') or name.startswith('WebSite_') \ - or name.startswith('WebSection_') or name.startswith('Base_'): - return None - if not reserved_name_dict_init: - # Feed reserved_name_dict_init with skin names - portal = self.getPortalObject() - for skin_folder in portal.portal_skins.objectValues(): - for id in skin_folder.objectIds(): - reserved_name_dict[id] = 1 - for id in portal.objectIds(): - reserved_name_dict[id] = 1 - reserved_name_dict_init = 1 - #LOG('aq_dynamic name',0, name) - if not request.has_key(CACHE_KEY): - request[CACHE_KEY] = {} - elif request[CACHE_KEY].has_key(name): - return request[CACHE_KEY][name] - try: - portal = self.getPortalObject() - # Use the webmaster identity to find documents - if request[CACHE_KEY].has_key(WEBSITE_USER): - user = request[CACHE_KEY][WEBSITE_USER] # Retrieve user from request cache - else: - user = portal.acl_users.getUserById(self.getWebmaster()) - request[CACHE_KEY][WEBSITE_USER] = user # Cache user per request - if user is not None: - old_manager = getSecurityManager() - newSecurityManager(get_request(), user) - document = self.WebSite_getDocumentValue(name=name, portal=portal) - request[CACHE_KEY][name] = document - if user is not None: - setSecurityManager(old_manager) - except: - # Cleanup non recursion dict in case of exception - if request[CACHE_KEY].has_key(name): - del request[CACHE_KEY][name] - raise - if document is not None: - document = aq_base(document.asContext(id=name, # Hide some properties to permit location the original - original_container=document.getParentValue(), - original_id=document.getId(), - editable_absolute_url=document.absolute_url())) - return document + web_section_key = WEBSITE_KEY - # Draft - this is being worked on security.declareProtected(Permissions.AccessContentsInformation, 'getWebSiteValue') def getWebSiteValue(self): """ @@ -266,24 +132,29 @@ class WebSite(Domain): """ return self + # Virtual Hosting Support security.declarePrivate( 'manage_beforeDelete' ) def manage_beforeDelete(self, item, container): if item is self: handle = self.meta_type + '/' + self.getId() BeforeTraverse.unregisterBeforeTraverse(item, handle) - Domain.manage_beforeDelete(self, item, container) + WebSection.manage_beforeDelete(self, item, container) security.declarePrivate( 'manage_afterAdd' ) def manage_afterAdd(self, item, container): if item is self: handle = self.meta_type + '/' + self.getId() BeforeTraverse.registerBeforeTraverse(item, WebSiteTraversalHook(), handle) - Domain.manage_afterAdd(self, item, container) + WebSection.manage_afterAdd(self, item, container) # Experimental methods - def findUrlList(self, document): - """ - Return a list of URLs which exist in the site for - a given document - """ - pass \ No newline at end of file + def getPermanentURLList(self, document): + """ + Return a list of URLs which exist in the site for + a given document. This could be implemented either + by keep a history of documents which have been + accessed or by parsing all WebSections and listing + all documents in each of them to build a reverse + mapping of getPermanentURL + """ + pass \ No newline at end of file diff --git a/product/ERP5/PropertySheet/WebSite.py b/product/ERP5/PropertySheet/WebSite.py index e921a56133..963ab1cffc 100644 --- a/product/ERP5/PropertySheet/WebSite.py +++ b/product/ERP5/PropertySheet/WebSite.py @@ -34,10 +34,24 @@ class WebSite: { 'id' : 'container_layout', 'description' : 'ID of a page template or form which defines the rendering layout for the container', 'type' : 'string', + 'default' : None, + 'acquisition_base_category' : ('parent',), + 'acquisition_portal_type' : ('Web Section', 'Web Site'), + 'acquisition_copy_value' : 0, + 'acquisition_mask_value' : 1, + 'acquisition_accessor_id' : 'getContainerLayout', + 'acquisition_depends' : None, 'mode' : '' }, { 'id' : 'content_layout', 'description' : 'ID of a page template or form which defines the rendering layout for contents', 'type' : 'string', + 'default' : None, + 'acquisition_base_category' : ('parent',), + 'acquisition_portal_type' : ('Web Section', 'Web Site'), + 'acquisition_copy_value' : 0, + 'acquisition_mask_value' : 1, + 'acquisition_accessor_id' : 'getContentLayout', + 'acquisition_depends' : None, 'mode' : '' }, { 'id' : 'webmaster', 'description' : 'ID of a user which has complete access to all documents in the site.', -- 2.30.9