[Zope-CVS] SVN: zversioning/trunk/src/versioning/ Added patches for
Zope3
Uwe Oestermeier
uwe_oestermeier at iwm-kmrc.de
Tue Oct 12 14:15:30 EDT 2004
Log message for revision 28025:
Added patches for Zope3
Changed:
U zversioning/trunk/src/versioning/interfaces.py
A zversioning/trunk/src/versioning/patches/
A zversioning/trunk/src/versioning/patches/zope.app.copypastemove.__init__.py
A zversioning/trunk/src/versioning/patches/zope.app.copypastemove.tests.test_objectcopier.py
U zversioning/trunk/src/versioning/storage.py
U zversioning/trunk/src/versioning/tests/README.txt
-=-
Modified: zversioning/trunk/src/versioning/interfaces.py
===================================================================
--- zversioning/trunk/src/versioning/interfaces.py 2004-10-12 18:11:44 UTC (rev 28024)
+++ zversioning/trunk/src/versioning/interfaces.py 2004-10-12 18:15:29 UTC (rev 28025)
@@ -38,8 +38,10 @@
import persistent, zope
from zope.interface import Interface
+from zope.app.container.interfaces import INameChooser
+
class IRepository(Interface):
"""A version repository providing core functionality.
@@ -230,6 +232,11 @@
"""
+class IVersionHistory(INameChooser) :
+ """ A version history of a single object should be able to
+ generate unique names for each version within the version history.
+ """
+
# XXX
class IHistoryStorage(Interface) : # IHistories
Added: zversioning/trunk/src/versioning/patches/zope.app.copypastemove.__init__.py
===================================================================
--- zversioning/trunk/src/versioning/patches/zope.app.copypastemove.__init__.py 2004-10-12 18:11:44 UTC (rev 28024)
+++ zversioning/trunk/src/versioning/patches/zope.app.copypastemove.__init__.py 2004-10-12 18:15:29 UTC (rev 28025)
@@ -0,0 +1,471 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Copy, Paste and Move support for content components
+
+$Id: __init__.py 27314 2004-08-27 21:28:57Z jim $
+"""
+__docformat__ = 'restructuredtext'
+
+from zope.interface import implements, Invalid
+from zope.exceptions import NotFoundError, DuplicationError
+
+from zope.app import zapi
+from zope.app.container.sample import SampleContainer
+from zope.event import notify
+from zope.app.event.objectevent import ObjectCopiedEvent
+from zope.app.copypastemove.interfaces import IObjectMover
+from zope.app.copypastemove.interfaces import IObjectCopier
+from zope.app.location.pickling import locationCopy
+from zope.app.container.interfaces import INameChooser
+from zope.app.container.constraints import checkObject
+
+class ObjectMover(object):
+ """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.contained import Contained
+ >>> ob = Contained()
+ >>> container = ExampleContainer()
+ >>> 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 = ExampleContainer()
+ >>> mover.moveableTo(container2)
+ 1
+ >>> mover.moveableTo({})
+ Traceback (most recent call last):
+ ...
+ TypeError: Container is not a valid Zope container.
+
+ Of course, once we've decided we can move an object, we can use
+ the mover to do so:
+
+ >>> mover.moveTo(container2)
+ u'foo'
+ >>> list(container)
+ []
+ >>> list(container2)
+ [u'foo']
+ >>> ob.__parent__ is container2
+ 1
+
+ We can also specify a name:
+
+ >>> mover.moveTo(container2, u'bar')
+ 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')
+ 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: Container is not a valid Zope container.
+
+
+ Do a test for preconditions:
+
+ >>> import zope.interface
+ >>> import zope.schema
+ >>> def preNoZ(container, name, ob):
+ ... "Silly precondition example"
+ ... if name.startswith("Z"):
+ ... raise zope.interface.Invalid("Invalid name.")
+
+ >>> class I1(zope.interface.Interface):
+ ... def __setitem__(name, on):
+ ... "Add an item"
+ ... __setitem__.precondition = preNoZ
+
+ >>> from zope.app.container.interfaces import IContainer
+ >>> class C1(object):
+ ... zope.interface.implements(I1, IContainer)
+ ... def __repr__(self):
+ ... return 'C1'
+
+ >>> from zope.app.container.constraints import checkObject
+ >>> container3 = C1()
+ >>> mover.moveableTo(container3, 'ZDummy')
+ 0
+ >>> mover.moveableTo(container3, 'newName')
+ 1
+
+ And a test for constraints:
+
+ >>> def con1(container):
+ ... "silly container constraint"
+ ... if not hasattr(container, 'x'):
+ ... return False
+ ... return True
+ ...
+ >>> class I2(zope.interface.Interface):
+ ... __parent__ = zope.schema.Field(constraint = con1)
+ ...
+ >>> class constrainedObject(object):
+ ... zope.interface.implements(I2)
+ ... def __init__(self):
+ ... self.__name__ = 'constrainedObject'
+ ...
+ >>> cO = constrainedObject()
+ >>> mover2 = ObjectMover(cO)
+ >>> mover2.moveableTo(container)
+ 0
+ >>> container.x = 1
+ >>> mover2.moveableTo(container)
+ 1
+
+ """
+
+ implements(IObjectMover)
+
+ def __init__(self, object):
+ self.context = object
+ self.__parent__ = object # TODO: see if we can automate this
+
+ def moveTo(self, target, new_name=None):
+ '''Move this object to the `target` given.
+
+ Returns the new name within the `target`
+ Typically, the `target` is adapted to `IPasteTarget`.'''
+
+ obj = self.context
+ container = obj.__parent__
+
+ orig_name = obj.__name__
+ if new_name is None:
+ new_name = orig_name
+
+ checkObject(target, new_name, obj)
+
+ if target is container and new_name == orig_name:
+ # Nothing to do
+ return
+
+ chooser = INameChooser(target)
+ new_name = chooser.chooseName(new_name, obj)
+
+ target[new_name] = obj
+ del container[orig_name]
+ return new_name
+
+ def moveable(self):
+ '''Returns ``True`` if the object is moveable, otherwise ``False``.'''
+ return True
+
+ def moveableTo(self, target, name=None):
+ '''Say whether the object can be moved to the given target.
+
+ Returns ``True`` if it can be moved there. Otherwise, returns
+ ``False``.
+ '''
+ if name is None:
+ name = self.context.__name__
+ try:
+ checkObject(target, name, self.context)
+ except Invalid:
+ return False
+ return True
+
+class ObjectCopier(object):
+ """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.contained import Contained
+ >>> ob = Contained()
+ >>> container = ExampleContainer()
+ >>> 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 = ExampleContainer()
+ >>> copier.copyableTo(container2)
+ 1
+ >>> copier.copyableTo({})
+ Traceback (most recent call last):
+ ...
+ TypeError: Container is not a valid Zope container.
+
+ Of course, once we've decided we can copy an object, we can use
+ the copier to do so:
+
+ >>> copier.copyTo(container2)
+ u'foo'
+ >>> 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')
+ 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')
+ 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: Container is not a valid Zope container.
+
+ Do a test for preconditions:
+
+ >>> import zope.interface
+ >>> import zope.schema
+ >>> def preNoZ(container, name, ob):
+ ... "Silly precondition example"
+ ... if name.startswith("Z"):
+ ... raise zope.interface.Invalid("Invalid name.")
+
+ >>> class I1(zope.interface.Interface):
+ ... def __setitem__(name, on):
+ ... "Add an item"
+ ... __setitem__.precondition = preNoZ
+
+ >>> from zope.app.container.interfaces import IContainer
+ >>> class C1(object):
+ ... zope.interface.implements(I1, IContainer)
+ ... def __repr__(self):
+ ... return 'C1'
+
+ >>> from zope.app.container.constraints import checkObject
+ >>> container3 = C1()
+ >>> copier.copyableTo(container3, 'ZDummy')
+ 0
+ >>> copier.copyableTo(container3, 'newName')
+ 1
+
+ And a test for constraints:
+
+ >>> def con1(container):
+ ... "silly container constraint"
+ ... if not hasattr(container, 'x'):
+ ... return False
+ ... return True
+ ...
+ >>> class I2(zope.interface.Interface):
+ ... __parent__ = zope.schema.Field(constraint = con1)
+ ...
+ >>> class constrainedObject(object):
+ ... zope.interface.implements(I2)
+ ... def __init__(self):
+ ... self.__name__ = 'constrainedObject'
+ ...
+ >>> cO = constrainedObject()
+ >>> copier2 = ObjectCopier(cO)
+ >>> copier2.copyableTo(container)
+ 0
+ >>> container.x = 1
+ >>> copier2.copyableTo(container)
+ 1
+
+ """
+
+ implements(IObjectCopier)
+
+ def __init__(self, object):
+ self.context = object
+ self.__parent__ = object # TODO: see if we can automate this
+
+ def copyTo(self, target, new_name=None):
+ """Copy this object to the `target` given.
+
+ Returns the new name within the `target`.
+
+ 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 = obj.__parent__
+
+ orig_name = obj.__name__
+ if new_name is None:
+ new_name = orig_name
+
+ checkObject(target, new_name, obj)
+
+ chooser = INameChooser(target)
+ new_name = chooser.chooseName(new_name, obj)
+
+ copy = locationCopy(obj)
+ self._configureCopy(copy, target, new_name)
+ notify(ObjectCopiedEvent(copy))
+
+ target[new_name] = copy
+ return new_name
+
+ def _configureCopy(self, copy, target, new_name):
+ """Configures the copied object before it is added to `target`.
+
+ `target` and `new_name` are provided as additional information.
+
+ By default, `copy.__parent__` and `copy.__name__` are set to ``None``.
+
+ Subclasses may override this method to perform additional
+ configuration of the copied object.
+ """
+ copy.__parent__ = copy.__name__ = None
+
+ def copyable(self):
+ '''Returns True if the object is copyable, otherwise False.'''
+ return True
+
+ def copyableTo(self, target, name=None):
+ '''Say whether the object can be copied to the given `target`.
+
+ Returns ``True`` if it can be copied there. Otherwise, returns
+ ``False``.
+ '''
+ if name is None:
+ name = self.context.__name__
+ try:
+ checkObject(target, name, self.context)
+ except Invalid:
+ return False
+ return True
+
+
+class PrincipalClipboard(object):
+ '''Principal clipboard
+
+ Clipboard information consists on tuples of
+ ``{'action':action, 'target':target}``.
+ '''
+
+ def __init__(self, annotation):
+ self.context = annotation
+
+ def clearContents(self):
+ '''Clear the contents of the clipboard'''
+ self.context['clipboard'] = ()
+
+ def addItems(self, action, targets):
+ '''Add new items to the clipboard'''
+ contents = self.getContents()
+ actions = []
+ for target in targets:
+ actions.append({'action':action, 'target':target})
+ self.context['clipboard'] = contents + tuple(actions)
+
+ def setContents(self, clipboard):
+ '''Replace the contents of the clipboard by the given value'''
+ self.context['clipboard'] = clipboard
+
+ def getContents(self):
+ '''Return the contents of the clipboard'''
+ return self.context.get('clipboard', ())
+
+
+def rename(container, oldid, newid):
+ object = container.get(oldid)
+ if object is None:
+ raise NotFoundError(container, oldid)
+ mover = IObjectMover(object)
+
+ 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)
+
+class ExampleContainer(SampleContainer):
+ # Sample container used for examples in doc stringss in this module
+
+ implements(INameChooser)
+
+ def chooseName(self, name, ob):
+ while name in self:
+ name += '_'
+ return name
Added: zversioning/trunk/src/versioning/patches/zope.app.copypastemove.tests.test_objectcopier.py
===================================================================
--- zversioning/trunk/src/versioning/patches/zope.app.copypastemove.tests.test_objectcopier.py 2004-10-12 18:11:44 UTC (rev 28024)
+++ zversioning/trunk/src/versioning/patches/zope.app.copypastemove.tests.test_objectcopier.py 2004-10-12 18:15:29 UTC (rev 28025)
@@ -0,0 +1,157 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Object Copier Tests
+
+$Id: test_objectcopier.py 26551 2004-07-15 07:06:37Z srichter $
+"""
+from unittest import TestCase, TestSuite, main, makeSuite
+from zope.app.traversing.api import traverse
+from zope.app.site.tests.placefulsetup import PlacefulSetup
+from zope.app.tests import ztapi
+from zope.app.copypastemove.interfaces import IObjectCopier
+from zope.app.copypastemove import ObjectCopier
+
+class File(object):
+ pass
+
+class ObjectCopierTest(PlacefulSetup, TestCase):
+
+ def setUp(self):
+ PlacefulSetup.setUp(self)
+ PlacefulSetup.buildFolders(self)
+ ztapi.provideAdapter(None, IObjectCopier, ObjectCopier)
+
+ def test_copytosame(self):
+ root = self.rootFolder
+ container = traverse(root, 'folder1')
+ container['file1'] = File()
+ file = traverse(root, 'folder1/file1')
+ copier = IObjectCopier(file)
+ copier.copyTo(container, 'file1')
+ self.failUnless('file1' in container)
+ self.failUnless('file1-2' in container)
+
+ def test_copytosamewithnewname(self):
+ root = self.rootFolder
+ container = traverse(root, 'folder1')
+ container['file1'] = File()
+ file = traverse(root, 'folder1/file1')
+ copier = IObjectCopier(file)
+ copier.copyTo(container, 'file2')
+ self.failUnless('file1' in container)
+ self.failUnless('file2' in container)
+
+ def test_copytoother(self):
+ root = self.rootFolder
+ container = traverse(root, 'folder1')
+ container['file1'] = File()
+ target = traverse(root, 'folder2')
+ file = traverse(root, 'folder1/file1')
+ copier = IObjectCopier(file)
+ copier.copyTo(target, 'file1')
+ self.failUnless('file1' in container)
+ self.failUnless('file1' in target)
+
+ def test_copytootherwithnewname(self):
+ root = self.rootFolder
+ container = traverse(root, 'folder1')
+ container['file1'] = File()
+ target = traverse(root, 'folder2')
+ file = traverse(root, 'folder1/file1')
+ copier = IObjectCopier(file)
+ copier.copyTo(target, 'file2')
+ self.failUnless('file1' in container)
+ self.failUnless('file2' in target)
+
+ def test_copytootherwithnamecollision(self):
+ root = self.rootFolder
+ container = traverse(root, 'folder1')
+ container['file1'] = File()
+ target = traverse(root, 'folder2')
+ target['file1'] = File()
+ file = traverse(root, 'folder1/file1')
+ copier = IObjectCopier(file)
+ copier.copyTo(target, 'file1')
+ # we do it twice, just to test auto-name generation
+ copier.copyTo(target, 'file1')
+ self.failUnless('file1' in container)
+ self.failUnless('file1' in target)
+ self.failUnless('file1-2' in target)
+ self.failUnless('file1-3' in target)
+
+ def test_copyable(self):
+ root = self.rootFolder
+ container = traverse(root, 'folder1')
+ container['file1'] = File()
+ file = traverse(root, 'folder1/file1')
+ copier = IObjectCopier(file)
+ self.failUnless(copier.copyable())
+
+ def test_copyableTo(self):
+ # A file should be copyable to a folder that has an
+ # object with the same id.
+ root = self.rootFolder
+ container = traverse(root, 'folder1')
+ container['file1'] = File()
+ file = traverse(root, 'folder1/file1')
+ copier = IObjectCopier(file)
+ self.failUnless(copier.copyableTo(container, 'file1'))
+
+ def test_copyfoldertosibling(self):
+ root = self.rootFolder
+ target = traverse(root, '/folder2')
+ source = traverse(root, '/folder1/folder1_1')
+ copier = IObjectCopier(source)
+ copier.copyTo(target)
+ self.failUnless('folder1_1' in target)
+
+ def test_copyfoldertosame(self):
+ root = self.rootFolder
+ target = traverse(root, '/folder1')
+ source = traverse(root, '/folder1/folder1_1')
+ copier = IObjectCopier(source)
+ copier.copyTo(target)
+ self.failUnless('folder1_1' in target)
+
+ def test_copyfoldertosame2(self):
+ root = self.rootFolder
+ target = traverse(root, '/folder1/folder1_1')
+ source = traverse(root, '/folder1/folder1_1/folder1_1_1')
+ copier = IObjectCopier(source)
+ copier.copyTo(target)
+ self.failUnless('folder1_1_1' in target)
+
+ def test_copyfolderfromroot(self):
+ root = self.rootFolder
+ target = traverse(root, '/folder2')
+ source = traverse(root, '/folder1')
+ copier = IObjectCopier(source)
+ copier.copyTo(target)
+ self.failUnless('folder1' in target)
+
+ def test_copyfolderfromroot2(self):
+ root = self.rootFolder
+ target = traverse(root, '/folder2/folder2_1/folder2_1_1')
+ source = traverse(root, '/folder1')
+ copier = IObjectCopier(source)
+ copier.copyTo(target)
+ self.failUnless('folder1' in target)
+
+def test_suite():
+ return TestSuite((
+ makeSuite(ObjectCopierTest),
+ ))
+
+if __name__=='__main__':
+ main(defaultTest='test_suite')
Modified: zversioning/trunk/src/versioning/storage.py
===================================================================
--- zversioning/trunk/src/versioning/storage.py 2004-10-12 18:11:44 UTC (rev 28024)
+++ zversioning/trunk/src/versioning/storage.py 2004-10-12 18:15:29 UTC (rev 28025)
@@ -17,14 +17,20 @@
from zope.interface import implements
from zope.app.copypastemove.interfaces import IObjectCopier
from zope.app.folder import Folder
-from zope.app.container.interfaces import INameChooser
from zope.app.exception.interfaces import UserError
+from interfaces import IVersionHistory
+from interfaces import IHistoryStorage
-class SingleHistory(Folder) :
- implements(INameChooser)
+
+class VersionHistory(Folder) :
+ """ A simple folder implementation where each version
+ is labeled '001', '002' etc.
+ """
+ implements(IVersionHistory)
+
def checkName(self, name, object):
"""Check whether an object name is valid.
@@ -40,13 +46,11 @@
choosing the name.
"""
- return "Version %03d" % (len(self)+1)
-
+ return "%03d" % (len(self)+1)
+
+
-
-
-
class SimpleHistoryStorage(Folder) :
"""
Implements the probably most simple way of version control in Zope3.
@@ -62,15 +66,20 @@
>>> sample.keys()
"""
+
+ implements(IHistoryStorage)
def getTicket(self, obj) :
+ """ Returns the persistent oid of an object as
+ a ticket that remains stable across time.
+ """
return str(obj._p_oid)
def register(self, obj):
""" Register an obj for version control.
Creates a new version history for a resource."""
- history = SingleHistory()
+ history = VersionHistory() # XXX call IVersionHistory(self) instead?
ticket = self.getTicket(obj)
self[ticket] = history
return ticket
Modified: zversioning/trunk/src/versioning/tests/README.txt
===================================================================
--- zversioning/trunk/src/versioning/tests/README.txt 2004-10-12 18:11:44 UTC (rev 28024)
+++ zversioning/trunk/src/versioning/tests/README.txt 2004-10-12 18:15:29 UTC (rev 28025)
@@ -135,7 +135,11 @@
[u'\x00\x00\x00\x00\x00\x00\x00\x04', u'\x00\x00\x00\x00\x00\x00\x00\x05']
>>> adapter = VersionableAspectsAdapter(a, histories)
>>> adapter.writeAspects()
- 'Version 001'
+ '001'
+
+
+
+
More information about the Zope-CVS
mailing list