[Checkins] SVN: z3c.relationfield/trunk/src/z3c/relationfield/ Prevent the event failures from failing when utilities are missing or when objects do not implement IContained.
Alec Mitchell
apm13 at columbia.edu
Sat Apr 11 23:20:35 EDT 2009
Log message for revision 99135:
Prevent the event failures from failing when utilities are missing or when objects do not implement IContained.
Changed:
U z3c.relationfield/trunk/src/z3c/relationfield/event.py
U z3c.relationfield/trunk/src/z3c/relationfield/testing.py
A z3c.relationfield/trunk/src/z3c/relationfield/tests.py
-=-
Modified: z3c.relationfield/trunk/src/z3c/relationfield/event.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/event.py 2009-04-11 17:52:25 UTC (rev 99134)
+++ z3c.relationfield/trunk/src/z3c/relationfield/event.py 2009-04-12 03:20:35 UTC (rev 99135)
@@ -33,30 +33,38 @@
Any relation object on the object will be removed from the catalog.
"""
- catalog = component.getUtility(ICatalog)
-
+ catalog = component.queryUtility(ICatalog)
+ if catalog is None:
+ return
+
for name, relation in _relations(obj):
if relation is not None:
- catalog.unindex(relation)
+ try:
+ catalog.unindex(relation)
+ except KeyError:
+ # The relation value has already been unindexed.
+ pass
@grok.subscribe(IHasOutgoingRelations, IObjectModifiedEvent)
def updateRelations(obj, event):
"""Re-register relations, after they have been changed.
"""
- # if we do not yet have a parent, we ignore this modified event
- # completely. The object will need to be added somewhere first
- # before modification events will have an effect
- if obj.__parent__ is None:
+ catalog = component.getUtility(ICatalog)
+ intids = component.getUtility(IIntIds)
+
+ # check that the object has an intid, otherwise there's nothing to be done
+ try:
+ obj_id = intids.getId(obj)
+ except KeyError:
+ # The object has not been added to the ZODB yet
return
# remove previous relations coming from id (now have been overwritten)
- catalog = component.getUtility(ICatalog)
- intids = component.getUtility(IIntIds)
# have to activate query here with list() before unindexing them so we don't
# get errors involving buckets changing size
- rels = list(catalog.findRelations({'from_id': intids.getId(obj)}))
+ rels = list(catalog.findRelations({'from_id': obj_id}))
for rel in rels:
- catalog.unindex(rel)
+ catalog.unindex(rel)
# add new relations
addRelations(obj, event)
@@ -70,16 +78,23 @@
obj = event.object
if not IHasIncomingRelations.providedBy(obj):
return
- catalog = component.getUtility(ICatalog)
- intids = component.getUtility(IIntIds)
+ catalog = component.queryUtility(ICatalog)
+ intids = component.queryUtility(IIntIds)
+ if catalog is None or intids is None:
+ return
# find all relations that point to us
+ try:
+ obj_id = intids.getId(obj)
+ except KeyError:
+ # our intid was unregistered already
+ return
rels = list(catalog.findRelations({'to_id': intids.getId(obj)}))
for rel in rels:
rel.broken(rel.to_path)
# we also need to update the relations for these objects
notify(ObjectModifiedEvent(rel.from_object))
-
+
def realize_relations(obj):
"""Given an object, convert any temporary relations on it to real ones.
"""
Modified: z3c.relationfield/trunk/src/z3c/relationfield/testing.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/testing.py 2009-04-11 17:52:25 UTC (rev 99134)
+++ z3c.relationfield/trunk/src/z3c/relationfield/testing.py 2009-04-12 03:20:35 UTC (rev 99135)
@@ -1,7 +1,46 @@
import os
import z3c.relationfield
+from zc.relation.interfaces import ICatalog
+from zope.interface import implements
+from zope.component import provideUtility, getGlobalSiteManager
from zope.app.testing.functional import ZCMLLayer
+from zope.app.intid.interfaces import IIntIds
ftesting_zcml = os.path.join(
os.path.dirname(z3c.relationfield.__file__), 'ftesting.zcml')
FunctionalLayer = ZCMLLayer(ftesting_zcml, __name__, 'FunctionalLayer')
+
+class MockIntIds(object):
+ """Dumb utility for unit tests, returns sequential integers. Not a
+ complete implementation."""
+ implements(IIntIds)
+
+ def getId(self, ob):
+ """Appropriately raises KeyErrors when the object is not registered,
+ e.g. always"""
+ raise KeyError(ob)
+mock_intids = MockIntIds()
+
+
+class MockCatalog(object):
+ """Does nothing except exist"""
+ implements(ICatalog)
+
+ def findRelations(self, query):
+ return []
+mock_catalog = MockCatalog()
+
+def register_fake_intid():
+ provideUtility(mock_intids)
+def unregister_fake_intid():
+ sm = getGlobalSiteManager()
+ sm.unregisterUtility(mock_intids)
+
+def register_fake_catalog():
+ provideUtility(mock_catalog)
+def unregister_fake_catalog():
+ sm = getGlobalSiteManager()
+ sm.unregisterUtility(mock_catalog)
+
+class MockContent(object):
+ implements(z3c.relationfield.interfaces.IHasRelations)
Added: z3c.relationfield/trunk/src/z3c/relationfield/tests.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/tests.py (rev 0)
+++ z3c.relationfield/trunk/src/z3c/relationfield/tests.py 2009-04-12 03:20:35 UTC (rev 99135)
@@ -0,0 +1,68 @@
+from unittest import makeSuite, TestCase, TestSuite
+from z3c.relationfield.testing import (
+ register_fake_intid,
+ register_fake_catalog,
+ unregister_fake_intid,
+ unregister_fake_catalog,
+ MockContent,
+ )
+from z3c.relationfield import event
+from zope.component.interfaces import ObjectEvent
+from zope.component.interfaces import ComponentLookupError
+
+class EventTests(TestCase):
+ """Unit tests for the relation field event handlers. The event handlers
+ should be tolerant of missing utilities and objects not yet registered with
+ the intid utility."""
+
+ def setUp(self):
+ self.content = MockContent()
+ register_fake_catalog()
+ register_fake_intid()
+
+ def tearDown(self):
+ unregister_fake_catalog()
+ unregister_fake_intid()
+
+ def test_missing_intids(self):
+ """Event handlers which trigger on object removal should not
+ throw exceptions when the utilities are missing. The utilities may
+ have been deleted in the same transaction (e.g. site deletion)."""
+ # Remove intid utility and ensure the event handler doesn't fail
+ unregister_fake_intid()
+ try:
+ event.breakRelations(ObjectEvent(self.content))
+ except ComponentLookupError:
+ self.fail("breakRelations fails when intid utility is missing")
+
+ def test_break_relations_missing_catalog(self):
+ # Remove relations catalog and ensure the event handler doesn't fail
+ unregister_fake_catalog()
+ try:
+ event.breakRelations(ObjectEvent(self.content))
+ except ComponentLookupError:
+ self.fail("breakRelations fails when catalog utility is missing")
+
+ def test_remove_relations_missing_catalog(self):
+ # Remove relations catalog and ensure the event handler doesn't fail
+ unregister_fake_catalog()
+ try:
+ event.removeRelations(self.content, None)
+ except ComponentLookupError:
+ self.fail("removeRelations fails when catalog utility is missing")
+
+ def test_initid_failure(self):
+ """When an object has not yet been registered with the intid utility,
+ getId fails with a KeyError. The event handlers need to handle this
+ cleanly."""
+ # Our content is not yet registered with the intid utility
+ # and has no __parent__ attribute
+ try:
+ event.updateRelations(self.content, None)
+ except (KeyError, AttributeError):
+ self.fail("updateRelations fails with unregistered object")
+
+def test_suite():
+ suite=TestSuite()
+ suite.addTest(makeSuite(EventTests))
+ return suite
More information about the Checkins
mailing list