[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