Commit ad42d728 authored by Florent Guillaume's avatar Florent Guillaume

Zope now sends Zope 3 events when objects are added or removed from

standard containers. manage_afterAdd, manage_beforeDelete and
manage_afterClone are now deprecated. See
lib/python/Products/Five/tests/event.txt for details.

This requires Five 1.3 (included in Zope 2.9).
parent 7a3b9de0
......@@ -26,6 +26,11 @@ Zope Changes
Features added
- Zope now sends Zope 3 events when objects are added or removed
from standard containers. manage_afterAdd, manage_beforeDelete
and manage_afterClone are now deprecated. See
lib/python/Products/Five/tests/event.txt for details.
- Zope now utilizes ZODB 3.6. It had previously used
ZODB 3.4. As a result, the DBTab package was removed, as
ZODB 3.6 has multidatabase support that makes DBTab
......
......@@ -459,6 +459,10 @@ class AppInitializer:
from Products.Sessions.BrowserIdManager import BrowserIdManager
bid = BrowserIdManager('browser_id_manager', 'Browser Id Manager')
app._setObject('browser_id_manager', bid)
# FIXME explicitely call manage_afterAdd, as sometimes
# events are initialized too late
browser_id_manager = app.browser_id_manager
browser_id_manager.manage_afterAdd(browser_id_manager, app)
app._setInitializerFlag('browser_id_manager')
self.commit('Added browser_id_manager')
......@@ -475,6 +479,10 @@ class AppInitializer:
path='/temp_folder/session_data',
requestName='SESSION')
app._setObject('session_data_manager', sdm)
# FIXME explicitely call manage_afterAdd, as sometimes
# events are initialized too late
session_data_manager = app.session_data_manager
session_data_manager.manage_afterAdd(session_data_manager, app)
app._setInitializerFlag('session_data_manager')
self.commit('Added session_data_manager')
......@@ -523,6 +531,10 @@ class AppInitializer:
from Products.SiteErrorLog.SiteErrorLog import SiteErrorLog
error_log = SiteErrorLog()
app._setObject('error_log', error_log)
# FIXME explicitely call manage_afterAdd, as sometimes
# events are initialized too late
error_log = app.error_log
error_log.manage_afterAdd(error_log, app)
app._setInitializerFlag('error_log')
self.commit('Added site error_log at /error_log')
......
......@@ -30,6 +30,11 @@ from App.Dialogs import MessageDialog
from webdav.Lockable import ResourceLockedError
from zExceptions import Unauthorized, BadRequest
from zope.interface import implements
from zope.event import notify
from zope.app.event.objectevent import ObjectCopiedEvent
from zope.app.container.contained import ObjectMovedEvent
from Products.Five.event import ObjectWillBeMovedEvent
from Products.Five.event import FiveObjectClonedEvent
from OFS.interfaces import ICopyContainer
from OFS.interfaces import ICopySource
......@@ -154,85 +159,123 @@ class CopyContainer(ExtensionClass.Base):
If calling manage_pasteObjects from python code, pass the result of a
previous call to manage_cutObjects or manage_copyObjects as the first
argument.
Also sends IObjectCopiedEvent and IFiveObjectClonedEvent
or IObjectWillBeMovedEvent and IObjectMovedEvent.
"""
cp=None
# Done here to avoid circular imports
from Products.Five.subscribers import maybeCallDeprecated
if cb_copy_data is not None:
cp=cb_copy_data
cp = cb_copy_data
elif REQUEST is not None and REQUEST.has_key('__cp'):
cp = REQUEST['__cp']
else:
if REQUEST and REQUEST.has_key('__cp'):
cp=REQUEST['__cp']
cp = None
if cp is None:
raise CopyError, eNoData
try: cp=_cb_decode(cp)
except: raise CopyError, eInvalid
try:
op, mdatas = _cb_decode(cp)
except:
raise CopyError, eInvalid
oblist=[]
op=cp[0]
oblist = []
app = self.getPhysicalRoot()
result = []
for mdata in cp[1]:
for mdata in mdatas:
m = Moniker.loadMoniker(mdata)
try: ob = m.bind(app)
except: raise CopyError, eNotFound
try:
ob = m.bind(app)
except ConflictError:
raise
except:
raise CopyError, eNotFound
self._verifyObjectPaste(ob, validate_src=op+1)
oblist.append(ob)
if op==0:
result = []
if op == 0:
# Copy operation
for ob in oblist:
orig_id = ob.getId()
if not ob.cb_isCopyable():
raise CopyError, eNotSupported % escape(ob.getId())
try: ob._notifyOfCopyTo(self, op=0)
except: raise CopyError, MessageDialog(
title='Copy Error',
message=sys.exc_info()[1],
action ='manage_main')
ob=ob._getCopy(self)
orig_id=ob.getId()
id=self._get_id(ob.getId())
result.append({'id':orig_id, 'new_id':id})
raise CopyError, eNotSupported % escape(orig_id)
try:
ob._notifyOfCopyTo(self, op=0)
except ConflictError:
raise
except:
raise CopyError, MessageDialog(
title="Copy Error",
message=sys.exc_info()[1],
action='manage_main')
id = self._get_id(orig_id)
result.append({'id': orig_id, 'new_id': id})
ob = ob._getCopy(self)
ob._setId(id)
notify(ObjectCopiedEvent(ob))
self._setObject(id, ob)
ob = self._getOb(id)
ob._postCopy(self, op=0)
ob.manage_afterClone(ob)
ob.wl_clearLocks()
ob._postCopy(self, op=0)
maybeCallDeprecated('manage_afterClone', ob)
notify(FiveObjectClonedEvent(ob))
if REQUEST is not None:
return self.manage_main(self, REQUEST, update_menu=1,
cb_dataValid=1)
if op==1:
elif op == 1:
# Move operation
for ob in oblist:
id=ob.getId()
orig_id = ob.getId()
if not ob.cb_isMoveable():
raise CopyError, eNotSupported % escape(id)
try: ob._notifyOfCopyTo(self, op=1)
except: raise CopyError, MessageDialog(
title='Move Error',
message=sys.exc_info()[1],
action ='manage_main')
raise CopyError, eNotSupported % escape(orig_id)
try:
ob._notifyOfCopyTo(self, op=1)
except ConflictError:
raise
except:
raise CopyError, MessageDialog(
title="Move Error",
message=sys.exc_info()[1],
action='manage_main')
if not sanity_check(self, ob):
raise CopyError, 'This object cannot be pasted into itself'
raise CopyError, "This object cannot be pasted into itself"
orig_container = aq_parent(aq_inner(ob))
if aq_base(orig_container) is aq_base(self):
id = orig_id
else:
id = self._get_id(orig_id)
result.append({'id': orig_id, 'new_id': id})
notify(ObjectWillBeMovedEvent(ob, orig_container, orig_id,
self, id))
# try to make ownership explicit so that it gets carried
# along to the new location if needed.
ob.manage_changeOwnershipType(explicit=1)
aq_parent(aq_inner(ob))._delObject(id)
orig_container._delObject(orig_id, suppress_events=True)
ob = aq_base(ob)
orig_id=id
id=self._get_id(id)
result.append({'id':orig_id, 'new_id':id })
ob._setId(id)
self._setObject(id, ob, set_owner=0)
ob=self._getOb(id)
ob._postCopy(self, op=1)
self._setObject(id, ob, set_owner=0, suppress_events=True)
ob = self._getOb(id)
notify(ObjectMovedEvent(ob, orig_container, orig_id, self, id))
ob._postCopy(self, op=1)
# try to make ownership implicit if possible
ob.manage_changeOwnershipType(explicit=0)
......@@ -243,6 +286,7 @@ class CopyContainer(ExtensionClass.Base):
REQUEST['__cp'] = None
return self.manage_main(self, REQUEST, update_menu=1,
cb_dataValid=0)
return result
manage_renameForm=Globals.DTMLFile('dtml/renameForm', globals())
......@@ -259,31 +303,48 @@ class CopyContainer(ExtensionClass.Base):
return None
def manage_renameObject(self, id, new_id, REQUEST=None):
"""Rename a particular sub-object"""
try: self._checkId(new_id)
except: raise CopyError, MessageDialog(
title='Invalid Id',
message=sys.exc_info()[1],
action ='manage_main')
ob=self._getOb(id)
"""Rename a particular sub-object.
"""
try:
self._checkId(new_id)
except:
raise CopyError, MessageDialog(
title='Invalid Id',
message=sys.exc_info()[1],
action ='manage_main')
ob = self._getOb(id)
if ob.wl_isLocked():
raise ResourceLockedError, 'Object "%s" is locked via WebDAV' % ob.getId()
raise ResourceLockedError, ('Object "%s" is locked via WebDAV'
% ob.getId())
if not ob.cb_isMoveable():
raise CopyError, eNotSupported % escape(id)
self._verifyObjectPaste(ob)
try: ob._notifyOfCopyTo(self, op=1)
except: raise CopyError, MessageDialog(
title='Rename Error',
message=sys.exc_info()[1],
action ='manage_main')
self._delObject(id)
try:
ob._notifyOfCopyTo(self, op=1)
except ConflictError:
raise
except:
raise CopyError, MessageDialog(
title="Rename Error",
message=sys.exc_info()[1],
action ='manage_main')
notify(ObjectWillBeMovedEvent(ob, self, id, self, new_id))
self._delObject(id, suppress_events=True)
ob = aq_base(ob)
ob._setId(new_id)
# Note - because a rename always keeps the same context, we
# can just leave the ownership info unchanged.
self._setObject(new_id, ob, set_owner=0)
self._setObject(new_id, ob, set_owner=0, suppress_events=True)
ob = self._getOb(new_id)
notify(ObjectMovedEvent(ob, self, id, self, new_id))
ob._postCopy(self, op=1)
if REQUEST is not None:
......@@ -296,26 +357,46 @@ class CopyContainer(ExtensionClass.Base):
# Because it's still a "management" function.
manage_clone__roles__=None
def manage_clone(self, ob, id, REQUEST=None):
# Clone an object, creating a new object with the given id.
"""Clone an object, creating a new object with the given id.
"""
# Done here to avoid circular imports
from Products.Five.subscribers import maybeCallDeprecated
if not ob.cb_isCopyable():
raise CopyError, eNotSupported % escape(ob.getId())
try: self._checkId(id)
except: raise CopyError, MessageDialog(
title='Invalid Id',
message=sys.exc_info()[1],
action ='manage_main')
try:
self._checkId(id)
except:
raise CopyError, MessageDialog(
title='Invalid Id',
message=sys.exc_info()[1],
action ='manage_main')
self._verifyObjectPaste(ob)
try: ob._notifyOfCopyTo(self, op=0)
except: raise CopyError, MessageDialog(
title='Clone Error',
message=sys.exc_info()[1],
action ='manage_main')
ob=ob._getCopy(self)
try:
ob._notifyOfCopyTo(self, op=0)
except ConflictError:
raise
except:
raise CopyError, MessageDialog(
title="Clone Error",
message=sys.exc_info()[1],
action='manage_main')
ob = ob._getCopy(self)
ob._setId(id)
notify(ObjectCopiedEvent(ob))
self._setObject(id, ob)
ob=self._getOb(id)
ob = self._getOb(id)
ob._postCopy(self, op=0)
ob.manage_afterClone(ob)
maybeCallDeprecated('manage_afterClone', ob)
notify(FiveObjectClonedEvent(ob))
return ob
def cb_dataValid(self):
......
......@@ -15,6 +15,7 @@
$Id$
"""
import warnings
import marshal
import sys, fnmatch, copy, os, re
from cgi import escape
......@@ -42,6 +43,11 @@ from zope.interface import implements
import CopySupport
from interfaces import IObjectManager
from Traversable import Traversable
from zope.event import notify
from zope.app.container.contained import ObjectAddedEvent
from zope.app.container.contained import ObjectRemovedEvent
from Products.Five.event import ObjectWillBeAddedEvent
from Products.Five.event import ObjectWillBeRemovedEvent
# the name BadRequestException is relied upon by 3rd-party code
......@@ -266,11 +272,20 @@ class ObjectManager(
raise AttributeError, id
return default
def _setObject(self, id, object, roles=None, user=None, set_owner=1):
v=self._checkId(id)
if v is not None: id=v
try: t=object.meta_type
except: t=None
def _setObject(self, id, object, roles=None, user=None, set_owner=1,
suppress_events=False):
"""Set an object into this container.
Also sends IObjectWillBeAddedEvent and IObjectAddedEvent.
"""
# Done here to avoid circular imports
from Products.Five.subscribers import maybeCallDeprecated
ob = object # better name, keep original function signature
v = self._checkId(id)
if v is not None:
id = v
t = getattr(ob, 'meta_type', None)
# If an object by the given id already exists, remove it.
for object_info in self._objects:
......@@ -278,78 +293,78 @@ class ObjectManager(
self._delObject(id)
break
self._objects=self._objects+({'id':id,'meta_type':t},)
self._setOb(id,object)
object=self._getOb(id)
if not suppress_events:
notify(ObjectWillBeAddedEvent(ob, self, id))
self._objects = self._objects + ({'id': id, 'meta_type': t},)
self._setOb(id, ob)
ob = self._getOb(id)
if set_owner:
object.manage_fixupOwnershipAfterAdd()
# TODO: eventify manage_fixupOwnershipAfterAdd
# This will be called for a copy/clone, or a normal _setObject.
ob.manage_fixupOwnershipAfterAdd()
# Try to give user the local role "Owner", but only if
# no local roles have been set on the object yet.
if hasattr(object, '__ac_local_roles__'):
if object.__ac_local_roles__ is None:
user=getSecurityManager().getUser()
if user is not None:
userid=user.getId()
if userid is not None:
object.manage_setLocalRoles(userid, ['Owner'])
object.manage_afterAdd(object, self)
if getattr(ob, '__ac_local_roles__', _marker) is None:
user = getSecurityManager().getUser()
if user is not None:
userid = user.getId()
if userid is not None:
ob.manage_setLocalRoles(userid, ['Owner'])
if not suppress_events:
notify(ObjectAddedEvent(ob, self, id))
maybeCallDeprecated('manage_afterAdd', ob, self)
return id
def manage_afterAdd(self, item, container):
for object in self.objectValues():
try: s=object._p_changed
except: s=0
if hasattr(aq_base(object), 'manage_afterAdd'):
object.manage_afterAdd(item, container)
if s is None: object._p_deactivate()
# Don't do recursion anymore, a subscriber does that.
warnings.warn(
"%s.manage_afterAdd is deprecated and will be removed in "
"Zope 2.11, you should use an IObjectAddedEvent "
"subscriber instead." % self.__class__.__name__,
DeprecationWarning, stacklevel=2)
manage_afterAdd.__five_method__ = True
def manage_afterClone(self, item):
for object in self.objectValues():
try: s=object._p_changed
except: s=0
if hasattr(aq_base(object), 'manage_afterClone'):
object.manage_afterClone(item)
if s is None: object._p_deactivate()
# Don't do recursion anymore, a subscriber does that.
warnings.warn(
"%s.manage_afterClone is deprecated and will be removed in "
"Zope 2.11, you should use an IFiveObjectClonedEvent "
"subscriber instead." % self.__class__.__name__,
DeprecationWarning, stacklevel=2)
manage_afterClone.__five_method__ = True
def manage_beforeDelete(self, item, container):
for object in self.objectValues():
try: s=object._p_changed
except: s=0
try:
if hasattr(aq_base(object), 'manage_beforeDelete'):
object.manage_beforeDelete(item, container)
except BeforeDeleteException, ob:
raise
except ConflictError:
raise
except:
LOG('Zope',ERROR,'manage_beforeDelete() threw',
error=sys.exc_info())
# In debug mode when non-Manager, let exceptions propagate.
if getConfiguration().debug_mode:
if not getSecurityManager().getUser().has_role('Manager'):
raise
if s is None: object._p_deactivate()
def _delObject(self, id, dp=1):
object=self._getOb(id)
try:
object.manage_beforeDelete(object, self)
except BeforeDeleteException, ob:
raise
except ConflictError:
raise
except:
LOG('Zope', ERROR, '_delObject() threw',
error=sys.exc_info())
# In debug mode when non-Manager, let exceptions propagate.
if getConfiguration().debug_mode:
if not getSecurityManager().getUser().has_role('Manager'):
raise
self._objects=tuple(filter(lambda i,n=id: i['id']!=n, self._objects))
# Don't do recursion anymore, a subscriber does that.
warnings.warn(
"%s.manage_beforeDelete is deprecated and will be removed in "
"Zope 2.11, you should use an IObjectWillBeRemovedEvent "
"subscriber instead." % self.__class__.__name__,
DeprecationWarning, stacklevel=2)
manage_beforeDelete.__five_method__ = True
def _delObject(self, id, dp=1, suppress_events=False):
"""Delete an object from this container.
Also sends IObjectWillBeRemovedEvent and IObjectRemovedEvent.
"""
# Done here to avoid circular imports
from Products.Five.subscribers import maybeCallDeprecated
ob = self._getOb(id)
maybeCallDeprecated('manage_beforeDelete', ob, self)
if not suppress_events:
notify(ObjectWillBeRemovedEvent(ob, self, id))
self._objects = tuple([i for i in self._objects
if i['id'] != id])
self._delOb(id)
# Indicate to the object that it has been deleted. This is
......@@ -357,8 +372,13 @@ class ObjectManager(
# tolerate failure here because the object being deleted could
# be a Broken object, and it is not possible to set attributes
# on Broken objects.
try: object._v__object_deleted__ = 1
except: pass
try:
ob._v__object_deleted__ = 1
except:
pass
if not suppress_events:
notify(ObjectRemovedEvent(ob, self, id))
def objectIds(self, spec=None):
# Returns a list of subobject ids of the current object.
......
......@@ -30,7 +30,7 @@ from IOrderSupport import IOrderedContainer as z2IOrderedContainer
from ObjectManager import ObjectManager
class OrderSupport:
class OrderSupport(object):
""" Ordered container mixin class.
......@@ -251,13 +251,12 @@ class OrderSupport:
# Override Inherited Method of ObjectManager Subclass
#
_old_manage_renameObject = ObjectManager.inheritedAttribute(
'manage_renameObject')
def manage_renameObject(self, id, new_id, REQUEST=None):
""" Rename a particular sub-object without changing its position.
"""
old_position = self.getObjectPosition(id)
result = self._old_manage_renameObject(id, new_id, REQUEST)
result = super(OrderSupport, self).manage_renameObject(id, new_id,
REQUEST)
self.moveObjectToPosition(new_id, old_position)
return result
......
......@@ -20,6 +20,7 @@ item types.
$Id$
"""
import warnings
import marshal, re, sys, time
import AccessControl.Role, AccessControl.Owned, App.Common
......@@ -60,13 +61,28 @@ class Item(Base, Resource, CopySource, App.Management.Tabs, Traversable,
isTopLevelPrincipiaApplicationObject=0
def manage_afterAdd(self, item, container):
pass
warnings.warn(
"%s.manage_afterAdd is deprecated and will be removed in "
"Zope 2.11, you should use an IObjectAddedEvent "
"subscriber instead." % self.__class__.__name__,
DeprecationWarning, stacklevel=2)
manage_afterAdd.__five_method__ = True
def manage_beforeDelete(self, item, container):
pass
warnings.warn(
"%s.manage_beforeDelete is deprecated and will be removed in "
"Zope 2.11, you should use an IObjectWillBeRemovedEvent "
"subscriber instead." % self.__class__.__name__,
DeprecationWarning, stacklevel=2)
manage_beforeDelete.__five_method__ = True
def manage_afterClone(self, item):
pass
warnings.warn(
"%s.manage_afterClone is deprecated and will be removed in "
"Zope 2.11, you should use an IFiveObjectClonedEvent "
"subscriber instead." % self.__class__.__name__,
DeprecationWarning, stacklevel=2)
manage_afterClone.__five_method__ = True
# Direct use of the 'id' attribute is deprecated - use getId()
id=''
......
......@@ -37,6 +37,11 @@ from AccessControl.Permissions import access_contents_information, \
view_management_screens
from zLOG import LOG, INFO, ERROR, WARNING
from Products.ZCatalog.Lazy import LazyMap
from zope.event import notify
from zope.app.container.contained import ObjectAddedEvent
from zope.app.container.contained import ObjectRemovedEvent
from Products.Five.event import ObjectWillBeAddedEvent
from Products.Five.event import ObjectWillBeRemovedEvent
manage_addBTreeFolderForm = DTMLFile('folderAdd', globals())
......@@ -404,47 +409,64 @@ class BTreeFolder2Base (Persistent):
'it is already in use.' % id)
def _setObject(self, id, object, roles=None, user=None, set_owner=1):
v=self._checkId(id)
if v is not None: id=v
def _setObject(self, id, object, roles=None, user=None, set_owner=1,
suppress_events=False):
# Done here to avoid circular imports
from Products.Five.subscribers import maybeCallDeprecated
ob = object # better name, keep original function signature
v = self._checkId(id)
if v is not None:
id = v
# If an object by the given id already exists, remove it.
if self.has_key(id):
self._delObject(id)
self._setOb(id, object)
object = self._getOb(id)
if not suppress_events:
notify(ObjectWillBeAddedEvent(ob, self, id))
self._setOb(id, ob)
ob = self._getOb(id)
if set_owner:
object.manage_fixupOwnershipAfterAdd()
# TODO: eventify manage_fixupOwnershipAfterAdd
# This will be called for a copy/clone, or a normal _setObject.
ob.manage_fixupOwnershipAfterAdd()
# Try to give user the local role "Owner", but only if
# no local roles have been set on the object yet.
if hasattr(object, '__ac_local_roles__'):
if object.__ac_local_roles__ is None:
user=getSecurityManager().getUser()
if user is not None:
userid=user.getId()
if userid is not None:
object.manage_setLocalRoles(userid, ['Owner'])
object.manage_afterAdd(object, self)
if getattr(ob, '__ac_local_roles__', _marker) is None:
user = getSecurityManager().getUser()
if user is not None:
userid = user.getId()
if userid is not None:
ob.manage_setLocalRoles(userid, ['Owner'])
if not suppress_events:
notify(ObjectAddedEvent(ob, self, id))
maybeCallDeprecated('manage_afterAdd', ob, self)
return id
def _delObject(self, id, dp=1):
object = self._getOb(id)
try:
object.manage_beforeDelete(object, self)
except BeforeDeleteException, ob:
raise
except ConflictError:
raise
except:
LOG('Zope', ERROR, 'manage_beforeDelete() threw',
error=sys.exc_info())
def _delObject(self, id, dp=1, suppress_events=False):
# Done here to avoid circular imports
from Products.Five.subscribers import maybeCallDeprecated
ob = self._getOb(id)
maybeCallDeprecated('manage_beforeDelete', ob, self)
if not suppress_events:
notify(ObjectWillBeRemovedEvent(ob, self, id))
self._delOb(id)
if not suppress_events:
notify(ObjectRemovedEvent(ob, self, id))
# Aliases for mapping-like access.
__len__ = objectCount
......
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