============================================================== Using a relationfield to express relationships between objects ============================================================== :Author: Unknown :Version: unknown Introduction ============ It is sometimes desirable to somehow have objects store references to other objects. There are different ways to do this, some better than others, some easier than others. I'll show you one way of doing it, based on the z3c.relationfield and z3c.relationfieldui packages. I won't claim it's the best way out there, but I certainly found it easy! Prerequisites ============= You should know how to work with schema and AddForms based on them. You should also know about catalogs, indexes and integer IDs. How Relations work ================== The z3c.relationfield (currently) provides 3 fields you can use as schema fields in your interface definitions: Relation, RelationChoice and RelationList. The value(s) they store are RelationValue objects. You could store relations to objects by using standard python references, by doing .. code-block:: python a = A() b = B() ... b.rel = a This will work perfectly, and even if 'a' and 'b' are already stored separately in the ZODB and afterwards related like that, the ZODB is smart enough not to store a copy of 'a', but a real reference to 'a'. (which is, by the way, a common misconception about the ZODB [1]). But this has a problem: if you later intend to remove 'a' from the ZODB (by removing it from its container), it won't actually be deleted. You won't find it under its previous container, but 'b.rel' will still yield the 'a' object, as 'b' holds a reference to it. This is the same problem normal indexes in catalogs have, and they solved it by using integer IDs for objects: each object that is entered into the ZODB gets its own unique ID. If you have this ID, you can later look up the object it represents, whenever you need it. The same problem can usually use the same solution, so a RelationValue merely stores the integer ID of the 'to' object. As the RelationValue is meant to be stored as an attribute of the 'from' object, storing the id of the 'from' object doesn't make much sense, so a real reference is stored here. With the 'to_object' and 'from_object' attributes you can get to the actual objects of the RelationValue, 'to_id' and 'from_id' gives access to the integer IDs. This, of course requires you to have an Unique Integer ID utility activated in your app. You can do this by configuring your application like this: .. code-block:: python from zope.app.intid.interfaces import IIntIds from zope.app.intid import IntIds class Relations(grok.Application, grok.Container): grok.local_utility(IntIds, provides=IIntIds) Searching relations =================== A very handy and important feature of zc.relation is the relation catalog (zc.relation.catalog.Catalog): it enables you to search for specific relations, like 'Give me all employees that have to report to this manager'. However, zc.relation requires you still to define indexes, it just provided the catalog functionality. Luckily z3c.relationfield provides a RelationCatalog that derives from zc.relation.catalog.Catalog, but automatically creates the needed indexes. So creating the catalog is quite easy: .. code-block:: python from zc.relation.interfaces import ICatalog from z3c.relationfield import RelationCatalog class Test(grok.Application, grok.Container): grok.local_utility(IntIds, provides=IIntIds) grok.local_utility(RelationCatalog, provides=ICatalog) z3c.relationfield also defines the appropriate event handlers so that all objects that have relations are automatically indexed. This is done by using the 'marker interfaces' IHasOutgoingRelations, IHasIncomingRelations, IHasRelations. The first one indicates that the object refers to another object, the second one that the object can be referred to by another object. The 3rd one is a combination of both Relation and RelationChoice =========================== Enough theory, let's define an application to use our relations in. Imagine you want an app in which everyone can have a buddy. So every 'buddy' object can have a 'link' to another buddy object. The relationfieldui[4] package has 2 possible schema fields for this: Relation and RelationChoice. The difference is really about what widget will be used to render it in a form. Relation will render a textbox and a button that will popup a window in which you are to select an object and then via AJAX fill in the path to the object. The other one however, works just like an ordinary Choice field, so we'll use that one. Let's define the buddy interface like this: .. code-block:: python from zope.interface import Interface from zope import schema from z3c.relationfield import RelationChoice class IBuddy(Interface, IHasRelations): name = schema.TextLine(title=u'Name') buddy = RelationChoice(title=u'Buddy', source = BuddySource(), required = False) Note the use of the IHasRelations marker interface. As the RelationChoice field works like a normal Choice field, you need to specify a set of values, a vocabulary or a source where it will draw it's values from. I use a source, and the relationfieldui defines a handy baseclass you can use: RelationSourceFactory. .. code-block:: python from z3c.relationfieldui import RelationSourceFactory class BuddySource(RelationSourceFactory): def getTargets(self): return [b for b in grok.getSite().values() if IBuddy.providedBy(b)] def getTitle(self, value): return value.to_object.name All you need to do is define a getTargets method that returns an iterable to all the objects that can be a target for your relation, and a getTitle method that can turn a RelationValue into a human readable text to show in the drop- down list. Another thing you'll be needing is an IObjectPath implementation, like this: .. code-block:: python from z3c.objpath.interfaces import IObjectPath from z3c.objpath import path, resolve class ObjectPath(grok.GlobalUtility): grok.provides(IObjectPath) def path(self, obj): return path(grok.getSite(), obj) def resolve(self, path): return resolve(grok.getSite(), path) This class can create a path to an object or an object from a path. A path is really how to get to the object in the ZODB. The RelationSourceFactory uses this to create tokens from objects. Now let's implement IBuddy and create an AddForm and a View: .. code-block:: python class Buddy(grok.Model): grok.implements(IBuddy) class AddBuddy(grok.AddForm): grok.context(grok.Container) grok.name('add') form_fields = grok.Fields(IBuddy) @grok.action('Add') def Add(self, **data): buddy = Buddy() self.applyData(buddy, **data) self.context[buddy.name] = buddy self.redirect(self.url(buddy)) class BuddyView(grok.View): grok.context(IBuddy) grok.name('index') The buddyview template may look like this: .. code-block:: xml