diff --git a/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/base_type_interaction_workflow/interactions/ActionInformation_set.xml b/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/base_type_interaction_workflow/interactions/ActionInformation_set.xml index 4fdae1ca03a03bf576dc3841153264cb43bf799d..d4e29f282b98dcaad7f05d19e4ef2ba8011ec90d 100644 --- a/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/base_type_interaction_workflow/interactions/ActionInformation_set.xml +++ b/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/base_type_interaction_workflow/interactions/ActionInformation_set.xml @@ -31,7 +31,7 @@ <key> <string>after_script_name</string> </key> <value> <list> - <string>ActionInformation_clearCache</string> + <string>BaseType_clearCache</string> </list> </value> </item> diff --git a/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/base_type_interaction_workflow/interactions/BaseType_delete.xml b/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/base_type_interaction_workflow/interactions/BaseType_delete.xml deleted file mode 100644 index 8e9aa046c96223c6a03069e2c26e71de04b691d9..0000000000000000000000000000000000000000 --- a/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/base_type_interaction_workflow/interactions/BaseType_delete.xml +++ /dev/null @@ -1,95 +0,0 @@ -<?xml version="1.0"?> -<ZopeData> - <record id="1" aka="AAAAAAAAAAE="> - <pickle> - <tuple> - <global name="InteractionDefinition" module="Products.ERP5.Interaction"/> - <tuple/> - </tuple> - </pickle> - <pickle> - <dictionary> - <item> - <key> <string>actbox_category</string> </key> - <value> <string>workflow</string> </value> - </item> - <item> - <key> <string>actbox_name</string> </key> - <value> <string></string> </value> - </item> - <item> - <key> <string>actbox_url</string> </key> - <value> <string></string> </value> - </item> - <item> - <key> <string>activate_script_name</string> </key> - <value> - <tuple/> - </value> - </item> - <item> - <key> <string>after_script_name</string> </key> - <value> - <list> - <string>BaseType_clearCache</string> - </list> - </value> - </item> - <item> - <key> <string>before_commit_script_name</string> </key> - <value> - <tuple/> - </value> - </item> - <item> - <key> <string>description</string> </key> - <value> <string></string> </value> - </item> - <item> - <key> <string>guard</string> </key> - <value> - <none/> - </value> - </item> - <item> - <key> <string>id</string> </key> - <value> <string>BaseType_delete</string> </value> - </item> - <item> - <key> <string>method_id</string> </key> - <value> - <list> - <string>_delObject</string> - </list> - </value> - </item> - <item> - <key> <string>once_per_transaction</string> </key> - <value> <int>1</int> </value> - </item> - <item> - <key> <string>portal_type_filter</string> </key> - <value> - <list> - <string>Base Type</string> - </list> - </value> - </item> - <item> - <key> <string>script_name</string> </key> - <value> - <tuple/> - </value> - </item> - <item> - <key> <string>title</string> </key> - <value> <string></string> </value> - </item> - <item> - <key> <string>trigger_type</string> </key> - <value> <int>2</int> </value> - </item> - </dictionary> - </pickle> - </record> -</ZopeData> diff --git a/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/base_type_interaction_workflow/scripts/ActionInformation_clearCache.xml b/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/base_type_interaction_workflow/scripts/ActionInformation_clearCache.xml deleted file mode 100644 index 815fb5921599b5246282d52313faa38ee9650d86..0000000000000000000000000000000000000000 --- a/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/base_type_interaction_workflow/scripts/ActionInformation_clearCache.xml +++ /dev/null @@ -1,125 +0,0 @@ -<?xml version="1.0"?> -<ZopeData> - <record id="1" aka="AAAAAAAAAAE="> - <pickle> - <tuple> - <global name="PythonScript" module="Products.PythonScripts.PythonScript"/> - <tuple/> - </tuple> - </pickle> - <pickle> - <dictionary> - <item> - <key> <string>Script_magic</string> </key> - <value> <int>3</int> </value> - </item> - <item> - <key> <string>_bind_names</string> </key> - <value> - <object> - <klass> - <global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/> - </klass> - <tuple/> - <state> - <dictionary> - <item> - <key> <string>_asgns</string> </key> - <value> - <dictionary> - <item> - <key> <string>name_container</string> </key> - <value> <string>container</string> </value> - </item> - <item> - <key> <string>name_context</string> </key> - <value> <string>context</string> </value> - </item> - <item> - <key> <string>name_m_self</string> </key> - <value> <string>script</string> </value> - </item> - <item> - <key> <string>name_subpath</string> </key> - <value> <string>traverse_subpath</string> </value> - </item> - </dictionary> - </value> - </item> - </dictionary> - </state> - </object> - </value> - </item> - <item> - <key> <string>_body</string> </key> - <value> <string>base_type = state_change[\'object\'].aq_parent\n -base_type.clearGetRawActionInformationListCache()\n -</string> </value> - </item> - <item> - <key> <string>_code</string> </key> - <value> - <none/> - </value> - </item> - <item> - <key> <string>_params</string> </key> - <value> <string>state_change</string> </value> - </item> - <item> - <key> <string>errors</string> </key> - <value> - <tuple/> - </value> - </item> - <item> - <key> <string>func_code</string> </key> - <value> - <object> - <klass> - <global name="FuncCode" module="Shared.DC.Scripts.Signature"/> - </klass> - <tuple/> - <state> - <dictionary> - <item> - <key> <string>co_argcount</string> </key> - <value> <int>1</int> </value> - </item> - <item> - <key> <string>co_varnames</string> </key> - <value> - <tuple> - <string>state_change</string> - <string>_getattr_</string> - <string>_getitem_</string> - <string>base_type</string> - </tuple> - </value> - </item> - </dictionary> - </state> - </object> - </value> - </item> - <item> - <key> <string>func_defaults</string> </key> - <value> - <none/> - </value> - </item> - <item> - <key> <string>id</string> </key> - <value> <string>ActionInformation_clearCache</string> </value> - </item> - <item> - <key> <string>warnings</string> </key> - <value> - <tuple/> - </value> - </item> - </dictionary> - </pickle> - </record> -</ZopeData> diff --git a/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/base_type_interaction_workflow/scripts/BaseType_clearCache.xml b/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/base_type_interaction_workflow/scripts/BaseType_clearCache.xml index 892f69522af9a727fcaefe695af012b629666fef..9adf71826637055b78edeb8b023dc622adf131bb 100644 --- a/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/base_type_interaction_workflow/scripts/BaseType_clearCache.xml +++ b/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/base_type_interaction_workflow/scripts/BaseType_clearCache.xml @@ -53,8 +53,7 @@ </item> <item> <key> <string>_body</string> </key> - <value> <string>base_type = state_change[\'object\']\n -base_type.clearGetRawActionInformationListCache()\n + <value> <string>state_change[\'object\'].clearGetActionListCache()\n </string> </value> </item> <item> @@ -67,6 +66,14 @@ base_type.clearGetRawActionInformationListCache()\n <key> <string>_params</string> </key> <value> <string>state_change</string> </value> </item> + <item> + <key> <string>_proxy_roles</string> </key> + <value> + <tuple> + <string>Manager</string> + </tuple> + </value> + </item> <item> <key> <string>errors</string> </key> <value> @@ -92,9 +99,8 @@ base_type.clearGetRawActionInformationListCache()\n <value> <tuple> <string>state_change</string> - <string>_getitem_</string> - <string>base_type</string> <string>_getattr_</string> + <string>_getitem_</string> </tuple> </value> </item> diff --git a/product/ERP5/bootstrap/erp5_core/bt/revision b/product/ERP5/bootstrap/erp5_core/bt/revision index 438ea09fbbc52b2605d26f07c6fae86796e0c827..c1883bc017d327016ff4c4f88fe9d652fe4e141b 100644 --- a/product/ERP5/bootstrap/erp5_core/bt/revision +++ b/product/ERP5/bootstrap/erp5_core/bt/revision @@ -1 +1 @@ -1341 \ No newline at end of file +1342 \ No newline at end of file diff --git a/product/ERP5Type/Core/ActionInformation.py b/product/ERP5Type/Core/ActionInformation.py index 84d092f48b7a13e4021e044cca433799fbb1bbb7..0b2a495f1ce908f2e4f39d95e970d8a654632d50 100644 --- a/product/ERP5Type/Core/ActionInformation.py +++ b/product/ERP5Type/Core/ActionInformation.py @@ -52,53 +52,12 @@ class ActionInformation(XMLObject): security = ClassSecurityInfo() security.declareObjectProtected(AccessContentsInformation) - zope.interface.implements(interfaces.IAction) - # Declarative properties property_sheets = ( PropertySheet.CategoryCore , PropertySheet.DublinCore , PropertySheet.ActionInformation ) - security.declareProtected(AccessContentsInformation, 'test') - def test(self, ec): - """Test if the action should be displayed or not for the given context""" - if self.isVisible(): - permission_list = self.getActionPermissionList() - if permission_list: - category = self.getActionType() or '' - info = ec.vars - if (info['here'] is not None and - (category[:6] == 'object' or - category[:8] == 'workflow')): - context = info['here'] - elif (info['folder'] is not None and - category[:6] == 'folder'): - context = info['folder'] - else: - context = info['portal'] - has_permission = getSecurityManager().getUser().has_permission - for permission in permission_list: - if not has_permission(permission, context): - return False - condition = self.getCondition() - return condition is None or condition(ec) - return False - - security.declareProtected(AccessContentsInformation, 'getActionInfo') - def getActionInfo(self, ec): - """Return a dict with values required to display the action""" - action = self.getAction() - icon = self.getIcon() - return {'id': self.getReference(), - 'name': self.getTitle(), - 'description': self.getDescription(), - 'category': self.getActionType(), - 'priority': self.getFloatIndex(), - 'icon': icon is not None and icon(ec) or '', - 'url': action is not None and action(ec) or '', - } - def _setAction(self, value): """Overridden setter for 'action' to accept strings and clean null values """ @@ -166,78 +125,58 @@ class ActionInformation(XMLObject): self.getConditionText()] return ' '.join(filter(None, search_source_list)) - security.declareProtected(AccessContentsInformation, 'getActionType') - def getActionType(self): - # Since we should have only one category that starts with - # 'action_type/' here, we call self.categories[0][12:] (12 is the - # length of 'action_type/'), instead of self.getActionType() for - # better performance. - categories = getattr(self, 'categories', []) - return len(categories) and categories[0][12:] or None - - security.declarePrivate('getRawActionInformation') - def getRawActionInformation(self): - """Return RawActionInformation instance that is not persistent and - is cacheable.""" - # we cache cloned Expressions because original ones are persistent - # objects. - icon = self.getIcon() - if icon is not None: - icon = Expression(icon.text) - action = self.getAction() - if action is not None: - action = Expression(action.text) - condition = self.getCondition() - if condition is not None: - condition = Expression(condition.text) - return RawActionInformation( - {'id':self.getReference(), - 'name':self.getTitle(), - 'description':self.getDescription(), - 'category':self.getActionType(), - 'priority':self.getFloatIndex(), - 'icon':icon, - 'action':action, - 'condition':condition, - 'action_permission':self.getActionPermissionList(), - } - ) - -class RawActionInformation(object): + security.declarePrivate('getCacheableAction') + def getCacheableAction(self): + """Return an action object that is not persistent and is cacheable""" + return CacheableAction(id=self.getReference(), + name=self.getTitle(), + description=self.getDescription(), + category=self.getActionType(), + priority=self.getFloatIndex(), + icon=self.getIconText(), + action=self.getActionText(), + condition=self.getConditionText(), + permission_list=self.getActionPermissionList()) + + +class CacheableAction(object): """The purpose of this class is to provide a cacheable instance having an enough information of Action Information document.""" - def __init__(self, kw): - self.action = kw.pop('action') - self.icon = kw.pop('icon') - self.condition = kw.pop('condition') - self.action_permission = kw.pop('action_permission') + zope.interface.implements(interfaces.IAction) + + test_permission = None + + def __init__(self, **kw): + for attr in 'action', 'icon', 'condition': + expression = kw.pop(attr, None) + setattr(self, attr, expression and Expression(expression)) self.param_dict = kw + self.permission_list = kw.pop('permission_list', None) + if self.permission_list: + category = kw['category'] or '' + if category[:6] == 'object' or category[:8] == 'workflow': + self.test_permission = 'here' + elif category[:6] == 'folder': + self.test_permission = 'folder' + else: + self.test_permission = 'portal' - def getPriority(self): - return self.param_dict['priority'] + def __getitem__(self, attr): + return self.param_dict[attr] def test(self, ec): """Test if the action should be displayed or not for the given context""" - permission_list = self.action_permission - if permission_list: - category = self.param_dict['category'] or '' - info = ec.vars - if (info['here'] is not None and - (category[:6] == 'object' or - category[:8] == 'workflow')): - context = info['here'] - elif (info['folder'] is not None and - category[:6] == 'folder'): - context = info['folder'] - else: - context = info['portal'] + test_permission = self.test_permission + if test_permission: + context = ec.vars[test_permission] + if context is None: + context = ec.vars['portal'] has_permission = getSecurityManager().getUser().has_permission - for permission in permission_list: + for permission in self.permission_list: if not has_permission(permission, context): return False - condition = self.condition - return condition is None or condition(ec) + return self.condition is None or self.condition(ec) def cook(self, ec): param_dict = self.param_dict.copy() diff --git a/product/ERP5Type/ERP5Type.py b/product/ERP5Type/ERP5Type.py index 54e627cb73e71a46be2c5dd2cb94894e47c840dc..4fe2f5b4d01c2cd6ff409db0541f52f30175ee70 100644 --- a/product/ERP5Type/ERP5Type.py +++ b/product/ERP5Type/ERP5Type.py @@ -239,7 +239,7 @@ class ERP5TypeInformation(XMLObject, security = ClassSecurityInfo() security.declareObjectProtected(Permissions.AccessContentsInformation) - zope.interface.implements(interfaces.IActionProvider) + zope.interface.implements(interfaces.IActionContainer) # Declarative properties property_sheets = ( PropertySheet.BaseType, ) @@ -494,15 +494,14 @@ class ERP5TypeInformation(XMLObject, """ ec = createExpressionContext(ob) best_action = (), None - for action in self.getActionListFor(ob): - if action.getReference() == view: + for action in self.getActionList(): + if action['id'] == view: if action.test(ec): break else: # In case that "view" (or "list") action is not present or not allowed, # find something that's allowed (of the same category, if possible). - index = (action.getActionType().endswith('_' + view), - -action.getFloatIndex()) + index = action['category'].endswith('_' + view), if best_action[0] < index and action.test(ec): best_action = index, action else: @@ -511,45 +510,38 @@ class ERP5TypeInformation(XMLObject, raise AccessControl_Unauthorized( 'No accessible views available for %r' % ob.getPath()) - target = action.getAction()(ec).strip().split(ec.vars['object_url'])[-1] + target = action.cook(ec)['url'].strip().split(ec.vars['object_url'])[-1] if target.startswith('/'): target = target[1:] __traceback_info__ = self.getId(), target return ob.restrictedTraverse(target) - def _getRawActionInformationList(self): - return sorted( - (x.getRawActionInformation() for x in \ - self.getActionInformationList() if x.isVisible()), - key=lambda x:x.getPriority()) - _getRawActionInformationList = CachingMethod( - _getRawActionInformationList, - id='_getRawActionInformationList', + security.declarePrivate('getCachedActionList') + def getCacheableActionList(self): + """Return a cacheable list of enabled actions""" + return [action.getCacheableAction() + for action in self.getActionInformationList() + if action.isVisible()] + + def _getActionList(self): + action_list = self.getCacheableActionList() + action_list.sort(key=lambda x:x['priority']) + return action_list + _getActionList = CachingMethod(_getActionList, + id='getActionList', cache_factory='erp5_content_long', - cache_id_generator=lambda method_id, *args, **kwd:str(method_id)) + cache_id_generator=lambda method_id, *args: method_id) - security.declarePrivate('getRawActionInformationList') - def getRawActionInformationList(self): - """Return all visible action informations sorted by priority.""" - return self._getRawActionInformationList(self, scope=self.id) + security.declarePrivate('getActionList') + def getActionList(self): + """Return the list of enabled actions from cache, sorted by priority""" + return self._getActionList(self, scope=self.id) security.declareProtected(Permissions.ModifyPortalContent, - 'clearGetRawActionInformationListCache') - def clearGetRawActionInformationListCache(self): + 'clearGetActionListCache') + def clearGetActionListCache(self): """Clear a cache of _getRawActionInformationList.""" - self._getRawActionInformationList.delete(scope=self.id) - - security.declarePrivate('getActionListFor') - def getActionListFor(self, ob=None): - """Return all actions of the object""" - return self.getActionInformationList() - - security.declarePrivate('getFilteredActionListFor') - def getFilteredActionListFor(self, ob=None): - """Return all actions applicable to the object""" - ec = createExpressionContext(ob) - return (action for action in self.getActionInformationList() - if action.test(ec)) + self._getActionList.delete(scope=self.id) security.declareProtected(Permissions.AccessContentsInformation, 'getActionInformationList') diff --git a/product/ERP5Type/Tool/TypesTool.py b/product/ERP5Type/Tool/TypesTool.py index ed7ffe676caa6aa3ca05529756f9d13254a79744..58f1e82946f4fdc08325a5afcb7eb9bae1a96191 100644 --- a/product/ERP5Type/Tool/TypesTool.py +++ b/product/ERP5Type/Tool/TypesTool.py @@ -23,8 +23,7 @@ from OFS.Folder import Folder as OFSFolder import transaction from Products.CMFCore import TypesTool as CMFCore_TypesTool from Products.ERP5Type.Tool.BaseTool import BaseTool -from Products.ERP5Type.Cache import CachingMethod -from Products.ERP5Type import interfaces, Permissions +from Products.ERP5Type import Permissions from Products.ERP5Type.ERP5Type import ERP5TypeInformation from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod from zLOG import LOG, WARNING, PANIC @@ -40,33 +39,13 @@ class TypesTool(BaseTool, CMFCore_TypesTool.TypesTool): security = ClassSecurityInfo() security.declareObjectProtected(Permissions.AccessContentsInformation) - zope.interface.implements(interfaces.IActionProvider) - - security.declarePrivate('getRawActionInformationListFor') - def getRawActionInformationListFor(self, ob=None): - """Return all action informations of the object""" - if ob is not None: - type_info = self.getTypeInfo(ob) - if type_info is not None: - return type_info.getRawActionInformationList() - return () - security.declarePrivate('getActionListFor') def getActionListFor(self, ob=None): - """Return all actions of the object""" - if ob is not None: - type_info = self.getTypeInfo(ob) - if type_info is not None: - return type_info.getActionListFor(ob) - return () - - security.declarePrivate('getFilteredActionListFor') - def getFilteredActionListFor(self, ob=None): """Return all actions applicable to the object""" if ob is not None: type_info = self.getTypeInfo(ob) if type_info is not None: - return type_info.getFilteredActionListFor(ob) + return type_info.getActionList() return () def getTypeInfo(self, *args): diff --git a/product/ERP5Type/interfaces/__init__.py b/product/ERP5Type/interfaces/__init__.py index eb3b4a43532ca568436bcde154c942a69e9a81ce..b422b9c0b86f7b21f7da3465e139cd63a2571f96 100644 --- a/product/ERP5Type/interfaces/__init__.py +++ b/product/ERP5Type/interfaces/__init__.py @@ -4,7 +4,7 @@ from consistency_message import IConsistencyMessage from divergence_message import IDivergenceMessage from object_message import IObjectMessage -from action_provider import IAction, IActionProvider +from action_provider import IAction, IActionContainer from cache_plugin import ICachePlugin from category_access_provider import ICategoryAccessProvider from value_access_provider import IValueAccessProvider diff --git a/product/ERP5Type/interfaces/action_provider.py b/product/ERP5Type/interfaces/action_provider.py index 7b8c996b94a137bedf130c6528535fa9c2dc2bd1..396a4a39a9e3edcbc7ec356fc706f29606af4d22 100644 --- a/product/ERP5Type/interfaces/action_provider.py +++ b/product/ERP5Type/interfaces/action_provider.py @@ -33,11 +33,18 @@ from zope.interface import Interface class IAction(Interface): """ """ + def __getitem__(attr): + """Return any information independant of the context + + The following keys must have a value: + - id (string) + - category (string) + - priority (numeric) + """ def test(ec): """Test if the action should be displayed or not for the given context """ - - def getActionInfo(ec): + def cook(ec): """Return a dict with information required to display the action The dict must contain the following keys: @@ -50,12 +57,12 @@ class IAction(Interface): - priority (numeric) """ -class IActionProvider(Interface): +class IActionContainer(Interface): """ """ - def getActionListFor(ob): - """Return all actions of the object""" - - def getFilteredActionListFor(ob): - """Return all actions applicable to the object + def getCacheableActionList(): + """Return a cacheable list of enabled actions + """ + def getActionList(): + """Return the list of enabled actions from cache, sorted by priority """ diff --git a/product/ERP5Type/patches/ActionsTool.py b/product/ERP5Type/patches/ActionsTool.py index d3d25791c6ecf273122970d7e90f0218067a015e..a8e28176ce09a3e6bf821864e4513499dfca8508 100644 --- a/product/ERP5Type/patches/ActionsTool.py +++ b/product/ERP5Type/patches/ActionsTool.py @@ -39,10 +39,9 @@ def listFilteredActionsFor(self, object=None): elif hasattr(provider, 'getActionListFor'): from Products.ERP5Type.Utils import createExpressionContext ec = createExpressionContext(object) - actions.extend( - (action.cook(ec) - for action in provider.getRawActionInformationListFor(object) - if action.test(ec))) + actions.extend(action.cook(ec) + for action in provider.getActionListFor(object) + if action.test(ec)) else: # for Action Providers written for CMF versions before 1.5 actions.extend( self._listActionInfos(provider, object) ) diff --git a/product/ERP5Type/tests/testERP5TypeInterfaces.py b/product/ERP5Type/tests/testERP5TypeInterfaces.py index c3bb7a6b800453392aff9c2b7094ad1e8ca03183..2469555f636c20efbfd4cb99f3a4024f40335adf 100644 --- a/product/ERP5Type/tests/testERP5TypeInterfaces.py +++ b/product/ERP5Type/tests/testERP5TypeInterfaces.py @@ -40,13 +40,11 @@ implements_tuple_list = [ 'IConsistencyMessage'), (('Products.ERP5Type.DivergenceMessage', 'DivergenceMessage'), 'IDivergenceMessage'), - (('Products.ERP5Type.Tool.TypesTool', 'TypesTool'), - 'IActionProvider'), (('Products.ERP5Type.ERP5Type', 'ERP5TypeInformation'), - 'IActionProvider'), + 'IActionContainer'), (('Products.ERP5Type.ERP5Type', 'ERP5TypeInformation'), 'ILocalRoleAssignor'), - (('Products.ERP5Type.Document.ActionInformation', 'ActionInformation'), + (('Products.ERP5Type.Document.ActionInformation', 'CacheableAction'), 'IAction'), (('Products.ERP5Type.Document.RoleInformation', 'RoleInformation'), 'ILocalRoleGenerator'),