Back to index

plone3  3.1.7
ActionInformation.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
00004 #
00005 # This software is subject to the provisions of the Zope Public License,
00006 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
00007 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00008 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00009 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00010 # FOR A PARTICULAR PURPOSE.
00011 #
00012 ##############################################################################
00013 """ Information about customizable actions.
00014 
00015 $Id: ActionInformation.py 77186 2007-06-28 19:06:19Z yuppie $
00016 """
00017 
00018 from UserDict import UserDict
00019 
00020 from AccessControl import ClassSecurityInfo
00021 from Acquisition import aq_base, aq_inner, aq_parent
00022 from Globals import InitializeClass
00023 from OFS.ObjectManager import IFAwareObjectManager
00024 from OFS.OrderedFolder import OrderedFolder
00025 from OFS.PropertyManager import PropertyManager
00026 from OFS.SimpleItem import SimpleItem
00027 from zope.i18nmessageid import Message
00028 from zope.interface import implements
00029 
00030 from Expression import Expression
00031 from interfaces import IAction
00032 from interfaces import IActionCategory
00033 from interfaces import IActionInfo
00034 from interfaces.portal_actions import ActionInfo as z2IActionInfo
00035 from permissions import View
00036 from utils import _checkPermission
00037 from utils import getToolByName
00038 
00039 _unchanged = [] # marker
00040 
00041 
00042 class ActionCategory(IFAwareObjectManager, OrderedFolder):
00043 
00044     """ Group of Action objects.
00045     """
00046 
00047     implements(IActionCategory)
00048     __implements__ = OrderedFolder.__implements__
00049 
00050     _product_interfaces = (IActionCategory, IAction)
00051 
00052     security = ClassSecurityInfo()
00053 
00054     security.declarePrivate('listActions')
00055     def listActions(self):
00056         """ List the actions defined in this category and its subcategories.
00057         """
00058         actions = []
00059 
00060         for obj in self.objectValues():
00061             if IActionCategory.providedBy(obj):
00062                 actions.extend( obj.listActions() )
00063             elif IAction.providedBy(obj):
00064                 actions.append(obj)
00065 
00066         return tuple(actions)
00067 
00068 InitializeClass(ActionCategory)
00069 
00070 
00071 class Action(PropertyManager, SimpleItem):
00072 
00073     """ Reference to an action.
00074     """
00075 
00076     implements(IAction)
00077 
00078     i18n_domain = 'cmf_default'
00079 
00080     security = ClassSecurityInfo()
00081 
00082     _properties = (
00083         {'id': 'title', 'type': 'string', 'mode': 'w',
00084          'label': 'Title'},
00085         {'id': 'description', 'type': 'text', 'mode': 'w',
00086          'label': 'Description'},
00087         {'id':'i18n_domain', 'type': 'string', 'mode':'w',
00088          'label':'I18n Domain'},
00089         {'id': 'url_expr', 'type': 'string', 'mode': 'w',
00090          'label': 'URL (Expression)'},
00091         {'id': 'icon_expr', 'type': 'string', 'mode': 'w',
00092          'label': 'Icon (Expression)'},
00093         {'id': 'available_expr', 'type': 'string', 'mode': 'w',
00094          'label': 'Condition (Expression)'},
00095         {'id': 'permissions', 'type': 'multiple selection', 'mode': 'w',
00096          'label': 'Permissions', 'select_variable': 'possible_permissions'},
00097         {'id': 'visible', 'type': 'boolean', 'mode': 'w',
00098          'label': 'Visible?'},
00099         )
00100 
00101     manage_options = (
00102         PropertyManager.manage_options
00103         + SimpleItem.manage_options)
00104 
00105     def __init__(self, id, **kw):
00106         self.id = id
00107         self._setPropValue( 'title', kw.get('title', '') )
00108         self._setPropValue( 'description', kw.get('description', '') )
00109         self._setPropValue( 'i18n_domain', kw.get('i18n_domain', '') )
00110         self._setPropValue( 'url_expr', kw.get('url_expr', '') )
00111         self._setPropValue( 'icon_expr', kw.get('icon_expr', '') )
00112         self._setPropValue( 'available_expr', kw.get('available_expr', '') )
00113         self._setPropValue( 'permissions', kw.get('permissions', () ) )
00114         self._setPropValue( 'visible', kw.get('visible', True) )
00115 
00116     def _setPropValue(self, id, value):
00117         self._wrapperCheck(value)
00118         if isinstance(value, list):
00119             value = tuple(value)
00120         setattr(self, id, value)
00121         if value and id.endswith('_expr'):
00122             setattr( self, '%s_object' % id, Expression(value) )
00123 
00124     security.declarePrivate('getInfoData')
00125     def getInfoData(self):
00126         """ Get the data needed to create an ActionInfo.
00127         """
00128         category_path = []
00129         lazy_keys = []
00130         lazy_map = {}
00131 
00132         lazy_map['id'] = self.getId()
00133 
00134         parent = aq_parent(self)
00135         while parent is not None and parent.getId() != 'portal_actions':
00136             category_path.append( parent.getId() )
00137             parent = aq_parent(parent)
00138         lazy_map['category'] = '/'.join(category_path[::-1])
00139 
00140         for id, val in self.propertyItems():
00141             if id.endswith('_expr'):
00142                 id = id[:-5]
00143                 if val:
00144                     val = getattr(self, '%s_expr_object' % id)
00145                     lazy_keys.append(id)
00146                 elif id == 'available':
00147                     val = True
00148             elif id == 'i18n_domain':
00149                 continue
00150             elif self.i18n_domain and id in ('title', 'description'):
00151                 val = Message(val, self.i18n_domain)
00152             lazy_map[id] = val
00153 
00154         return (lazy_map, lazy_keys)
00155 
00156 InitializeClass(Action)
00157 
00158 
00159 class ActionInfo(UserDict):
00160 
00161     """ A lazy dictionary for Action infos.
00162     """
00163 
00164     implements(IActionInfo)
00165     __implements__ = z2IActionInfo
00166 
00167     __allow_access_to_unprotected_subobjects__ = 1
00168 
00169     def __init__(self, action, ec):
00170 
00171         if isinstance(action, dict):
00172             lazy_keys = []
00173             UserDict.__init__(self, action)
00174             if 'name' in self.data:
00175                 self.data.setdefault( 'id', self.data['name'].lower() )
00176                 self.data.setdefault( 'title', self.data['name'] )
00177                 del self.data['name']
00178             self.data.setdefault( 'url', '' )
00179             self.data.setdefault( 'category', 'object' )
00180             self.data.setdefault( 'visible', True )
00181             self.data['available'] = True
00182         else:
00183             # if action isn't a dict, it has to implement IAction
00184             (lazy_map, lazy_keys) = action.getInfoData()
00185             UserDict.__init__(self, lazy_map)
00186 
00187         self.data['allowed'] = True
00188         permissions = self.data.pop( 'permissions', () )
00189         if permissions:
00190             self.data['allowed'] = self._checkPermissions
00191             lazy_keys.append('allowed')
00192 
00193         self._ec = ec
00194         self._lazy_keys = lazy_keys
00195         self._permissions = permissions
00196 
00197     def __getitem__(self, key):
00198         value = UserDict.__getitem__(self, key)
00199         if key in self._lazy_keys:
00200             value = self.data[key] = value(self._ec)
00201             self._lazy_keys.remove(key)
00202         return value
00203 
00204     def __eq__(self, other):
00205         # this is expensive, use it with care
00206         [ self.__getitem__(key) for key in self._lazy_keys ]
00207 
00208         if isinstance(other, self.__class__):
00209             [ other[key] for key in other._lazy_keys ]
00210             return self.data == other.data
00211         elif isinstance(other, UserDict):
00212             return self.data == other.data
00213         else:
00214             return self.data == other
00215 
00216     def copy(self):
00217         c = UserDict.copy(self)
00218         c._lazy_keys = self._lazy_keys[:]
00219         return c
00220 
00221     def _checkPermissions(self, ec):
00222         """ Check permissions in the current context.
00223         """
00224         category = self['category']
00225         object = ec.contexts['object']
00226         if object is not None and ( category.startswith('object') or
00227                                     category.startswith('workflow') or
00228                                     category.startswith('document') ):
00229             context = object
00230         else:
00231             folder = ec.contexts['folder']
00232             if folder is not None and category.startswith('folder'):
00233                 context = folder
00234             else:
00235                 context = ec.contexts['portal']
00236 
00237         for permission in self._permissions:
00238             if _checkPermission(permission, context):
00239                 return True
00240         return False
00241 
00242 
00243 class ActionInformation( SimpleItem ):
00244 
00245     """ Represent a single selectable action.
00246 
00247     Actions generate links to views of content, or to specific methods
00248     of the site.  They can be filtered via their conditions.
00249     """
00250 
00251     implements(IAction)
00252 
00253     __allow_access_to_unprotected_subobjects__ = 1
00254 
00255     security = ClassSecurityInfo()
00256 
00257     def __init__( self
00258                 , id
00259                 , title=''
00260                 , description=''
00261                 , category='object'
00262                 , condition=''
00263                 , permissions=()
00264                 , priority=10
00265                 , visible=True
00266                 , action=''
00267                 ):
00268         """ Set up an instance.
00269         """
00270         self.edit( id
00271                  , title
00272                  , description
00273                  , category
00274                  , condition
00275                  , permissions
00276                  , priority
00277                  , visible
00278                  , action
00279                  )
00280 
00281     security.declarePrivate('edit')
00282     def edit( self
00283             , id=_unchanged
00284             , title=_unchanged
00285             , description=_unchanged
00286             , category=_unchanged
00287             , condition=_unchanged
00288             , permissions=_unchanged
00289             , priority=_unchanged
00290             , visible=_unchanged
00291             , action=_unchanged
00292             ):
00293         """Edit the specified properties.
00294         """
00295 
00296         if id is not _unchanged:
00297             self.id = id
00298         if title is not _unchanged:
00299             self.title = title
00300         if description is not _unchanged:
00301             self.description = description
00302         if category is not _unchanged:
00303             self.category = category
00304         if condition is not _unchanged:
00305             if condition and isinstance(condition, basestring):
00306                 condition = Expression(condition)
00307             self.condition = condition
00308         if permissions is not _unchanged:
00309             if permissions == ('',):
00310                 permissions = ()
00311             self.permissions = permissions
00312         if priority is not _unchanged:
00313             self.priority = priority
00314         if visible is not _unchanged:
00315             self.visible = visible
00316         if action is not _unchanged:
00317             if action and isinstance(action, basestring):
00318                 action = Expression(action)
00319             self.setActionExpression(action)
00320 
00321     security.declareProtected( View, 'Title' )
00322     def Title(self):
00323 
00324         """ Return the Action title.
00325         """
00326         return self.title or self.getId()
00327 
00328     security.declareProtected( View, 'Description' )
00329     def Description( self ):
00330 
00331         """ Return a description of the action.
00332         """
00333         return self.description
00334 
00335     security.declarePrivate( 'testCondition' )
00336     def testCondition( self, ec ):
00337 
00338         """ Evaluate condition using context, 'ec', and return 0 or 1.
00339         """
00340         if self.condition:
00341             return bool( self.condition(ec) )
00342         else:
00343             return True
00344 
00345     security.declarePublic( 'getAction' )
00346     def getAction( self, ec ):
00347 
00348         """ Compute the action using context, 'ec'; return a mapping of
00349             info about the action.
00350         """
00351         return ActionInfo(self, ec)
00352 
00353     security.declarePrivate( '_getActionObject' )
00354     def _getActionObject( self ):
00355 
00356         """ Find the action object, working around name changes.
00357         """
00358         action = getattr( self, 'action', None )
00359 
00360         if action is None:  # Forward compatibility, used to be '_action'
00361             action = getattr( self, '_action', None )
00362             if action is not None:
00363                 self.action = self._action
00364                 del self._action
00365 
00366         return action
00367 
00368     security.declarePublic( 'getActionExpression' )
00369     def getActionExpression( self ):
00370 
00371         """ Return the text of the TALES expression for our URL.
00372         """
00373         action = self._getActionObject()
00374         expr = action and action.text or ''
00375         if expr and isinstance(expr, basestring):
00376             if ( not expr.startswith('string:')
00377                  and not expr.startswith('python:') ):
00378                 expr = 'string:${object_url}/%s' % expr
00379                 self.action = Expression( expr )
00380         return expr
00381 
00382     security.declarePrivate( 'setActionExpression' )
00383     def setActionExpression(self, action):
00384         if action and isinstance(action, basestring):
00385             if ( not action.startswith('string:')
00386                  and not action.startswith('python:') ):
00387                 action = 'string:${object_url}/%s' % action
00388             action = Expression( action )
00389         self.action = action
00390 
00391     security.declarePublic( 'getCondition' )
00392     def getCondition(self):
00393 
00394         """ Return the text of the TALES expression for our condition.
00395         """
00396         return getattr( self, 'condition', None ) and self.condition.text or ''
00397 
00398     security.declarePublic( 'getPermissions' )
00399     def getPermissions( self ):
00400 
00401         """ Return the permission, if any, required to execute the action.
00402 
00403         Return an empty tuple if no permission is required.
00404         """
00405         return self.permissions
00406 
00407     security.declarePublic( 'getCategory' )
00408     def getCategory( self ):
00409 
00410         """ Return the category in which the action should be grouped.
00411         """
00412         return self.category or 'object'
00413 
00414     security.declarePublic( 'getVisibility' )
00415     def getVisibility( self ):
00416 
00417         """ Return whether the action should be visible in the CMF UI.
00418         """
00419         return bool(self.visible)
00420 
00421     security.declarePrivate('getMapping')
00422     def getMapping(self):
00423         """ Get a mapping of this object's data.
00424         """
00425         return { 'id': self.id,
00426                  'title': self.title or self.id,
00427                  'description': self.description,
00428                  'category': self.category or 'object',
00429                  'condition': getattr(self, 'condition', None)
00430                               and self.condition.text or '',
00431                  'permissions': self.permissions,
00432                  'visible': bool(self.visible),
00433                  'action': self.getActionExpression() }
00434 
00435     security.declarePrivate('clone')
00436     def clone( self ):
00437         """ Get a newly-created AI just like us.
00438         """
00439         return self.__class__( priority=self.priority, **self.getMapping() )
00440 
00441     security.declarePrivate('getInfoData')
00442     def getInfoData(self):
00443         """ Get the data needed to create an ActionInfo.
00444         """
00445         lazy_keys = []
00446         lazy_map = self.getMapping()
00447 
00448         if lazy_map['action']:
00449             lazy_map['url'] = self._getActionObject()
00450             lazy_keys.append('url')
00451         else:
00452             lazy_map['url'] = ''
00453         del lazy_map['action']
00454 
00455         if lazy_map['condition']:
00456             lazy_map['available'] = self.testCondition
00457             lazy_keys.append('available')
00458         else:
00459             lazy_map['available'] = True
00460         del lazy_map['condition']
00461 
00462         return (lazy_map, lazy_keys)
00463 
00464 InitializeClass( ActionInformation )
00465 
00466 
00467 def getOAI(context, object=None):
00468     request = getattr(context, 'REQUEST', None)
00469     if request:
00470         cache = request.get('_oai_cache', None)
00471         if cache is None:
00472             request['_oai_cache'] = cache = {}
00473         info = cache.get( id(object), None )
00474     else:
00475         info = None
00476     if info is None:
00477         if object is None or not hasattr(object, 'aq_base'):
00478             folder = None
00479         else:
00480             folder = object
00481             # Search up the containment hierarchy until we find an
00482             # object that claims it's a folder.
00483             while folder is not None:
00484                 if getattr(aq_base(folder), 'isPrincipiaFolderish', 0):
00485                     # found it.
00486                     break
00487                 else:
00488                     folder = aq_parent(aq_inner(folder))
00489         info = oai(context, folder, object)
00490         if request:
00491             cache[ id(object) ] = info
00492     return info
00493 
00494 
00495 class oai:
00496 
00497     #Provided for backwards compatability
00498     # Provides information that may be needed when constructing the list of
00499     # available actions.
00500     __allow_access_to_unprotected_subobjects__ = 1
00501 
00502     def __init__( self, tool, folder, object=None ):
00503         self.portal = portal = aq_parent(aq_inner(tool))
00504         membership = getToolByName(tool, 'portal_membership')
00505         self.isAnonymous = membership.isAnonymousUser()
00506         self.user_id = membership.getAuthenticatedMember().getId()
00507         self.portal_url = portal.absolute_url()
00508         if folder is not None:
00509             self.folder_url = folder.absolute_url()
00510             self.folder = folder
00511         else:
00512             self.folder_url = self.portal_url
00513             self.folder = portal
00514 
00515         # The name "content" is deprecated and will go away in CMF 2.0!
00516         self.object = self.content = object
00517         if object is not None:
00518             self.content_url = self.object_url = object.absolute_url()
00519         else:
00520             self.content_url = self.object_url = None
00521 
00522     def __getitem__(self, name):
00523         # Mapping interface for easy string formatting.
00524         if name[:1] == '_':
00525             raise KeyError, name
00526         if hasattr(self, name):
00527             return getattr(self, name)
00528         raise KeyError, name