Commit 46cc3cda authored by Julien Muchembled's avatar Julien Muchembled

Clean up API for portal type actions and fix related interaction workflow

* ERP5Type.ERP5Type:
  - getRawActionInformationList -> getActionList
  - split _getRawActionInformationList into
    _getActionList and getCacheableActionList
  - clearGetRawActionInformationListCache -> clearGetActionListCache
* ERP5Type.Core.ActionInformation:
  RawActionInformation -> CacheableAction
* Update interface for actions.
* Very small optimization in CacheableAction.
* Remove awful ActionInformation.getActionType now that the result is cached.
* Remove obsolete/unused code.
* Fix interaction workflow to clear cache.

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@29840 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 5817a1c1
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
<key> <string>after_script_name</string> </key> <key> <string>after_script_name</string> </key>
<value> <value>
<list> <list>
<string>ActionInformation_clearCache</string> <string>BaseType_clearCache</string>
</list> </list>
</value> </value>
</item> </item>
......
<?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>
<?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>
...@@ -53,8 +53,7 @@ ...@@ -53,8 +53,7 @@
</item> </item>
<item> <item>
<key> <string>_body</string> </key> <key> <string>_body</string> </key>
<value> <string>base_type = state_change[\'object\']\n <value> <string>state_change[\'object\'].clearGetActionListCache()\n
base_type.clearGetRawActionInformationListCache()\n
</string> </value> </string> </value>
</item> </item>
<item> <item>
...@@ -67,6 +66,14 @@ base_type.clearGetRawActionInformationListCache()\n ...@@ -67,6 +66,14 @@ base_type.clearGetRawActionInformationListCache()\n
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>state_change</string> </value> <value> <string>state_change</string> </value>
</item> </item>
<item>
<key> <string>_proxy_roles</string> </key>
<value>
<tuple>
<string>Manager</string>
</tuple>
</value>
</item>
<item> <item>
<key> <string>errors</string> </key> <key> <string>errors</string> </key>
<value> <value>
...@@ -92,9 +99,8 @@ base_type.clearGetRawActionInformationListCache()\n ...@@ -92,9 +99,8 @@ base_type.clearGetRawActionInformationListCache()\n
<value> <value>
<tuple> <tuple>
<string>state_change</string> <string>state_change</string>
<string>_getitem_</string>
<string>base_type</string>
<string>_getattr_</string> <string>_getattr_</string>
<string>_getitem_</string>
</tuple> </tuple>
</value> </value>
</item> </item>
......
1341 1342
\ No newline at end of file \ No newline at end of file
...@@ -52,53 +52,12 @@ class ActionInformation(XMLObject): ...@@ -52,53 +52,12 @@ class ActionInformation(XMLObject):
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declareObjectProtected(AccessContentsInformation) security.declareObjectProtected(AccessContentsInformation)
zope.interface.implements(interfaces.IAction)
# Declarative properties # Declarative properties
property_sheets = ( PropertySheet.CategoryCore property_sheets = ( PropertySheet.CategoryCore
, PropertySheet.DublinCore , PropertySheet.DublinCore
, PropertySheet.ActionInformation , 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): def _setAction(self, value):
"""Overridden setter for 'action' to accept strings and clean null values """Overridden setter for 'action' to accept strings and clean null values
""" """
...@@ -166,78 +125,58 @@ class ActionInformation(XMLObject): ...@@ -166,78 +125,58 @@ class ActionInformation(XMLObject):
self.getConditionText()] self.getConditionText()]
return ' '.join(filter(None, search_source_list)) return ' '.join(filter(None, search_source_list))
security.declareProtected(AccessContentsInformation, 'getActionType') security.declarePrivate('getCacheableAction')
def getActionType(self): def getCacheableAction(self):
# Since we should have only one category that starts with """Return an action object that is not persistent and is cacheable"""
# 'action_type/' here, we call self.categories[0][12:] (12 is the return CacheableAction(id=self.getReference(),
# length of 'action_type/'), instead of self.getActionType() for name=self.getTitle(),
# better performance. description=self.getDescription(),
categories = getattr(self, 'categories', []) category=self.getActionType(),
return len(categories) and categories[0][12:] or None priority=self.getFloatIndex(),
icon=self.getIconText(),
security.declarePrivate('getRawActionInformation') action=self.getActionText(),
def getRawActionInformation(self): condition=self.getConditionText(),
"""Return RawActionInformation instance that is not persistent and permission_list=self.getActionPermissionList())
is cacheable."""
# we cache cloned Expressions because original ones are persistent
# objects. class CacheableAction(object):
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):
"""The purpose of this class is to provide a cacheable instance having """The purpose of this class is to provide a cacheable instance having
an enough information of Action Information document.""" an enough information of Action Information document."""
def __init__(self, kw): zope.interface.implements(interfaces.IAction)
self.action = kw.pop('action')
self.icon = kw.pop('icon') test_permission = None
self.condition = kw.pop('condition')
self.action_permission = kw.pop('action_permission') 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.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): def __getitem__(self, attr):
return self.param_dict['priority'] return self.param_dict[attr]
def test(self, ec): def test(self, ec):
"""Test if the action should be displayed or not for the given context""" """Test if the action should be displayed or not for the given context"""
permission_list = self.action_permission test_permission = self.test_permission
if permission_list: if test_permission:
category = self.param_dict['category'] or '' context = ec.vars[test_permission]
info = ec.vars if context is None:
if (info['here'] is not None and context = ec.vars['portal']
(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 has_permission = getSecurityManager().getUser().has_permission
for permission in permission_list: for permission in self.permission_list:
if not has_permission(permission, context): if not has_permission(permission, context):
return False return False
condition = self.condition return self.condition is None or self.condition(ec)
return condition is None or condition(ec)
def cook(self, ec): def cook(self, ec):
param_dict = self.param_dict.copy() param_dict = self.param_dict.copy()
......
...@@ -239,7 +239,7 @@ class ERP5TypeInformation(XMLObject, ...@@ -239,7 +239,7 @@ class ERP5TypeInformation(XMLObject,
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation) security.declareObjectProtected(Permissions.AccessContentsInformation)
zope.interface.implements(interfaces.IActionProvider) zope.interface.implements(interfaces.IActionContainer)
# Declarative properties # Declarative properties
property_sheets = ( PropertySheet.BaseType, ) property_sheets = ( PropertySheet.BaseType, )
...@@ -494,15 +494,14 @@ class ERP5TypeInformation(XMLObject, ...@@ -494,15 +494,14 @@ class ERP5TypeInformation(XMLObject,
""" """
ec = createExpressionContext(ob) ec = createExpressionContext(ob)
best_action = (), None best_action = (), None
for action in self.getActionListFor(ob): for action in self.getActionList():
if action.getReference() == view: if action['id'] == view:
if action.test(ec): if action.test(ec):
break break
else: else:
# In case that "view" (or "list") action is not present or not allowed, # In case that "view" (or "list") action is not present or not allowed,
# find something that's allowed (of the same category, if possible). # find something that's allowed (of the same category, if possible).
index = (action.getActionType().endswith('_' + view), index = action['category'].endswith('_' + view),
-action.getFloatIndex())
if best_action[0] < index and action.test(ec): if best_action[0] < index and action.test(ec):
best_action = index, action best_action = index, action
else: else:
...@@ -511,45 +510,38 @@ class ERP5TypeInformation(XMLObject, ...@@ -511,45 +510,38 @@ class ERP5TypeInformation(XMLObject,
raise AccessControl_Unauthorized( raise AccessControl_Unauthorized(
'No accessible views available for %r' % ob.getPath()) '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('/'): if target.startswith('/'):
target = target[1:] target = target[1:]
__traceback_info__ = self.getId(), target __traceback_info__ = self.getId(), target
return ob.restrictedTraverse(target) return ob.restrictedTraverse(target)
def _getRawActionInformationList(self): security.declarePrivate('getCachedActionList')
return sorted( def getCacheableActionList(self):
(x.getRawActionInformation() for x in \ """Return a cacheable list of enabled actions"""
self.getActionInformationList() if x.isVisible()), return [action.getCacheableAction()
key=lambda x:x.getPriority()) for action in self.getActionInformationList()
_getRawActionInformationList = CachingMethod( if action.isVisible()]
_getRawActionInformationList,
id='_getRawActionInformationList', 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_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') security.declarePrivate('getActionList')
def getRawActionInformationList(self): def getActionList(self):
"""Return all visible action informations sorted by priority.""" """Return the list of enabled actions from cache, sorted by priority"""
return self._getRawActionInformationList(self, scope=self.id) return self._getActionList(self, scope=self.id)
security.declareProtected(Permissions.ModifyPortalContent, security.declareProtected(Permissions.ModifyPortalContent,
'clearGetRawActionInformationListCache') 'clearGetActionListCache')
def clearGetRawActionInformationListCache(self): def clearGetActionListCache(self):
"""Clear a cache of _getRawActionInformationList.""" """Clear a cache of _getRawActionInformationList."""
self._getRawActionInformationList.delete(scope=self.id) self._getActionList.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))
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'getActionInformationList') 'getActionInformationList')
......
...@@ -23,8 +23,7 @@ from OFS.Folder import Folder as OFSFolder ...@@ -23,8 +23,7 @@ from OFS.Folder import Folder as OFSFolder
import transaction import transaction
from Products.CMFCore import TypesTool as CMFCore_TypesTool from Products.CMFCore import TypesTool as CMFCore_TypesTool
from Products.ERP5Type.Tool.BaseTool import BaseTool from Products.ERP5Type.Tool.BaseTool import BaseTool
from Products.ERP5Type.Cache import CachingMethod from Products.ERP5Type import Permissions
from Products.ERP5Type import interfaces, Permissions
from Products.ERP5Type.ERP5Type import ERP5TypeInformation from Products.ERP5Type.ERP5Type import ERP5TypeInformation
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
from zLOG import LOG, WARNING, PANIC from zLOG import LOG, WARNING, PANIC
...@@ -40,33 +39,13 @@ class TypesTool(BaseTool, CMFCore_TypesTool.TypesTool): ...@@ -40,33 +39,13 @@ class TypesTool(BaseTool, CMFCore_TypesTool.TypesTool):
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation) 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') security.declarePrivate('getActionListFor')
def getActionListFor(self, ob=None): 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""" """Return all actions applicable to the object"""
if ob is not None: if ob is not None:
type_info = self.getTypeInfo(ob) type_info = self.getTypeInfo(ob)
if type_info is not None: if type_info is not None:
return type_info.getFilteredActionListFor(ob) return type_info.getActionList()
return () return ()
def getTypeInfo(self, *args): def getTypeInfo(self, *args):
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
from consistency_message import IConsistencyMessage from consistency_message import IConsistencyMessage
from divergence_message import IDivergenceMessage from divergence_message import IDivergenceMessage
from object_message import IObjectMessage from object_message import IObjectMessage
from action_provider import IAction, IActionProvider from action_provider import IAction, IActionContainer
from cache_plugin import ICachePlugin from cache_plugin import ICachePlugin
from category_access_provider import ICategoryAccessProvider from category_access_provider import ICategoryAccessProvider
from value_access_provider import IValueAccessProvider from value_access_provider import IValueAccessProvider
......
...@@ -33,11 +33,18 @@ from zope.interface import Interface ...@@ -33,11 +33,18 @@ from zope.interface import Interface
class IAction(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): def test(ec):
"""Test if the action should be displayed or not for the given context """Test if the action should be displayed or not for the given context
""" """
def cook(ec):
def getActionInfo(ec):
"""Return a dict with information required to display the action """Return a dict with information required to display the action
The dict must contain the following keys: The dict must contain the following keys:
...@@ -50,12 +57,12 @@ class IAction(Interface): ...@@ -50,12 +57,12 @@ class IAction(Interface):
- priority (numeric) - priority (numeric)
""" """
class IActionProvider(Interface): class IActionContainer(Interface):
""" """
""" """
def getActionListFor(ob): def getCacheableActionList():
"""Return all actions of the object""" """Return a cacheable list of enabled actions
"""
def getFilteredActionListFor(ob): def getActionList():
"""Return all actions applicable to the object """Return the list of enabled actions from cache, sorted by priority
""" """
...@@ -39,10 +39,9 @@ def listFilteredActionsFor(self, object=None): ...@@ -39,10 +39,9 @@ def listFilteredActionsFor(self, object=None):
elif hasattr(provider, 'getActionListFor'): elif hasattr(provider, 'getActionListFor'):
from Products.ERP5Type.Utils import createExpressionContext from Products.ERP5Type.Utils import createExpressionContext
ec = createExpressionContext(object) ec = createExpressionContext(object)
actions.extend( actions.extend(action.cook(ec)
(action.cook(ec) for action in provider.getActionListFor(object)
for action in provider.getRawActionInformationListFor(object) if action.test(ec))
if action.test(ec)))
else: else:
# for Action Providers written for CMF versions before 1.5 # for Action Providers written for CMF versions before 1.5
actions.extend( self._listActionInfos(provider, object) ) actions.extend( self._listActionInfos(provider, object) )
......
...@@ -40,13 +40,11 @@ implements_tuple_list = [ ...@@ -40,13 +40,11 @@ implements_tuple_list = [
'IConsistencyMessage'), 'IConsistencyMessage'),
(('Products.ERP5Type.DivergenceMessage', 'DivergenceMessage'), (('Products.ERP5Type.DivergenceMessage', 'DivergenceMessage'),
'IDivergenceMessage'), 'IDivergenceMessage'),
(('Products.ERP5Type.Tool.TypesTool', 'TypesTool'),
'IActionProvider'),
(('Products.ERP5Type.ERP5Type', 'ERP5TypeInformation'), (('Products.ERP5Type.ERP5Type', 'ERP5TypeInformation'),
'IActionProvider'), 'IActionContainer'),
(('Products.ERP5Type.ERP5Type', 'ERP5TypeInformation'), (('Products.ERP5Type.ERP5Type', 'ERP5TypeInformation'),
'ILocalRoleAssignor'), 'ILocalRoleAssignor'),
(('Products.ERP5Type.Document.ActionInformation', 'ActionInformation'), (('Products.ERP5Type.Document.ActionInformation', 'CacheableAction'),
'IAction'), 'IAction'),
(('Products.ERP5Type.Document.RoleInformation', 'RoleInformation'), (('Products.ERP5Type.Document.RoleInformation', 'RoleInformation'),
'ILocalRoleGenerator'), 'ILocalRoleGenerator'),
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment