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:
parent
e5fefb456d
commit
cb9dc6219f
6 changed files with 153 additions and 55 deletions
|
@ -119,6 +119,15 @@ working with events).
|
||||||
>>> from zope.app.testing import ztapi
|
>>> 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
|
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
|
registered with an IntIds (unique ids) utility, so we have to set up such
|
||||||
an utility (using a stub/dummy implementation for testing purposes):
|
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
|
So we are ready again to register a set of relations with our new relations
|
||||||
registry and query it.
|
registry and query it.
|
||||||
|
|
||||||
>>> from zope.app import zapi
|
|
||||||
>>> relations = zapi.getUtility(IRelationsRegistry)
|
|
||||||
|
|
||||||
>>> relations.register(LivesIn(clark, washington))
|
>>> relations.register(LivesIn(clark, washington))
|
||||||
>>> relations.register(LivesIn(audrey, newyork))
|
>>> relations.register(LivesIn(audrey, newyork))
|
||||||
>>> relations.register(LivesIn(kirk, newyork))
|
>>> relations.register(LivesIn(kirk, newyork))
|
||||||
|
@ -189,24 +195,47 @@ It should work also for triadic relations:
|
||||||
Handling object removal
|
Handling object removal
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Often it is desirable to unregister a when one of the objects
|
Often it is desirable to unregister a relation when one of the objects
|
||||||
involved in the relation is removed from its container. This can be
|
involved in it is removed from its container. This can be
|
||||||
done by subscribing to IObjectRemovedEvent. The relation.registry module
|
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.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.app.container.contained import ObjectRemovedEvent
|
||||||
>>> from zope.event import notify
|
>>> 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))
|
>>> len(relations.query(first=clark))
|
||||||
2
|
2
|
||||||
|
|
||||||
|
We simulate the removal of kirk by calling notify, so clark hasn't got
|
||||||
|
a son any longer :-(
|
||||||
|
|
||||||
>>> notify(ObjectRemovedEvent(kirk))
|
>>> notify(ObjectRemovedEvent(kirk))
|
||||||
|
|
||||||
Thus there should only remain one relation containing clark as first:
|
Thus there should only remain one relation containing clark as first:
|
||||||
|
|
|
@ -36,10 +36,22 @@
|
||||||
trusted="true"
|
trusted="true"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<subscriber
|
||||||
|
for=".interfaces.IRelationsRegistry
|
||||||
|
zope.app.container.interfaces.IObjectAddedEvent"
|
||||||
|
handler=".registry.setupIndexes"
|
||||||
|
/>
|
||||||
|
|
||||||
<subscriber
|
<subscriber
|
||||||
for="zope.interface.Interface
|
for="zope.interface.Interface
|
||||||
zope.app.container.interfaces.IObjectRemovedEvent"
|
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 -->
|
<!-- browser settings -->
|
||||||
|
|
|
@ -2,11 +2,27 @@
|
||||||
|
|
||||||
import unittest, doctest
|
import unittest, doctest
|
||||||
from zope.app.testing.functional import FunctionalTestCase
|
from zope.app.testing.functional import FunctionalTestCase
|
||||||
|
from zope.app.testing import setup
|
||||||
from zope.testbrowser import Browser
|
from zope.testbrowser import Browser
|
||||||
|
|
||||||
|
from zope.app import component, intid, zapi
|
||||||
|
|
||||||
class BrowserTest(FunctionalTestCase):
|
class BrowserTest(FunctionalTestCase):
|
||||||
"Functional tests for the relation package."
|
"Functional tests for the relation package."
|
||||||
|
|
||||||
|
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):
|
def test(self):
|
||||||
browser = Browser()
|
browser = Browser()
|
||||||
browser.handleErrors = False
|
browser.handleErrors = False
|
||||||
|
@ -26,7 +42,7 @@ class BrowserTest(FunctionalTestCase):
|
||||||
|
|
||||||
def test_suite():
|
def test_suite():
|
||||||
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
|
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
|
||||||
#browser = FunctionalDocFileSuite('skin/cyberview.txt', optionflags=flags)
|
#browser = FunctionalDocFileSuite('funky.txt', optionflags=flags)
|
||||||
browser = unittest.makeSuite(BrowserTest)
|
browser = unittest.makeSuite(BrowserTest)
|
||||||
return unittest.TestSuite((browser,))
|
return unittest.TestSuite((browser,))
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,41 @@ $Id$
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.interface import Interface, Attribute
|
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):
|
class IRelationsRegistryUpdate(Interface):
|
||||||
""" Interface for registering and unregistering relations with a
|
""" Interface for registering and unregistering relations with a
|
||||||
relations registry.
|
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.')
|
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,12 @@ from zope.app import zapi
|
||||||
from zope.app.catalog.catalog import Catalog
|
from zope.app.catalog.catalog import Catalog
|
||||||
from zope.app.catalog.field import FieldIndex
|
from zope.app.catalog.field import FieldIndex
|
||||||
from zope.app.intid.interfaces import IIntIds
|
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):
|
class DummyRelationsRegistry(object):
|
||||||
|
@ -70,18 +74,13 @@ class RelationsRegistry(Catalog):
|
||||||
|
|
||||||
implements(IRelationsRegistry)
|
implements(IRelationsRegistry)
|
||||||
|
|
||||||
indexesSetUp = False
|
|
||||||
|
|
||||||
def setupIndexes(self):
|
def setupIndexes(self):
|
||||||
self['relationship'] = FieldIndex('relationship', IIndexableRelation)
|
for idx in ('relationship', 'first', 'second', 'third'):
|
||||||
self['first'] = FieldIndex('first', IIndexableRelation)
|
if idx not in self:
|
||||||
self['second'] = FieldIndex('second', IIndexableRelation)
|
self[idx] = FieldIndex(idx, IIndexableRelation)
|
||||||
self['third'] = FieldIndex('third', IIndexableRelation)
|
|
||||||
self.indexesSetUp = True
|
|
||||||
|
|
||||||
def register(self, relation):
|
def register(self, relation):
|
||||||
if not self.indexesSetUp:
|
#self.setupIndexes()
|
||||||
self.setupIndexes()
|
|
||||||
self.index_doc(_getUid(relation), relation)
|
self.index_doc(_getUid(relation), relation)
|
||||||
|
|
||||||
def unregister(self, relation):
|
def unregister(self, relation):
|
||||||
|
@ -133,24 +132,48 @@ def _getUid(ob):
|
||||||
return zapi.getUtility(IIntIds).getId(ob)
|
return zapi.getUtility(IIntIds).getId(ob)
|
||||||
|
|
||||||
def _getRelationship(relation):
|
def _getRelationship(relation):
|
||||||
return _getClassString(relation.__class__)
|
return _getClassString(removeSecurityProxy(relation).__class__)
|
||||||
|
|
||||||
def _getClassString(cls):
|
def _getClassString(cls):
|
||||||
return cls.__module__ + '.' + cls.__name__
|
return '%s.%s' % (cls.__module__, cls.__name__)
|
||||||
|
|
||||||
|
|
||||||
# event handler
|
# events and handlers
|
||||||
|
|
||||||
def unregisterRelations(context, event):
|
class RelationInvalidatedEvent(ObjectEvent):
|
||||||
""" Handles IObjectRemoved event: unregisters all relations for the
|
implements(IRelationInvalidatedEvent)
|
||||||
object that has been removed.
|
|
||||||
|
|
||||||
|
def invalidateRelations(context, event):
|
||||||
|
""" Handles IObjectRemoved event: sends out an IRelationInvalidatedEvent
|
||||||
|
for all relations the object to be removed is involved in.
|
||||||
"""
|
"""
|
||||||
relations = []
|
relations = []
|
||||||
registry = zapi.getUtility(IRelationsRegistry)
|
registry = zapi.getUtility(IRelationsRegistry)
|
||||||
for attr in ('first', 'second', 'third'):
|
for attr in ('first', 'second', 'third'):
|
||||||
relations = registry.query(**{attr: context})
|
relations = registry.query(**{attr: context})
|
||||||
for relation in relations:
|
for relation in relations:
|
||||||
registry.unregister(relation)
|
notify(RelationInvalidatedEvent(relation))
|
||||||
# to do: unregister relation also from the IntId utility
|
|
||||||
# (if appropriate).
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,10 @@ class IntIdsStub:
|
||||||
self.objs.append(ob)
|
self.objs.append(ob)
|
||||||
return self.objs.index(ob)
|
return self.objs.index(ob)
|
||||||
|
|
||||||
|
def unregister(self, ob):
|
||||||
|
id = self.getId(ob)
|
||||||
|
self.objs[id] = None
|
||||||
|
|
||||||
|
|
||||||
class TestRelation(unittest.TestCase):
|
class TestRelation(unittest.TestCase):
|
||||||
"Basic tests for the relation package."
|
"Basic tests for the relation package."
|
||||||
|
|
Loading…
Add table
Reference in a new issue