more clean-up of code; invalidating of relations as a consequence of object removal is now fully event-based

git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@660 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2005-11-06 11:11:05 +00:00
parent e5fefb456d
commit cb9dc6219f
6 changed files with 153 additions and 55 deletions

View file

@ -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

View file

@ -36,10 +36,22 @@
trusted="true"
/>
<subscriber
for=".interfaces.IRelationsRegistry
zope.app.container.interfaces.IObjectAddedEvent"
handler=".registry.setupIndexes"
/>
<subscriber
for="zope.interface.Interface
zope.app.container.interfaces.IObjectRemovedEvent"
handler=".registry.unregisterRelations"
handler=".registry.invalidateRelations"
/>
<subscriber
for="cybertools.relation.interfaces.IRelation
cybertools.relation.interfaces.IRelationInvalidatedEvent"
handler=".registry.removeRelation"
/>
<!-- browser settings -->

View file

@ -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,))

View file

@ -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.')

View file

@ -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()

View file

@ -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."