[Checkins] SVN: z3c.relationfield/trunk/ Improve broken relation support.
Martijn Faassen
faassen at infrae.com
Mon Jan 19 14:51:43 EST 2009
Log message for revision 94856:
Improve broken relation support.
Changed:
U z3c.relationfield/trunk/CHANGES.txt
U z3c.relationfield/trunk/src/z3c/relationfield/README.txt
U z3c.relationfield/trunk/src/z3c/relationfield/event.py
U z3c.relationfield/trunk/src/z3c/relationfield/interfaces.py
U z3c.relationfield/trunk/src/z3c/relationfield/relation.py
-=-
Modified: z3c.relationfield/trunk/CHANGES.txt
===================================================================
--- z3c.relationfield/trunk/CHANGES.txt 2009-01-19 19:50:42 UTC (rev 94855)
+++ z3c.relationfield/trunk/CHANGES.txt 2009-01-19 19:51:42 UTC (rev 94856)
@@ -4,8 +4,24 @@
0.3 (unreleased)
================
-* ...
+* Introduce two new interfaces: ``IHasOutgoingRelations`` and
+ ``IHasIncomingRelations``. ``IHasOutgoingRelations`` should be provided
+ by objects that actually have relations set on them, so that
+ they can be properly cataloged. ``IHasIncomingRelations`` should be
+ set on objects that can be related to, so that broken relations
+ can be properly tracked. ``IHasRelations`` now extends both,
+ so if you provide those on your object you have an object that can
+ have both outgoing as well as incoming relations.
+* Improve broken relations support. When you now break a relation (by
+ removing the relation target), ``to_id`` and ``to_object`` become
+ ``None``. ``to_path`` however will remain the path that the relation
+ last pointed to. ``TemporaryRelation`` objects that when realized
+ are broken relations can also be created.
+
+ You can also for broken status by calling ``isBroken`` on a
+ relation.
+
0.2 (2009-01-08)
================
Modified: z3c.relationfield/trunk/src/z3c/relationfield/README.txt
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/README.txt 2009-01-19 19:50:42 UTC (rev 94855)
+++ z3c.relationfield/trunk/src/z3c/relationfield/README.txt 2009-01-19 19:51:42 UTC (rev 94856)
@@ -40,7 +40,9 @@
The ``IHasRelations`` marker interface is needed to let the relations
on ``Item`` be cataloged (when they are put in a container and removed
-from it, for instance).
+from it, for instance). It is in fact a combination of
+``IHasIncomingRelations`` and ``IHasOutgoingRelations``, which is fine
+as we want items to support both.
Finally we need a test application::
@@ -159,6 +161,8 @@
>>> sorted(root['b'].rel.from_interfaces_flattened)
[<InterfaceClass zope.app.container.interfaces.IContained>,
+ <InterfaceClass z3c.relationfield.interfaces.IHasIncomingRelations>,
+ <InterfaceClass z3c.relationfield.interfaces.IHasOutgoingRelations>,
<InterfaceClass z3c.relationfield.interfaces.IHasRelations>,
<InterfaceClass __builtin__.IItem>,
<InterfaceClass zope.location.interfaces.ILocation>,
@@ -166,6 +170,8 @@
<InterfaceClass zope.interface.Interface>]
>>> sorted(root['b'].rel.to_interfaces_flattened)
[<InterfaceClass zope.app.container.interfaces.IContained>,
+ <InterfaceClass z3c.relationfield.interfaces.IHasIncomingRelations>,
+ <InterfaceClass z3c.relationfield.interfaces.IHasOutgoingRelations>,
<InterfaceClass z3c.relationfield.interfaces.IHasRelations>,
<InterfaceClass __builtin__.IItem>,
<InterfaceClass zope.location.interfaces.ILocation>,
@@ -194,7 +200,10 @@
... def path(self, obj):
... return obj.__name__
... def resolve(self, path):
- ... return root[path]
+ ... try:
+ ... return root[path]
+ ... except KeyError:
+ ... raise ValueError("Cannot resolve path %s" % path)
>>> grok.testing.grok_component('ObjectPath', ObjectPath)
True
@@ -382,6 +391,43 @@
>>> l[0].from_path
u'b'
+Breaking a relation
+===================
+
+We have a relation from ``b`` to ``c`` right now::
+
+ >>> sorted(catalog.findRelations({'to_id': c_id}))
+ [<z3c.relationfield.relation.RelationValue object at ...>]
+
+The relation isn't broken::
+
+ >>> b.rel.isBroken()
+ False
+
+We are now going to break this relation by removing ``c``::
+
+ >>> del root['c']
+
+The relation is broken now::
+
+ >>> b.rel.isBroken()
+ True
+
+The original relation still has a ``to_path``::
+
+ >>> b.rel.to_path
+ u'c'
+
+It's broken however as there is no ``to_object``::
+
+ >>> b.rel.to_object is None
+ True
+
+The ``to_id`` is also gone::
+
+ >>> b.rel.to_id is None
+ True
+
RelationList
============
@@ -492,3 +538,26 @@
>>> after3 = sorted(catalog.findRelations({'to_id': a_id}))
>>> len(after3) > len(after2)
True
+
+Broken temporary relations
+==========================
+
+Let's create another temporary relation, this time a broken one that
+cannot be resolved::
+
+ >>> root['e'] = Item()
+ >>> root['e'].rel = TemporaryRelationValue('nonexistent')
+
+Let's try realizing this relation::
+
+ >>> realize_relations(root['e'])
+
+We end up with a broken relation::
+
+ >>> root['e'].rel.isBroken()
+ True
+
+It's pointing to the nonexistent path::
+
+ >>> root['e'].rel.to_path
+ 'nonexistent'
Modified: z3c.relationfield/trunk/src/z3c/relationfield/event.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/event.py 2009-01-19 19:50:42 UTC (rev 94855)
+++ z3c.relationfield/trunk/src/z3c/relationfield/event.py 2009-01-19 19:51:42 UTC (rev 94856)
@@ -3,19 +3,20 @@
from zope.interface import providedBy
from zope.schema import getFields
from zope import component
-from zope.app.intid.interfaces import IIntIds
+from zope.app.intid.interfaces import IIntIds, IIntIdRemovedEvent
from zope.app.container.interfaces import (IObjectAddedEvent,
IObjectRemovedEvent)
from zope.lifecycleevent.interfaces import IObjectModifiedEvent
from zc.relation.interfaces import ICatalog
-from z3c.relationfield.interfaces import (IHasRelations,
+from z3c.relationfield.interfaces import (IHasOutgoingRelations,
+ IHasIncomingRelations,
IRelation, IRelationList,
IRelationValue,
ITemporaryRelationValue)
- at grok.subscribe(IHasRelations, IObjectAddedEvent)
+ at grok.subscribe(IHasOutgoingRelations, IObjectAddedEvent)
def addRelations(obj, event):
"""Register relations.
@@ -24,7 +25,7 @@
for name, relation in _relations(obj):
_setRelation(obj, name, relation)
- at grok.subscribe(IHasRelations, IObjectRemovedEvent)
+ at grok.subscribe(IHasOutgoingRelations, IObjectRemovedEvent)
def removeRelations(obj, event):
"""Remove relations.
@@ -36,7 +37,7 @@
if relation is not None:
catalog.unindex(relation)
- at grok.subscribe(IHasRelations, IObjectModifiedEvent)
+ at grok.subscribe(IHasOutgoingRelations, IObjectModifiedEvent)
def updateRelations(obj, event):
"""Re-register relations, after they have been changed.
"""
@@ -53,6 +54,23 @@
# add new relations
addRelations(obj, event)
+ at grok.subscribe(IIntIdRemovedEvent)
+def breakRelations(event):
+ """Break relations on any object pointing to us.
+
+ That is, store the object path on the broken relation.
+ """
+ obj = event.object
+ if not IHasIncomingRelations.providedBy(obj):
+ return
+ catalog = component.getUtility(ICatalog)
+ intids = component.getUtility(IIntIds)
+
+ # find all relations that point to us
+ rels = list(catalog.findRelations({'to_id': intids.getId(obj)}))
+ for rel in rels:
+ rel.broken(rel.to_path)
+
def realize_relations(obj):
"""Given an object, convert any temporary relations on it to real ones.
"""
Modified: z3c.relationfield/trunk/src/z3c/relationfield/interfaces.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/interfaces.py 2009-01-19 19:50:42 UTC (rev 94855)
+++ z3c.relationfield/trunk/src/z3c/relationfield/interfaces.py 2009-01-19 19:51:42 UTC (rev 94856)
@@ -1,13 +1,29 @@
from zope.interface import Interface, Attribute
from zope.schema.interfaces import IField, IList
-class IHasRelations(Interface):
- """Marker interface indicating that the object has relations.
+class IHasOutgoingRelations(Interface):
+ """Marker interface indicating that the object has outgoing relations.
- Use this interface to make sure that the relations get added and
- removed from the catalog when appropriate.
+ Provide this interface on your own objects with outgoing relations
+ to make sure that the relations get added and removed from the
+ catalog when appropriate.
"""
+class IHasIncomingRelations(Interface):
+ """Marker interface indicating the the object has incoming relations.
+
+ Provide this interface on your own objects with incoming
+ relations. This will make sure that broken relations to that
+ object are tracked properly.
+ """
+
+class IHasRelations(IHasIncomingRelations, IHasOutgoingRelations):
+ """Marker interface indicating that the object has relations of any kind.
+
+ Provide this interface if the object can have both outgoing as
+ well as incoming relations.
+ """
+
class IRelation(IField):
pass
@@ -34,11 +50,15 @@
from_attribute = Attribute("The name of the attribute of the from object.")
- to_object = Attribute("The object this relation is pointing to.")
+ to_object = Attribute("The object this relation is pointing to. "
+ "This value is None if the relation is broken.")
- to_id = Attribute("Id of the object this relation is pointing to.")
+ to_id = Attribute("Id of the object this relation is pointing to. "
+ "This value is None if the relation is broken.")
- to_path = Attribute("The path of the object this relation is pointing to.")
+ to_path = Attribute("The path of the object this relation is pointing to. "
+ "If the relation is broken, this value will still "
+ "point to the last path the relation pointed to.")
to_interfaces = Attribute("The interfaces of the to-object.")
@@ -46,6 +66,21 @@
"The interfaces of the to object, flattened. "
"This includes all base interfaces.")
+ def broken(to_path):
+ """Set this relation as broken.
+
+ to_path - the (non-nonexistent) path that the relation pointed to.
+
+ The relation will be broken. If you provide
+ IHasIncomingRelations on objects that have incoming relations,
+ relations will be automatically broken when you remove an
+ object.
+ """
+
+ def isBroken():
+ """Return True if this is a broken relation.
+ """
+
class ITemporaryRelationValue(Interface):
"""A temporary relation.
Modified: z3c.relationfield/trunk/src/z3c/relationfield/relation.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/relation.py 2009-01-19 19:50:42 UTC (rev 94855)
+++ z3c.relationfield/trunk/src/z3c/relationfield/relation.py 2009-01-19 19:51:42 UTC (rev 94856)
@@ -13,13 +13,15 @@
class RelationValue(Persistent):
implements(IRelationValue)
+ _broken_to_path = None
+
def __init__(self, to_id):
self.to_id = to_id
- # these will be set automatically by RelationProperty
+ # these will be set automatically by events
self.from_object = None
self.__parent__ = None
self.from_attribute = None
-
+
@property
def from_id(self):
intids = component.getUtility(IIntIds)
@@ -43,6 +45,8 @@
@property
def to_path(self):
+ if self.to_object is None:
+ return self._broken_to_path
return _path(self.to_object)
@property
@@ -58,6 +62,13 @@
return cmp(self.to_id, None)
return cmp(self.to_id, other.to_id)
+ def broken(self, to_path):
+ self._broken_to_path = to_path
+ self.to_id = None
+
+ def isBroken(self):
+ return self.to_id is None
+
class TemporaryRelationValue(Persistent):
"""A relation that isn't fully formed yet.
@@ -71,13 +82,20 @@
def convert(self):
object_path = component.getUtility(IObjectPath)
- # XXX what if we have a broken relation?
- to_object = object_path.resolve(self.to_path)
+ try:
+ to_object = object_path.resolve(self.to_path)
+ except ValueError:
+ # we have a broken relation, so create one
+ result = RelationValue(None)
+ result.broken(self.to_path)
+ return result
intids = component.getUtility(IIntIds)
to_id = intids.getId(to_object)
return RelationValue(to_id)
def _object(id):
+ if id is None:
+ return None
intids = component.getUtility(IIntIds)
try:
return intids.getObject(id)
More information about the Checkins
mailing list