Plone Undelete

by Alec Mitchell last modified Feb 16, 2011 02:08 AM

A CMFEditions add-on product for enabling undeletion of objects from folders.

Project Description

Plone undelete is an add-on Product for CMFEditions which adds support for restoring content that has been deleted from a container. Containers that wish to provide this functionality must provide a marker interface, IUndeleteContainer, and must also be adaptable to IAnnotatable. Let's make a simple container that provides these interfaces:

>>> from Products.ploneundelete.tests import FakeContainer
>>> from Products.ploneundelete.interfaces import IUndeleteContainer
>>> from zope.app.annotation.interfaces import IAttributeAnnotatable
>>> from zope.interface import directlyProvides
>>> folder = FakeContainer()
>>> directlyProvides(folder, IUndeleteContainer, IAttributeAnnotatable)

Providing this interface allows us to adapt our object to the IUndeleteSupport interface which provides some utility methods to help with the undeleletion process. First we need to add a sub object with some properties, which we will be deleting and undeleting:

>>> from Products.ploneundelete.tests import VersionedObject
>>> folder['new_obj'] = VersionedObject('new_obj')
>>> child = folder['new_obj']
>>> child.title = 'Title'
>>> child.extra = 'extra stuff'

Then we apply our undelete support interface and see how it works. We start with the method saveObjectInfo, which is used before objects are deleted to save metadata about the object, as well as make a permanent snapshot of the object for undeletion:

>>> from Products.ploneundelete.interfaces import IUndeleteSupport
>>> undelete_folder = IUndeleteSupport(folder)
>>> undelete_folder.saveObjectInfo(child)
>>> deleted = undelete_folder.listDeleted()
>>> len(deleted) == 1
True
>>> deleted[0]['id']
'new_obj'
>>> deleted[0]['title']
'Title'
>>> deleted[0]['revision']
0

Saving the info again will not update the revision:

>>> undelete_folder.saveObjectInfo(child)
>>> deleted = undelete_folder.listDeleted()
>>> len(deleted) == 1
True
>>> deleted[0]['revision']
0

Changing the object will cause a new revision to be saved:

>>> child.title = 'Updated Title'
>>> undelete_folder.saveObjectInfo(child)
>>> deleted = undelete_folder.listDeleted()
>>> len(deleted) == 1
True
>>> deleted[0]['revision']
1
>>> deleted[0]['title']
'Updated Title'
>>> uid = deleted[0]['uid']

When we delete the object, we should now be able to restore it:

>>> del folder['new_obj']
>>> folder.get('new_obj', 'No Object')
'No Object'
>>> undelete_folder.undelete(uid)
>>> child = folder['new_obj']
>>> child.title
'Updated Title'
>>> deleted = undelete_folder.listDeleted()
>>> len(deleted) == 0
True

This is all a bit manual so far, which also makes it a bit useless. The magic comes in because of an event subscriber that's registered to look for IObjectWillBeRemovedEvent events on ICanBeUndeleted objects, and automatically perform saveObjectInfo on them. So any ICanBeUndeleted object which has been deleted from an IUndeleteContainer will automatically be undeletable:

>>> from Products.ploneundelete.interfaces import ICanBeUndeleted
>>> folder['new_obj2'] = VersionedObject('new_obj2')
>>> child2 = folder['new_obj2']
>>> directlyProvides(child2, ICanBeUndeleted)
>>> child2.title = 'My Title for 2'
>>> child2.extra = 'My Extra for 2'
>>> del folder['new_obj2']
>>> folder.get('new_obj2', 'No Object')
'No Object'
>>> deleted = undelete_folder.listDeleted()
>>> len(deleted) == 1
True
>>> undelete_folder.undelete(deleted[0]['uid'])
>>> child2 = folder['new_obj2']
>>> child2.title
'My Title for 2'
>>> child2.extra
'My Extra for 2'
>>> deleted = undelete_folder.listDeleted()
>>> len(deleted) == 0
True

That's about it.

Notes

Currently ploneundelete requires a specific branch of CMFEditions from svn: http://svn.plone.org/svn/collective/CMFEditions/branches/undelete-support

At present turning on <five:containerEvents />, which is needed for IObjectWillBeRemovedEvent to be fired, in Zope 2.8 will break a number of critical AT and CMF functions. So objects that want to be undeletable need to call saveObjectInfo explicitly in their manage_beforeDelete methods. :-(

Credits

Current Release

No stable release available yet.

If you are interested in getting the source code of this project, you can get it from the Code repository .

All Releases

Version Released Description Compatibility Licenses Status

Comments (0)