diff --git a/relation/README.txt b/relation/README.txt
index d60c7bd..0a9d48f 100644
--- a/relation/README.txt
+++ b/relation/README.txt
@@ -117,8 +117,17 @@ working with events).
>>> from cybertools.relation.registry import RelationsRegistry
>>> from cybertools.relation.interfaces import IRelationsRegistry
>>> from zope.app.testing import ztapi
- >>> ztapi.provideUtility(IRelationsRegistry, RelationsRegistry())
+ >>> ztapi.provideUtility(IRelationsRegistry, RelationsRegistry())
+ >>> from zope.app import zapi
+ >>> relations = zapi.getUtility(IRelationsRegistry)
+
+In real life the indexes needed will be set up manually after object creation
+or via subscription to IObjectCreatedEvent - here we have to do this
+explicitly:
+
+ >>> relations.setupIndexes()
+
In order to register relations the objects that are referenced have to be
registered with an IntIds (unique ids) utility, so we have to set up such
an utility (using a stub/dummy implementation for testing purposes):
@@ -139,9 +148,6 @@ the attributes needed for indexing:
So we are ready again to register a set of relations with our new relations
registry and query it.
- >>> from zope.app import zapi
- >>> relations = zapi.getUtility(IRelationsRegistry)
-
>>> relations.register(LivesIn(clark, washington))
>>> relations.register(LivesIn(audrey, newyork))
>>> relations.register(LivesIn(kirk, newyork))
@@ -189,28 +195,51 @@ It should work also for triadic relations:
Handling object removal
~~~~~~~~~~~~~~~~~~~~~~~
-Often it is desirable to unregister a when one of the objects
-involved in the relation is removed from its container. This can be
+Often it is desirable to unregister a relation when one of the objects
+involved in it is removed from its container. This can be
done by subscribing to IObjectRemovedEvent. The relation.registry module
-provides a simple handler for this event.
+provides a simple handler for this event. (In real life all this is
+done via configure.zcml - see relation/configure.zcml for an example that
+also provides the default behaviour.)
>>> from zope.app.container.interfaces import IObjectRemovedEvent
- >>> from zope.interface import Interface
- >>> from cybertools.relation.registry import unregisterRelations
- >>> ztapi.subscribe([Interface, IObjectRemovedEvent], None, unregisterRelations)
-
-We simulate the removal of kirk by calling notify:
-
>>> from zope.app.container.contained import ObjectRemovedEvent
>>> from zope.event import notify
+ >>> from zope.interface import Interface
+ >>> from cybertools.relation.registry import invalidateRelations
+ >>> ztapi.subscribe([Interface, IObjectRemovedEvent], None,
+ ... invalidateRelations)
+
+The invalidateRelations handler will query for all relations the object to be
+removed is involved in and then fire for these relations a
+IRelationInvalidatedEvent. This then does the real work.
+
+So we also have to subscribe to this event. We use a standard handler that
+removes the relation from whereever it is known that is provided in
+the registry module. (There might be other handlers that raise an
+exception thus preventing the removal of objects that take part in certain
+relations.)
+
+ >>> from cybertools.relation.interfaces import IRelation
+ >>> from cybertools.relation.interfaces import IRelationInvalidatedEvent
+ >>> from cybertools.relation.registry import removeRelation
+
+ >>> ztapi.subscribe([IRelation, IRelationInvalidatedEvent], None,
+ ... removeRelation)
+
+Let's first check if everything is still as before:
+
>>> len(relations.query(first=clark))
2
+We simulate the removal of kirk by calling notify, so clark hasn't got
+a son any longer :-(
+
>>> notify(ObjectRemovedEvent(kirk))
Thus there should only remain one relation containing clark as first:
>>> len(relations.query(first=clark))
1
-
\ No newline at end of file
+
diff --git a/relation/configure.zcml b/relation/configure.zcml
index 700b7ed..fb2d37c 100644
--- a/relation/configure.zcml
+++ b/relation/configure.zcml
@@ -36,10 +36,22 @@
trusted="true"
/>
+
+
+
+
diff --git a/relation/ftests.py b/relation/ftests.py
index 0159195..be55db1 100755
--- a/relation/ftests.py
+++ b/relation/ftests.py
@@ -2,12 +2,28 @@
import unittest, doctest
from zope.app.testing.functional import FunctionalTestCase
+from zope.app.testing import setup
from zope.testbrowser import Browser
+from zope.app import component, intid, zapi
+
class BrowserTest(FunctionalTestCase):
"Functional tests for the relation package."
- def test(self):
+ def setUp(self):
+ super(BrowserTest, self).setUp()
+ root = self.getRootFolder()
+ sitemanager = zapi.getSiteManager(root)
+ #defaultSite = component.site.LocalSiteManager(root)['default']
+ default = sitemanager['default']
+ intids = intid.IntIds()
+ default['intids'] = intids
+ reg = component.site.UtilityRegistration(u'',
+ intid.interfaces.IIntIds, default['intids'])
+ key = default.registrationManager.addRegistration(reg)
+ default.registrationManager[key].status = component.interfaces.registration.ActiveStatus
+
+ def test(self):
browser = Browser()
browser.handleErrors = False
browser.addHeader('Authorization', 'Basic mgr:mgrpw')
@@ -26,7 +42,7 @@ class BrowserTest(FunctionalTestCase):
def test_suite():
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
- #browser = FunctionalDocFileSuite('skin/cyberview.txt', optionflags=flags)
+ #browser = FunctionalDocFileSuite('funky.txt', optionflags=flags)
browser = unittest.makeSuite(BrowserTest)
return unittest.TestSuite((browser,))
diff --git a/relation/interfaces.py b/relation/interfaces.py
index 22d5973..0b9090b 100644
--- a/relation/interfaces.py
+++ b/relation/interfaces.py
@@ -23,8 +23,41 @@ $Id$
"""
from zope.interface import Interface, Attribute
+from zope.app.event.interfaces import IObjectEvent
+# relation interfaces
+
+class IRelation(Interface):
+ """ Base interface for relations.
+ """
+
+
+class IDyadicRelation(IRelation):
+ """ Relation connecting two objects.
+ """
+
+ first = Attribute('First object that belongs to the relation.')
+ second = Attribute('Second object that belongs to the relation.')
+
+
+class ITriadicRelation(IDyadicRelation):
+ """ Relation connecting three objects.
+ """
+
+ third = Attribute('Third object that belongs to the relation.')
+
+
+# event interfaces
+
+class IRelationInvalidatedEvent(IObjectEvent):
+ """ This event fires when a relation is invalidated, typically because
+ an object that is involved in the relation is removed.
+ """
+
+
+# relations registry interfaces
+
class IRelationsRegistryUpdate(Interface):
""" Interface for registering and unregistering relations with a
relations registry.
@@ -56,22 +89,3 @@ class IRelationsRegistry(IRelationsRegistryUpdate, IRelationsRegistryQuery):
"""
-class IRelation(Interface):
- """ Base interface for relations.
- """
-
-
-class IDyadicRelation(IRelation):
- """ Relation connecting two objects.
- """
-
- first = Attribute('First object that belongs to the relation.')
- second = Attribute('Second object that belongs to the relation.')
-
-
-class ITriadicRelation(IDyadicRelation):
- """ Relation connecting three objects.
- """
-
- third = Attribute('Third object that belongs to the relation.')
-
diff --git a/relation/registry.py b/relation/registry.py
index 1a19f6a..df70dbe 100644
--- a/relation/registry.py
+++ b/relation/registry.py
@@ -28,8 +28,12 @@ from zope.app import zapi
from zope.app.catalog.catalog import Catalog
from zope.app.catalog.field import FieldIndex
from zope.app.intid.interfaces import IIntIds
+from zope.app.location.interfaces import ILocation
+from zope.event import notify
+from zope.app.event.objectevent import ObjectEvent
+from zope.security.proxy import removeSecurityProxy
-from interfaces import IRelationsRegistry
+from interfaces import IRelationsRegistry, IRelationInvalidatedEvent
class DummyRelationsRegistry(object):
@@ -70,18 +74,13 @@ class RelationsRegistry(Catalog):
implements(IRelationsRegistry)
- indexesSetUp = False
-
def setupIndexes(self):
- self['relationship'] = FieldIndex('relationship', IIndexableRelation)
- self['first'] = FieldIndex('first', IIndexableRelation)
- self['second'] = FieldIndex('second', IIndexableRelation)
- self['third'] = FieldIndex('third', IIndexableRelation)
- self.indexesSetUp = True
+ for idx in ('relationship', 'first', 'second', 'third'):
+ if idx not in self:
+ self[idx] = FieldIndex(idx, IIndexableRelation)
def register(self, relation):
- if not self.indexesSetUp:
- self.setupIndexes()
+ #self.setupIndexes()
self.index_doc(_getUid(relation), relation)
def unregister(self, relation):
@@ -133,24 +132,48 @@ def _getUid(ob):
return zapi.getUtility(IIntIds).getId(ob)
def _getRelationship(relation):
- return _getClassString(relation.__class__)
+ return _getClassString(removeSecurityProxy(relation).__class__)
def _getClassString(cls):
- return cls.__module__ + '.' + cls.__name__
+ return '%s.%s' % (cls.__module__, cls.__name__)
-# event handler
+# events and handlers
-def unregisterRelations(context, event):
- """ Handles IObjectRemoved event: unregisters all relations for the
- object that has been removed.
+class RelationInvalidatedEvent(ObjectEvent):
+ implements(IRelationInvalidatedEvent)
+
+
+def invalidateRelations(context, event):
+ """ Handles IObjectRemoved event: sends out an IRelationInvalidatedEvent
+ for all relations the object to be removed is involved in.
"""
relations = []
registry = zapi.getUtility(IRelationsRegistry)
for attr in ('first', 'second', 'third'):
relations = registry.query(**{attr: context})
for relation in relations:
- registry.unregister(relation)
- # to do: unregister relation also from the IntId utility
- # (if appropriate).
+ notify(RelationInvalidatedEvent(relation))
+
+def removeRelation(context, event):
+ """ Handles IRelationInvalidatedEvent by unregistering the relation
+ and removing it from its container (if appropriate) and the IntIds
+ utility.
+ """
+ registry = zapi.getUtility(IRelationsRegistry)
+ registry.unregister(context)
+ if ILocation.providedBy(context):
+ parent = zapi.getParent(context)
+ if parent is not None:
+ del parent[context]
+ intids = zapi.getUtility(IIntIds)
+ intids.unregister(context)
+
+def setupIndexes(context, event):
+ """ Handles IObjectCreated event for the RelationsRegistry utility
+ and creates the indexes needed.
+ """
+ if isinstance(context, RelationsRegistry):
+ context.setupIndexes()
+
diff --git a/relation/tests.py b/relation/tests.py
index e0a46c0..1aa7c8c 100755
--- a/relation/tests.py
+++ b/relation/tests.py
@@ -29,6 +29,10 @@ class IntIdsStub:
self.objs.append(ob)
return self.objs.index(ob)
+ def unregister(self, ob):
+ id = self.getId(ob)
+ self.objs[id] = None
+
class TestRelation(unittest.TestCase):
"Basic tests for the relation package."