[Zope3-checkins] CVS: Zope3/src/zope/app - copypastemove.py:1.11

Jim Fulton jim at zope.com
Sun Sep 21 13:30:39 EDT 2003


Update of /cvs-repository/Zope3/src/zope/app
In directory cvs.zope.org:/tmp/cvs-serv11813/src/zope/app

Modified Files:
	copypastemove.py 
Log Message:
Refactored the copy/paste/move framework:

- Got rid of CopySource, MoveSource, and PasteTarget.

- The mover and copier no longer generate events. This is done by
  containers.

- For now, allow moving copying between and only only between
  containers with the same class.  This will change when
  http://dev.zope.org/Zope3/ContainmentConstraints is done.

- Use INameChooser to pick new names when moving or copying.

- The copier and mover have to remove security proxied before saving
  in the container.  This used to be done by the containment
  decorator, so this is not a change of policy.

We need to think about the security model for copy and move.

- Got rid of the NoChildrenObjectCopier and associated interfaces and
  adapters.

Simpified the container interface to use __setitem__ rather than
setObject.

Added a "rename" helper function.


=== Zope3/src/zope/app/copypastemove.py 1.10 => 1.11 ===
--- Zope3/src/zope/app/copypastemove.py:1.10	Fri Jun 13 13:41:10 2003
+++ Zope3/src/zope/app/copypastemove.py	Sun Sep 21 13:30:08 2003
@@ -17,26 +17,99 @@
 $Id$
 """
 
-from zope.app.traversing import getParent, getName, getPath
-from zope.component import getAdapter, queryAdapter
+from zope.app import zapi
+from zope.app.i18n import ZopeMessageIDFactory as _
 from zope.app.interfaces.copypastemove import IObjectMover
 from zope.app.interfaces.copypastemove import IObjectCopier
-from zope.app.interfaces.copypastemove import INoChildrenObjectCopier
 from zope.app.interfaces.container import IAddNotifiable
-from zope.app.interfaces.container import IDeleteNotifiable
-from zope.app.interfaces.container import IMoveNotifiable
-from zope.app.interfaces.container import ICopyNotifiable
-from zope.app.interfaces.container import IMoveSource
-from zope.app.interfaces.container import ICopySource, INoChildrenCopySource
-from zope.app.interfaces.container import CopyException
-from zope.app.interfaces.container import IPasteTarget
-from zope.app.event.objectevent import ObjectMovedEvent, ObjectCopiedEvent
-from zope.app.event import publish
+from zope.app.interfaces.container import INameChooser
 from zope.proxy import removeAllProxies
 from zope.interface import implements
+from zope.exceptions import NotFoundError, DuplicationError
+from zope.app.location import locationCopy
+from zope.app.event import publish
+from zope.app.event.objectevent import ObjectCopiedEvent
+from zope.proxy import removeAllProxies
 
 class ObjectMover:
-    '''Use getAdapter(obj, IObjectMover) to move an object somewhere.'''
+    """Adapter for moving objects between containers
+
+    To use an object mover, pass a contained object to the class.
+    The contained object should implement IContained.  It should be
+    contained in a container that has an adapter to INameChooser.
+
+    >>> from zope.app.container.sample import SampleContainer
+    >>> class SampleContainer(SampleContainer):
+    ...     implements(INameChooser)
+    ...     def chooseName(self, name, ob):
+    ...        while name in self:
+    ...           name += '_'
+    ...        return name
+
+    >>> from zope.app.container.contained import Contained
+    >>> ob = Contained()
+    >>> container = SampleContainer()
+    >>> container[u'foo'] = ob
+    >>> mover = ObjectMover(ob)
+
+    In addition to moving objects, object movers can tell you if the
+    object is movable:
+
+    >>> mover.moveable()
+    1
+
+    which, at least for now, they always are.  A better question to
+    ask is whether we can move to a particular container. Right now,
+    we can always move to a container of the same class:
+
+    >>> container2 = SampleContainer()
+    >>> mover.moveableTo(container2)
+    1
+    >>> mover.moveableTo({})
+    0
+
+    Of course, once we've decided we can move an object, we can use
+    the mover to do so:
+
+    >>> mover.moveTo(container2)
+    >>> list(container)
+    []
+    >>> list(container2)
+    [u'foo']
+    >>> ob.__parent__ is container2
+    1
+    
+    We can also specify a name:
+
+    >>> mover.moveTo(container2, u'bar')
+    >>> list(container2)
+    [u'bar']
+    >>> ob.__parent__ is container2
+    1
+    >>> ob.__name__
+    u'bar'
+
+    But we may not use the same name given, if the name is already in
+    use:
+
+    >>> container2[u'splat'] = 1
+    >>> mover.moveTo(container2, u'splat')
+    >>> l = list(container2)
+    >>> l.sort()
+    >>> l
+    [u'splat', u'splat_']
+    >>> ob.__name__
+    u'splat_'
+    
+
+    If we try to move to an invalid container, we'll get an error:
+
+    >>> mover.moveTo({})
+    Traceback (most recent call last):
+    ...
+    TypeError: Can only move objects between like containers
+        
+    """
 
     implements(IObjectMover)
 
@@ -50,39 +123,31 @@
         Typically, the target is adapted to IPasteTarget.'''
 
         obj = self.context
-        container = getParent(obj)
-        orig_name = getName(obj)
+        container = obj.__parent__
+
+        # XXX Only allow moving between the same types of containers for
+        # now, until we can properly implement containment constraints:
+        if target.__class__ != container.__class__:
+            raise TypeError(
+                _("Can only move objects between like containers")
+                )
+        
+        orig_name = obj.__name__
         if new_name is None:
             new_name = orig_name
 
-        movesource = getAdapter(container, IMoveSource)
-        target_path = getPath(target)
-        source_path = getPath(container)
-
-        if queryAdapter(obj, IMoveNotifiable):
-            getAdapter(obj, IMoveNotifiable).beforeDeleteHook(
-                obj, container, movingTo=target_path)
-        elif queryAdapter(obj, IDeleteNotifiable):
-            getAdapter(obj, IDeleteNotifiable).beforeDeleteHook(obj, container)
-
-        new_obj = movesource.removeObject(orig_name, target)
-        pastetarget = getAdapter(target, IPasteTarget)
-        # publish an ObjectCreatedEvent (perhaps...?)
-        new_name = pastetarget.pasteObject(new_name, new_obj)
-
-        # call afterAddHook
-        if queryAdapter(new_obj, IMoveNotifiable):
-            getAdapter(new_obj, IMoveNotifiable).afterAddHook(
-                new_obj, container, movedFrom=source_path)
-        elif queryAdapter(new_obj, IAddNotifiable):
-            getAdapter(new_obj, IAddNotifiable).afterAddHook(
-                new_obj, container)
-
-        # publish ObjectMovedEvent
-        publish(container, ObjectMovedEvent(
-            container, source_path, target_path))
+        if target is container and new_name == orig_name:
+            # Nothing to do
+            return
 
-        return new_name
+        chooser = zapi.getAdapter(target, INameChooser)
+        new_name = chooser.chooseName(new_name, obj)
+
+        # Can't store security proxies
+        obj = removeAllProxies(obj)
+
+        target[new_name] = obj
+        del container[orig_name]
 
     def moveable(self):
         '''Returns True if the object is moveable, otherwise False.'''
@@ -94,13 +159,99 @@
         Returns True if it can be moved there. Otherwise, returns
         false.
         '''
-        obj = self.context
-        if name is None:
-            name = getName(obj)
-        pastetarget = getAdapter(target, IPasteTarget)
-        return pastetarget.acceptsObject(name, obj)
+        return self.context.__parent__.__class__ == target.__class__ 
 
 class ObjectCopier:
+    """Adapter for copying objects between containers
+
+    To use an object copier, pass a contained object to the class.
+    The contained object should implement IContained.  It should be
+    contained in a container that has an adapter to INameChooser.
+
+    >>> from zope.app.container.sample import SampleContainer
+    >>> class SampleContainer(SampleContainer):
+    ...     implements(INameChooser)
+    ...     def chooseName(self, name, ob):
+    ...        while name in self:
+    ...           name += '_'
+    ...        return name
+
+    >>> from zope.app.container.contained import Contained
+    >>> ob = Contained()
+    >>> container = SampleContainer()
+    >>> container[u'foo'] = ob
+    >>> copier = ObjectCopier(ob)
+
+    In addition to moving objects, object copiers can tell you if the
+    object is movable:
+
+    >>> copier.copyable()
+    1
+
+    which, at least for now, they always are.  A better question to
+    ask is whether we can copy to a particular container. Right now,
+    we can always copy to a container of the same class:
+
+    >>> container2 = SampleContainer()
+    >>> copier.copyableTo(container2)
+    1
+    >>> copier.copyableTo({})
+    0
+
+    Of course, once we've decided we can copy an object, we can use
+    the copier to do so:
+
+    >>> copier.copyTo(container2)
+    >>> list(container)
+    [u'foo']
+    >>> list(container2)
+    [u'foo']
+    >>> ob.__parent__ is container
+    1
+    >>> container2[u'foo'] is ob
+    0
+    >>> container2[u'foo'].__parent__ is container2
+    1
+    >>> container2[u'foo'].__name__
+    u'foo'
+    
+    We can also specify a name:
+
+    >>> copier.copyTo(container2, u'bar')
+    >>> l = list(container2)
+    >>> l.sort()
+    >>> l
+    [u'bar', u'foo']
+
+    >>> ob.__parent__ is container
+    1
+    >>> container2[u'bar'] is ob
+    0
+    >>> container2[u'bar'].__parent__ is container2
+    1
+    >>> container2[u'bar'].__name__
+    u'bar'
+
+    But we may not use the same name given, if the name is already in
+    use:
+
+    >>> copier.copyTo(container2, u'bar')
+    >>> l = list(container2)
+    >>> l.sort()
+    >>> l
+    [u'bar', u'bar_', u'foo']
+    >>> container2[u'bar_'].__name__
+    u'bar_'
+    
+
+    If we try to copy to an invalid container, we'll get an error:
+
+    >>> copier.copyTo({})
+    Traceback (most recent call last):
+    ...
+    TypeError: Can only copy objects to like containers
+        
+    """
 
     implements(IObjectCopier)
 
@@ -119,33 +270,28 @@
         an IObjectCreated event should be published.
         """
         obj = self.context
-        container = getParent(obj)
-        orig_name = getName(obj)
+        container = obj.__parent__
+
+        # XXX Only allow moving between the same types of containers for
+        # now, until we can properly implement containment constraints:
+        if target.__class__ != container.__class__:
+            raise TypeError(
+                _("Can only copy objects to like containers")
+                )
+
+        orig_name = obj.__name__
         if new_name is None:
             new_name = orig_name
 
-        target_path = getPath(target)
-        source_path = getPath(container)
-
-        copysource = getAdapter(container, ICopySource)
-        obj = copysource.copyObject(orig_name, target_path)
+        chooser = zapi.getAdapter(target, INameChooser)
+        new_name = chooser.chooseName(new_name, obj)
 
-        pastetarget = getAdapter(target, IPasteTarget)
-        # publish an ObjectCreatedEvent (perhaps...?)
-        new_name = pastetarget.pasteObject(new_name, obj)
-
-        # call afterAddHook
-        if queryAdapter(obj, ICopyNotifiable):
-            getAdapter(obj, ICopyNotifiable).afterAddHook(
-                obj, container, copiedFrom=source_path)
-        elif queryAdapter(obj, IAddNotifiable):
-            getAdapter(obj, IAddNotifiable).afterAddHook(obj, container)
-
-        # publish ObjectCopiedEvent
-        publish(container,
-                ObjectCopiedEvent(container, source_path, target_path))
+        copy = removeAllProxies(obj)
+        copy = locationCopy(copy)
+        copy.__parent__ = copy.__name__ = None
+        publish(target, ObjectCopiedEvent(copy))
 
-        return new_name
+        target[new_name] = copy
 
     def copyable(self):
         '''Returns True if the object is copyable, otherwise False.'''
@@ -157,63 +303,8 @@
         Returns True if it can be copied there. Otherwise, returns
         False.
         '''
-        obj = self.context
-        if name is None:
-            name = getName(obj)
-        pastetarget = getAdapter(target, IPasteTarget)
-        return pastetarget.acceptsObject(name, obj)
-
-
-class NoChildrenObjectCopier(ObjectCopier):
-
-    implements(INoChildrenObjectCopier)
-
-    def __init__(self, object):
-        self.context = object
+        return self.context.__parent__.__class__ == target.__class__ 
 
-    def copyTo(self, target, new_name=None):
-        """Copy this object but not its children to the target given.
-
-        Returns the new name within the target, or None
-        if the target doesn't do names.
-        Typically, the target is adapted to IPasteTarget.
-        After the copy is added to the target container, publish
-        an IObjectCopied event in the context of the target container.
-        If a new object is created as part of the copying process, then
-        an IObjectCreated event should be published.
-        """
-        obj = self.context
-        container = getParent(obj)
-        orig_name = getName(obj)
-        if new_name is None:
-            new_name = orig_name
-
-        target_path = getPath(target)
-        source_path = getPath(container)
-
-        copysource = getAdapter(container, INoChildrenCopySource)
-        obj = copysource.copyObjectWithoutChildren(orig_name, target_path)
-        if obj is None:
-            raise CopyException(container, orig_name,
-                                'Could not get a copy without children of %s'
-                                % orig_name) 
-
-        pastetarget = getAdapter(target, IPasteTarget)
-        # publish an ObjectCreatedEvent (perhaps...?)
-        new_name = pastetarget.pasteObject(new_name, obj)
-
-        # call afterAddHook
-        if queryAdapter(obj, ICopyNotifiable):
-            getAdapter(obj, ICopyNotifiable).afterAddHook(
-                obj, container, copiedFrom=source_path)
-        elif queryAdapter(obj, IAddNotifiable):
-            getAdapter(obj, IAddNotifiable).afterAddHook(obj, container)
-
-        # publish ObjectCopiedEvent
-        publish(container, ObjectCopiedEvent(
-            container, source_path, target_path))
-
-        return new_name
 
 class PrincipalClipboard:
     '''Principal clipboard
@@ -245,3 +336,15 @@
         '''Return the contents of the clipboard'''
         return removeAllProxies(self.context.get('clipboard', ()))
 
+
+def rename(container, oldid, newid):
+    object = container.get(oldid)
+    if object is None:
+        raise NotFoundError(container, oldid)
+    mover = zapi.getAdapter(object, IObjectMover)
+
+    if newid in container:
+        raise DuplicationError("name, %s, is already in use" % newid)
+
+    if mover.moveable() and mover.moveableTo(container, newid):
+        mover.moveTo(container, newid)




More information about the Zope3-Checkins mailing list