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 @@
<key> <string>after_script_name</string> </key>
<value>
<list>
<string>ActionInformation_clearCache</string>
<string>BaseType_clearCache</string>
</list>
</value>
</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 @@
</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>
......
1341
\ No newline at end of file
1342
\ No newline at end of file
......@@ -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()
......
......@@ -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')
......
......@@ -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):
......
......@@ -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
......
......@@ -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
"""
......@@ -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) )
......
......@@ -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'),
......
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