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
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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,))
|
||||
|
||||
|
|
|
@ -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.')
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
|
Loading…
Add table
Reference in a new issue