z3c.relationfield
*****************
Introduction
============
This package implements a new schema field Relation, and the
RelationValue objects that store actual relations. It can index these
relations using the ``zc.relation`` infractructure, and using these
indexes can efficiently answer questions about the relations.
The package `z3c.relationfieldui`_ in addition provides a widget to
edit and display Relation fields.
.. _`z3c.relationfieldui`: http://pypi.python.org/pypi/z3c.relationfieldui
Setup
=====
``z3c.relationfield.Relation`` is a schema field that can be used to
express relations. Let's define a schema IItem that uses a relation
field:
.. code-block:: python
>>> from z3c.relationfield import Relation
>>> from zope.interface import Interface
>>> class IItem(Interface):
... rel = Relation(title=u"Relation")
We also define a class ``Item`` that implements both ``IItem``
and the special ``z3c.relationfield.interfaces.IHasRelations``
interface:
.. code-block:: python
>>> from z3c.relationfield.interfaces import IHasRelations
>>> from persistent import Persistent
>>> from zope.interface import implementer
>>> @implementer(IItem, IHasRelations)
... class Item(Persistent):
...
... def __init__(self):
... self.rel = None
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). 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:
.. code-block:: python
>>> from zope.site.site import SiteManagerContainer
>>> from zope.container.btree import BTreeContainer
>>> class TestApp(SiteManagerContainer, BTreeContainer):
... pass
We set up the test application:
.. code-block:: python
>>> from ZODB.MappingStorage import DB
>>> db = DB()
>>> conn = db.open()
>>> root = conn.root()['root'] = TestApp()
>>> conn.add(root)
We make sure that this is the current site, so we can look up local
utilities in it and so on. Normally this is done automatically by
Zope's traversal mechanism:
.. code-block:: python
>>> from zope.site.site import LocalSiteManager
>>> root.setSiteManager(LocalSiteManager(root))
>>> from zope.component.hooks import setSite
>>> setSite(root)
For this site to work with ``z3c.relationship``, we need to set up two
utilities. Firstly, an ``IIntIds`` that tracks unique ids for objects
in the ZODB:
.. code-block:: python
>>> from zope.intid import IntIds
>>> from zope.intid.interfaces import IIntIds
>>> root['intids'] = intids = IntIds()
>>> sm = root.getSiteManager()
>>> sm.registerUtility(intids, provided=IIntIds)
And secondly a relation catalog that actually indexes the relations:
.. code-block:: python
>>> from z3c.relationfield import RelationCatalog
>>> from zc.relation.interfaces import ICatalog
>>> root['catalog'] = catalog = RelationCatalog()
>>> sm.registerUtility(catalog, provided=ICatalog)
Using the relation field
========================
We'll add an item ``a`` to our application:
.. code-block:: python
>>> root['a'] = Item()
All items, including the one we just created, should have unique int
ids as this is required to link to them:
.. code-block:: python
>>> from zope import component
>>> from zope.intid.interfaces import IIntIds
>>> intids = component.getUtility(IIntIds)
>>> a_id = intids.getId(root['a'])
>>> a_id >= 0
True
The relation is currently ``None``:
.. code-block:: python
>>> root['a'].rel is None
True
Now we can create an item ``b`` that links to item ``a`` (through its
int id):
.. code-block:: python
>>> from z3c.relationfield import RelationValue
>>> b = Item()
>>> b.rel = RelationValue(a_id)
We now store the ``b`` object in a container, which will also set up
its relation (as an ``IObjectAddedEvent`` will be fired):
.. code-block:: python
>>> root['b'] = b
Let's examine the relation. First we'll check which attribute of the
pointing object ('b') this relation is pointing from:
.. code-block:: python
>>> root['b'].rel.from_attribute
'rel'
We can ask for the object it is pointing at:
.. code-block:: python
>>> to_object = root['b'].rel.to_object
>>> to_object.__name__
'a'
We can also get the object that is doing the pointing; since we
supplied the ``IHasRelations`` interface, the event system took care
of setting this:
.. code-block:: python
>>> from_object = root['b'].rel.from_object
>>> from_object.__name__
'b'
This object is also known as the ``__parent__``; again the event
sytem took care of setting this:
.. code-block:: python
>>> parent_object = root['b'].rel.__parent__
>>> parent_object is from_object
True
The relation also knows about the interfaces of both the pointing object
and the object that is being pointed at:
.. code-block:: python
>>> from pprint import pprint
>>> pprint(sorted(root['b'].rel.from_interfaces))
[<InterfaceClass zope.location.interfaces.IContained>,
<InterfaceClass z3c.relationfield.interfaces.IHasRelations>,
<InterfaceClass builtins.IItem>,
<InterfaceClass persistent.interfaces.IPersistent>]
>>> pprint(sorted(root['b'].rel.to_interfaces))
[<InterfaceClass zope.location.interfaces.IContained>,
<InterfaceClass z3c.relationfield.interfaces.IHasRelations>,
<InterfaceClass builtins.IItem>,
<InterfaceClass persistent.interfaces.IPersistent>]
We can also get the interfaces in flattened form:
.. code-block:: python
>>> pprint(sorted(root['b'].rel.from_interfaces_flattened))
[<InterfaceClass zope.location.interfaces.IContained>,
<InterfaceClass z3c.relationfield.interfaces.IHasIncomingRelations>,
<InterfaceClass z3c.relationfield.interfaces.IHasOutgoingRelations>,
<InterfaceClass z3c.relationfield.interfaces.IHasRelations>,
<InterfaceClass builtins.IItem>,
<InterfaceClass zope.location.interfaces.ILocation>,
<InterfaceClass persistent.interfaces.IPersistent>,
<InterfaceClass zope.interface.Interface>]
>>> pprint(sorted(root['b'].rel.to_interfaces_flattened))
[<InterfaceClass zope.location.interfaces.IContained>,
<InterfaceClass z3c.relationfield.interfaces.IHasIncomingRelations>,
<InterfaceClass z3c.relationfield.interfaces.IHasOutgoingRelations>,
<InterfaceClass z3c.relationfield.interfaces.IHasRelations>,
<InterfaceClass builtins.IItem>,
<InterfaceClass zope.location.interfaces.ILocation>,
<InterfaceClass persistent.interfaces.IPersistent>,
<InterfaceClass zope.interface.Interface>]
Paths
=====
We can also obtain the path of the relation (both from where it is
pointing as well as to where it is pointing). The path should be a
human-readable reference to the object we are pointing at, suitable
for serialization. In order to work with paths, we first need to set
up an ``IObjectPath`` utility.
Since in this example we only place objects into a single flat root
container, the paths in this demonstration can be extremely simple:
just the name of the object we point to. In more sophisticated
applications a path would typically be a slash separated path, like
``/foo/bar``:
.. code-block:: python
>>> from zope.interface import Interface
>>> from zope.interface import implementer
>>> from z3c.objpath.interfaces import IObjectPath
>>> @implementer(IObjectPath)
... class ObjectPath(object):
...
... def path(self, obj):
... return obj.__name__
... def resolve(self, path):
... try:
... return root[path]
... except KeyError:
... raise ValueError("Cannot resolve path %s" % path)
>>> from zope.component import getGlobalSiteManager
>>> gsm = getGlobalSiteManager()
>>> op = ObjectPath()
>>> gsm.registerUtility(op)
After this, we can get the path of the object the relation points to:
.. code-block:: python
>>> root['b'].rel.to_path
'a'
We can also get the path of the object that is doing the pointing:
.. code-block:: python
>>> root['b'].rel.from_path
'b'
Comparing and sorting relations
===============================
Let's create a bunch of ``RelationValue`` objects and compare them:
.. code-block:: python
>>> rel_to_a = RelationValue(a_id)
>>> b_id = intids.getId(root['b'])
>>> rel_to_b = RelationValue(b_id)
>>> rel_to_a == rel_to_b
False
Relations of course are equal to themselves:
.. code-block:: python
>>> rel_to_a == rel_to_a
True
A relation that is stored is equal to a relation that isn't stored yet:
.. code-block:: python
>>> root['b'].rel == rel_to_a
True
We can also sort relations:
.. code-block:: python
>>> expected = [('', 'a'), ('', 'b'), ('b', 'a')]
>>> observed = [(rel.from_path, rel.to_path) for rel in
... sorted([root['b'].rel, rel_to_a, rel_to_b])]
>>> expected == observed
True
Relation queries
================
Now that we have set up and indexed a relationship between ``a`` and
``b``, we can issue queries using the relation catalog. Let's first
get the catalog:
.. code-block:: python
>>> from zc.relation.interfaces import ICatalog
>>> catalog = component.getUtility(ICatalog)
Let's ask the catalog about the relation from ``b`` to ``a``:
.. code-block:: python
>>> l = sorted(catalog.findRelations({'to_id': intids.getId(root['a'])}))
>>> l
[<...RelationValue object at ...>]
We look at this relation object again. We indeed go the right one:
.. code-block:: python
>>> rel = l[0]
>>> rel.from_object.__name__
'b'
>>> rel.to_object.__name__
'a'
>>> rel.from_path
'b'
>>> rel.to_path
'a'
Asking for relations to ``b`` will result in an empty list, as no such
relations have been set up:
.. code-block:: python
>>> sorted(catalog.findRelations({'to_id': intids.getId(root['b'])}))
[]
We can also issue more specific queries, restricting it on the
attribute used for the relation field and the interfaces provided by
the related objects. Here we look for all relations between ``b`` and
``a`` that are stored in object attribute ``rel`` and are pointing
from an object with interface ``IItem`` to another object with the
interface ``IItem``:
.. code-block:: python
>>> sorted(catalog.findRelations({
... 'to_id': intids.getId(root['a']),
... 'from_attribute': 'rel',
... 'from_interfaces_flattened': IItem,
... 'to_interfaces_flattened': IItem}))
[<...RelationValue object at ...>]
There are no relations stored for another attribute:
.. code-block:: python
>>> sorted(catalog.findRelations({
... 'to_id': intids.getId(root['a']),
... 'from_attribute': 'foo'}))
[]
There are also no relations stored for a new interface we'll introduce
here:
.. code-block:: python
>>> class IFoo(IItem):
... pass
>>> sorted(catalog.findRelations({
... 'to_id': intids.getId(root['a']),
... 'from_interfaces_flattened': IItem,
... 'to_interfaces_flattened': IFoo}))
[]
Changing the relation
=====================
Let's create a new object ``c``:
.. code-block:: python
>>> root['c'] = Item()
>>> c_id = intids.getId(root['c'])
Nothing points to ``c`` yet:
.. code-block:: python
>>> sorted(catalog.findRelations({'to_id': c_id}))
[]
We currently have a relation from ``b`` to ``a``:
.. code-block:: python
>>> sorted(catalog.findRelations({'to_id': intids.getId(root['a'])}))
[<...RelationValue object at ...>]
We can change the relation to point at a new object ``c``:
.. code-block:: python
>>> root['b'].rel = RelationValue(c_id)
We need to send an ``IObjectModifiedEvent`` to let the catalog know we
have changed the relations:
.. code-block:: python
>>> from zope.event import notify
>>> from zope.lifecycleevent import ObjectModifiedEvent
>>> notify(ObjectModifiedEvent(root['b']))
We should find now a single relation from ``b`` to ``c``:
.. code-block:: python
>>> sorted(catalog.findRelations({'to_id': c_id}))
[<...RelationValue object at ...>]
The relation to ``a`` should now be gone:
.. code-block:: python
>>> sorted(catalog.findRelations({'to_id': intids.getId(root['a'])}))
[]
If we store the relation in a non schema field it should persist
the ObjectModifiedEvent.
.. code-block:: python
>>> from z3c.relationfield.event import _setRelation
>>> _setRelation(root['b'], 'my-fancy-relation', rel_to_a)
>>> sorted(catalog.findRelations({'to_id': intids.getId(root['a'])}))
[<...RelationValue object at ...>]
>>> notify(ObjectModifiedEvent(root['b']))
>>> rel = sorted(catalog.findRelations({'to_id': intids.getId(root['a'])}))
>>> rel
[<...RelationValue object at ...>]
>>> catalog.unindex(rel[0])
Removing the relation
=====================
We have a relation from ``b`` to ``c`` right now:
.. code-block:: python
>>> sorted(catalog.findRelations({'to_id': c_id}))
[<...RelationValue object at ...>]
We can clean up an existing relation from ``b`` to ``c`` by setting it
to ``None``:
.. code-block:: python
>>> root['b'].rel = None
We need to send an ``IObjectModifiedEvent`` to let the catalog know we
have changed the relations:
.. code-block:: python
>>> notify(ObjectModifiedEvent(root['b']))
Setting the relation on ``b`` to ``None`` should remove that relation
from the relation catalog, so we shouldn't be able to find it anymore:
.. code-block:: python
>>> sorted(catalog.findRelations({'to_id': intids.getId(root['c'])}))
[]
Let's reestablish the removed relation:
.. code-block:: python
>>> root['b'].rel = RelationValue(c_id)
>>> notify(ObjectModifiedEvent(root['b']))
>>> sorted(catalog.findRelations({'to_id': c_id}))
[<...RelationValue object at ...>]
Copying an object with relations
================================
Let's copy an object with relations:
.. code-block:: python
>>> from zope.copypastemove.interfaces import IObjectCopier
>>> IObjectCopier(root['b']).copyTo(root)
'b-2'
>>> 'b-2' in root
True
Two relations to ``c`` can now be found, one from the original, and
the other from the copy:
.. code-block:: python
>>> l = sorted(catalog.findRelations({'to_id': c_id}))
>>> len(l)
2
>>> l[0].from_path
'b'
>>> l[1].from_path
'b-2'
Relations are sortable
======================
Relations are sorted by default on a combination of the relation name,
the path of the object the relation is one and the path of the object
the relation is pointing to.
Let's query all relations availble right now and sort them:
.. code-block:: python
>>> l = sorted(catalog.findRelations())
>>> len(l)
2
>>> l[0].from_attribute
'rel'
>>> l[1].from_attribute
'rel'
>>> l[0].from_path
'b'
>>> l[1].from_path
'b-2'
Removing an object with relations
=================================
We will remove ``b-2`` again. Its relation should automatically be remove
from the catalog:
.. code-block:: python
>>> del root['b-2']
>>> l = sorted(catalog.findRelations({'to_id': c_id}))
>>> len(l)
1
>>> l[0].from_path
'b'
Breaking a relation
===================
We have a relation from ``b`` to ``c`` right now:
.. code-block:: python
>>> sorted(catalog.findRelations({'to_id': c_id}))
[<...RelationValue object at ...>]
We have no broken relations:
.. code-block:: python
>>> sorted(catalog.findRelations({'to_id': None}))
[]
The relation isn't broken:
.. code-block:: python
>>> b.rel.isBroken()
False
We are now going to break this relation by removing ``c``:
.. code-block:: python
>>> del root['c']
The relation is broken now:
.. code-block:: python
>>> b.rel.isBroken()
True
The original relation still has a ``to_path``:
.. code-block:: python
>>> b.rel.to_path
'c'
It's broken however as there is no ``to_object``:
.. code-block:: python
>>> b.rel.to_object is None
True
The ``to_id`` is also gone:
.. code-block:: python
>>> b.rel.to_id is None
True
We cannot find the broken relation in the catalog this way as it's not
pointing to ``c_id`` anymore:
.. code-block:: python
>>> sorted(catalog.findRelations({'to_id': c_id}))
[]
We can however find it by searching for relations that have a
``to_id`` of ``None``:
.. code-block:: python
>>> sorted(catalog.findRelations({'to_id': None}))
[<...RelationValue object at ...>]
A broken relation isn't equal to ``None`` (this was a bug):
.. code-block:: python
>>> b.rel == None
False
RelationChoice
==============
A ``RelationChoice`` field is much like an ordinary ``Relation`` field
but can be used to render a special widget that allows a choice of
selections.
We will first demonstrate a ``RelationChoice`` field has the same effect
as a ``Relation`` field itself:
.. code-block:: python
>>> from z3c.relationfield import RelationChoice
>>> class IChoiceItem(Interface):
... rel = RelationChoice(title=u"Relation", values=[])
>>> @implementer(IChoiceItem, IHasRelations)
... class ChoiceItem(Persistent):
...
... def __init__(self):
... self.rel = None
Let's create an object to point the relation to:
.. code-block:: python
>>> root['some_object'] = Item()
>>> some_object_id = intids.getId(root['some_object'])
And let's establish the relation:
:.. code-block:: python
>>> choice_item = ChoiceItem()
>>> choice_item.rel = RelationValue(some_object_id)
>>> root['choice_item'] = choice_item
We can query for this relation now:
.. code-block:: python
>>> l = sorted(catalog.findRelations({'to_id': some_object_id}))
>>> l
[<...RelationValue object at ...>]
RelationList
============
Let's now experiment with the ``RelationList`` field which can be used
to maintain a list of relations:
.. code-block:: python
>>> from z3c.relationfield import RelationList
>>> class IMultiItem(Interface):
... rel = RelationList(title=u"Relation")
We also define a class ``MultiItem`` that implements both
``IMultiItem`` and the special
``z3c.relationfield.interfaces.IHasRelations`` interface:
.. code-block:: python
>>> @implementer(IMultiItem, IHasRelations)
... class MultiItem(Persistent):
...
... def __init__(self):
... self.rel = None
We set up a few object we can then create relations between:
.. code-block:: python
>>> root['multi1'] = MultiItem()
>>> root['multi2'] = MultiItem()
>>> root['multi3'] = MultiItem()
Let's create a relation from ``multi1`` to both ``multi2`` and
``multi3``:
.. code-block:: python
>>> multi1_id = intids.getId(root['multi1'])
>>> multi2_id = intids.getId(root['multi2'])
>>> multi3_id = intids.getId(root['multi3'])
>>> root['multi1'].rel = [RelationValue(multi2_id),
... RelationValue(multi3_id)]
We need to notify that we modified the ObjectModifiedEvent
.. code-block:: python
>>> notify(ObjectModifiedEvent(root['multi1']))
Now that this is set up, let's verify whether we can find the
proper relations in in the catalog:
.. code-block:: python
>>> len(list(catalog.findRelations({'to_id': multi2_id})))
1
>>> len(list(catalog.findRelations({'to_id': multi3_id})))
1
>>> len(list(catalog.findRelations({'from_id': multi1_id})))
2
Temporary relations
===================
If we have an import procedure where we import relations from some
external source such as an XML file, it may be that we read a relation
that points to an object that does not yet exist as it is yet to be
imported. We provide a special ``TemporaryRelationValue`` for this
case. A ``TemporaryRelationValue`` just contains the path of what it
is pointing to, but does not resolve it yet. Let's use
``TemporaryRelationValue`` in a new object, creating a relation to
``a``:
.. code-block:: python
>>> from z3c.relationfield import TemporaryRelationValue
>>> root['d'] = Item()
>>> root['d'].rel = TemporaryRelationValue('a')
A modification event does not actually get this relation cataloged:
.. code-block:: python
>>> before = sorted(catalog.findRelations({'to_id': a_id}))
>>> notify(ObjectModifiedEvent(root['d']))
>>> after = sorted(catalog.findRelations({'to_id': a_id}))
>>> len(before) == len(after)
True
We will now convert all temporary relations on ``d`` to real ones:
.. code-block:: python
>>> from z3c.relationfield import realize_relations
>>> realize_relations(root['d'])
>>> notify(ObjectModifiedEvent(root['d']))
We can see the real relation object now:
.. code-block:: python
>>> root['d'].rel
<...RelationValue object at ...>
The relation will also now show up in the catalog:
.. code-block:: python
>>> after2 = sorted(catalog.findRelations({'to_id': a_id}))
>>> len(after2) > len(before)
True
Temporary relation values also work with ``RelationList`` objects:
.. code-block:: python
>>> root['multi_temp'] = MultiItem()
>>> root['multi_temp'].rel = [TemporaryRelationValue('a')]
Let's convert this to a real relation:
.. code-block:: python
>>> realize_relations(root['multi_temp'])
>>> notify(ObjectModifiedEvent(root['multi_temp']))
Again we can see the real relation object when we look at it:
.. code-block:: python
>>> root['multi_temp'].rel
[<...RelationValue object at ...>]
And we will now see this new relation appear in the catalog:
.. code-block:: python
>>> 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:
.. code-block:: python
>>> root['e'] = Item()
>>> root['e'].rel = TemporaryRelationValue('nonexistent')
Let's try realizing this relation:
.. code-block:: python
>>> realize_relations(root['e'])
We end up with a broken relation:
.. code-block:: python
>>> root['e'].rel.isBroken()
True
It's pointing to the nonexistent path:
.. code-block:: python
>>> root['e'].rel.to_path
'nonexistent'
Setting up a releation catalog
==============================
This package provides a RelationCatalog initialized with a set of indexes commonly useful for queries on RelationValue objects.
The default indexes are `from_id`, `to_id`, `from_attribute`, `from_interfaces_flattened` and `to_interfaces_flattened`.
Sometimes it is needed to define custom indexes or use less than the default ones.
The `zc.relationfield.index.RelationCatalog` class can be initialized with a list of dicts with keys `element` and `kwargs` to be passed to RelationCatalog `addValueIndex` method.
As `element` in general the attribute on the `IRelationValue` like `IRelationValue['from_id']` is expected.
However, if theres a subclass of `IRelationValue` is used with additional fields, those fields can be added here as indexes.
CHANGES
*******
2.0.0 (2024-11-30)
==================
- Revert: Consider RelationValue without source as broken.
[ksuess]
- Add support for Python 3.12, 3.13.
- Drop support for Python 3.7.
- Fix tests for Python 3.13.
[petschki]
1.1 (2023-08-17)
================
- Consider RelationValue without source as broken.
[ksuess]
1.0 (2023-02-22)
================
Breaking changes:
- Drop support for Python 2.7, 3.5, 3.6.
New features:
- Add support for Python 3.7, 3.8, 3.9, 3.10, 3.11.
0.9.0 (2019-09-15)
==================
New features:
- Provide IRelationBrokenEvent to be able to distinguish the event when
subscribing to IObjectModifiedEvent
[vangheem]
0.8.0 (2019-02-13)
==================
New features:
- Adresses `Still uses BTrees wrongly, screws up people changing Interfaces <https://github.com/zopefoundation/z3c.relationfield/issues/4>`_, allows third party software to define which indexes are used.
[jensens]
Bug fixes:
- Fix DeprecationWarnings in ``tests.py``.
[jensens]
0.7.1 (2018-11-08)
==================
- Python 3 compatibility: use the implementer decorator and fix ordering
[ale-rt]
- Python 3 compatibility: Make ``RelationValue`` hashable. [sallner]
- Renamed ``README.txt``to ``README.rst`` and ``CHANGES.txt`` to
``CHANGES.rst``.
[thet]
- Update buildout / travis config
[tomgross]
- Fix issue where relations are cleared on modify if they are not stored as
an class attribute. Usecase see https://github.com/plone/Products.CMFPlone/issues/2384
[tomgross]
0.7 (2015-03-13)
================
- Remove dependencies on zope.app.*
[davisagli]
0.6.3 (2014-04-15)
==================
* Remove dependency on grok.
[pbauer, jensens]
0.6.2 (2012-12-06)
==================
* Updated test setup and test to run with current versions of dependent
packages, thus running with Python 2.6, too.
* Added missing (test) dependencies.
* Rename __neq__ method to __ne__ since __neq__ is not the right builtin
name for != handlers.
0.6.1 (2009-10-11)
==================
* Fixes broken release.
0.6 (2009-10-11)
================
* Ensure that the value_type of a RelationList is not overwritten to be 'None'
when the field is constructed.
0.5 (2009-06-30)
================
* Move lxml and schema2xml dependencies to an [xml] extra so that people can
use this package without having to install lxml, which still causes issues
on some platforms. If z3c.schema2xml and lxml are not importable, the
relevant adapters will not be defined, but everything else will still work.
* Subscribe to IIntIdAddedEvent instead of IObjectAddedEvent to prevent
errors due to subscriber ordering.
0.4.3 (2009-06-04)
==================
* Add missing dependency for lxml.
0.4.2 (2009-04-22)
==================
* Prevent the event failures from failing when utilities are missing or when
objects do not implement IContained.
0.4.1 (2009-02-12)
==================
* Don't handle ``IObjectModified`` events for objects that do not yet
have a parent. There is no need to do so anyway, as these objects cannot
have outgoing relations indexed.
0.4 (2009-02-10)
================
* Introduce a ``RelationChoice`` field that behaves like
``schema.Choice`` but tracks relations. In combination with a source
(such as created by ``RelationSourceFactory`` provided by
``z3c.relationfieldui``) this can be used to create drop-down
selections for relations.
* Clarify the way comparing and sorting of ``RelationValue`` objects is
done in order to better support choice support.
0.3.2 (2009-01-21)
==================
* When a relation is broken, properly re-catalog things.
0.3.1 (2009-01-20)
==================
* Introduce sensible sort order for relations, based on a
``(from_attribute, from_path, to_path)`` tuple.
* Relations will now never compare to ``None``.
0.3 (2009-01-19)
================
* 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.
* The signature of the top-level function ``create_relation``
changed. It used to take the object to which the relation was to be
created, but should now get the path (in ``IObjectPath`` terms).
``create_relation`` will now create a broken relation object if the
path cannot be resolved.
0.2 (2009-01-08)
================
* Added support for ``RelationList`` fields. This allows one to
maintain a list of ``RelationValue`` objects that will be cataloged
like the regular ``Relation`` fields.
* Get rid of ``IRelationInfo`` adapter requirement. Just define a
``create_relation`` function that does the same work.
* When looking for relations on an object be more tolerant if those
cannot be found (just skip them) - this can happen when a schema is
changed.
0.1 (2008-12-05)
================
* Initial public release.
Download
********
Raw data
{
"_id": null,
"home_page": "https://github.com/zopefoundation/z3c.relationfield",
"name": "z3c.relationfield",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "relation field",
"author": "Martijn Faassen",
"author_email": "zope-dev@zope.dev",
"download_url": "https://files.pythonhosted.org/packages/95/d1/dddc25d210e7fd2b0cfdc0b9bb313c7b7c029fc158d0cc1f34278623b68b/z3c_relationfield-2.0.0.tar.gz",
"platform": null,
"description": "z3c.relationfield\n*****************\n\nIntroduction\n============\n\nThis package implements a new schema field Relation, and the\nRelationValue objects that store actual relations. It can index these\nrelations using the ``zc.relation`` infractructure, and using these\nindexes can efficiently answer questions about the relations.\n\nThe package `z3c.relationfieldui`_ in addition provides a widget to\nedit and display Relation fields.\n\n.. _`z3c.relationfieldui`: http://pypi.python.org/pypi/z3c.relationfieldui\n\nSetup\n=====\n\n``z3c.relationfield.Relation`` is a schema field that can be used to\nexpress relations. Let's define a schema IItem that uses a relation\nfield:\n\n.. code-block:: python\n\n >>> from z3c.relationfield import Relation\n >>> from zope.interface import Interface\n >>> class IItem(Interface):\n ... rel = Relation(title=u\"Relation\")\n\nWe also define a class ``Item`` that implements both ``IItem``\nand the special ``z3c.relationfield.interfaces.IHasRelations``\ninterface:\n\n.. code-block:: python\n\n >>> from z3c.relationfield.interfaces import IHasRelations\n >>> from persistent import Persistent\n >>> from zope.interface import implementer\n >>> @implementer(IItem, IHasRelations)\n ... class Item(Persistent):\n ...\n ... def __init__(self):\n ... self.rel = None\n\nThe ``IHasRelations`` marker interface is needed to let the relations\non ``Item`` be cataloged (when they are put in a container and removed\nfrom it, for instance). It is in fact a combination of\n``IHasIncomingRelations`` and ``IHasOutgoingRelations``, which is fine\nas we want items to support both.\n\nFinally we need a test application:\n\n.. code-block:: python\n\n >>> from zope.site.site import SiteManagerContainer\n >>> from zope.container.btree import BTreeContainer\n >>> class TestApp(SiteManagerContainer, BTreeContainer):\n ... pass\n\nWe set up the test application:\n\n.. code-block:: python\n\n >>> from ZODB.MappingStorage import DB\n >>> db = DB()\n >>> conn = db.open()\n >>> root = conn.root()['root'] = TestApp()\n >>> conn.add(root)\n\nWe make sure that this is the current site, so we can look up local\nutilities in it and so on. Normally this is done automatically by\nZope's traversal mechanism:\n\n.. code-block:: python\n\n >>> from zope.site.site import LocalSiteManager\n >>> root.setSiteManager(LocalSiteManager(root))\n >>> from zope.component.hooks import setSite\n >>> setSite(root)\n\nFor this site to work with ``z3c.relationship``, we need to set up two\nutilities. Firstly, an ``IIntIds`` that tracks unique ids for objects\nin the ZODB:\n\n.. code-block:: python\n\n >>> from zope.intid import IntIds\n >>> from zope.intid.interfaces import IIntIds\n >>> root['intids'] = intids = IntIds()\n >>> sm = root.getSiteManager()\n >>> sm.registerUtility(intids, provided=IIntIds)\n\nAnd secondly a relation catalog that actually indexes the relations:\n\n.. code-block:: python\n\n >>> from z3c.relationfield import RelationCatalog\n >>> from zc.relation.interfaces import ICatalog\n >>> root['catalog'] = catalog = RelationCatalog()\n >>> sm.registerUtility(catalog, provided=ICatalog)\n\nUsing the relation field\n========================\n\nWe'll add an item ``a`` to our application:\n\n.. code-block:: python\n\n >>> root['a'] = Item()\n\nAll items, including the one we just created, should have unique int\nids as this is required to link to them:\n\n.. code-block:: python\n\n >>> from zope import component\n >>> from zope.intid.interfaces import IIntIds\n >>> intids = component.getUtility(IIntIds)\n >>> a_id = intids.getId(root['a'])\n >>> a_id >= 0\n True\n\nThe relation is currently ``None``:\n\n.. code-block:: python\n\n >>> root['a'].rel is None\n True\n\nNow we can create an item ``b`` that links to item ``a`` (through its\nint id):\n\n.. code-block:: python\n\n >>> from z3c.relationfield import RelationValue\n >>> b = Item()\n >>> b.rel = RelationValue(a_id)\n\nWe now store the ``b`` object in a container, which will also set up\nits relation (as an ``IObjectAddedEvent`` will be fired):\n\n.. code-block:: python\n\n >>> root['b'] = b\n\nLet's examine the relation. First we'll check which attribute of the\npointing object ('b') this relation is pointing from:\n\n.. code-block:: python\n\n >>> root['b'].rel.from_attribute\n 'rel'\n\nWe can ask for the object it is pointing at:\n\n.. code-block:: python\n\n >>> to_object = root['b'].rel.to_object\n >>> to_object.__name__\n 'a'\n\nWe can also get the object that is doing the pointing; since we\nsupplied the ``IHasRelations`` interface, the event system took care\nof setting this:\n\n.. code-block:: python\n\n >>> from_object = root['b'].rel.from_object\n >>> from_object.__name__\n 'b'\n\nThis object is also known as the ``__parent__``; again the event\nsytem took care of setting this:\n\n.. code-block:: python\n\n >>> parent_object = root['b'].rel.__parent__\n >>> parent_object is from_object\n True\n\nThe relation also knows about the interfaces of both the pointing object\nand the object that is being pointed at:\n\n.. code-block:: python\n\n >>> from pprint import pprint\n >>> pprint(sorted(root['b'].rel.from_interfaces))\n [<InterfaceClass zope.location.interfaces.IContained>,\n <InterfaceClass z3c.relationfield.interfaces.IHasRelations>,\n <InterfaceClass builtins.IItem>,\n <InterfaceClass persistent.interfaces.IPersistent>]\n\n >>> pprint(sorted(root['b'].rel.to_interfaces))\n [<InterfaceClass zope.location.interfaces.IContained>,\n <InterfaceClass z3c.relationfield.interfaces.IHasRelations>,\n <InterfaceClass builtins.IItem>,\n <InterfaceClass persistent.interfaces.IPersistent>]\n\nWe can also get the interfaces in flattened form:\n\n.. code-block:: python\n\n >>> pprint(sorted(root['b'].rel.from_interfaces_flattened))\n [<InterfaceClass zope.location.interfaces.IContained>,\n <InterfaceClass z3c.relationfield.interfaces.IHasIncomingRelations>,\n <InterfaceClass z3c.relationfield.interfaces.IHasOutgoingRelations>,\n <InterfaceClass z3c.relationfield.interfaces.IHasRelations>,\n <InterfaceClass builtins.IItem>,\n <InterfaceClass zope.location.interfaces.ILocation>,\n <InterfaceClass persistent.interfaces.IPersistent>,\n <InterfaceClass zope.interface.Interface>]\n\n >>> pprint(sorted(root['b'].rel.to_interfaces_flattened))\n [<InterfaceClass zope.location.interfaces.IContained>,\n <InterfaceClass z3c.relationfield.interfaces.IHasIncomingRelations>,\n <InterfaceClass z3c.relationfield.interfaces.IHasOutgoingRelations>,\n <InterfaceClass z3c.relationfield.interfaces.IHasRelations>,\n <InterfaceClass builtins.IItem>,\n <InterfaceClass zope.location.interfaces.ILocation>,\n <InterfaceClass persistent.interfaces.IPersistent>,\n <InterfaceClass zope.interface.Interface>]\n\nPaths\n=====\n\nWe can also obtain the path of the relation (both from where it is\npointing as well as to where it is pointing). The path should be a\nhuman-readable reference to the object we are pointing at, suitable\nfor serialization. In order to work with paths, we first need to set\nup an ``IObjectPath`` utility.\n\nSince in this example we only place objects into a single flat root\ncontainer, the paths in this demonstration can be extremely simple:\njust the name of the object we point to. In more sophisticated\napplications a path would typically be a slash separated path, like\n``/foo/bar``:\n\n.. code-block:: python\n\n >>> from zope.interface import Interface\n >>> from zope.interface import implementer\n >>> from z3c.objpath.interfaces import IObjectPath\n\n\n >>> @implementer(IObjectPath)\n ... class ObjectPath(object):\n ...\n ... def path(self, obj):\n ... return obj.__name__\n ... def resolve(self, path):\n ... try:\n ... return root[path]\n ... except KeyError:\n ... raise ValueError(\"Cannot resolve path %s\" % path)\n\n >>> from zope.component import getGlobalSiteManager\n >>> gsm = getGlobalSiteManager()\n\n >>> op = ObjectPath()\n >>> gsm.registerUtility(op)\n\n\nAfter this, we can get the path of the object the relation points to:\n\n.. code-block:: python\n\n >>> root['b'].rel.to_path\n 'a'\n\nWe can also get the path of the object that is doing the pointing:\n\n.. code-block:: python\n\n >>> root['b'].rel.from_path\n 'b'\n\nComparing and sorting relations\n===============================\n\nLet's create a bunch of ``RelationValue`` objects and compare them:\n\n.. code-block:: python\n\n >>> rel_to_a = RelationValue(a_id)\n >>> b_id = intids.getId(root['b'])\n >>> rel_to_b = RelationValue(b_id)\n >>> rel_to_a == rel_to_b\n False\n\nRelations of course are equal to themselves:\n\n.. code-block:: python\n\n >>> rel_to_a == rel_to_a\n True\n\nA relation that is stored is equal to a relation that isn't stored yet:\n\n.. code-block:: python\n\n >>> root['b'].rel == rel_to_a\n True\n\nWe can also sort relations:\n\n.. code-block:: python\n\n >>> expected = [('', 'a'), ('', 'b'), ('b', 'a')]\n >>> observed = [(rel.from_path, rel.to_path) for rel in\n ... sorted([root['b'].rel, rel_to_a, rel_to_b])]\n >>> expected == observed\n True\n\n\nRelation queries\n================\n\nNow that we have set up and indexed a relationship between ``a`` and\n``b``, we can issue queries using the relation catalog. Let's first\nget the catalog:\n\n.. code-block:: python\n\n >>> from zc.relation.interfaces import ICatalog\n >>> catalog = component.getUtility(ICatalog)\n\nLet's ask the catalog about the relation from ``b`` to ``a``:\n\n.. code-block:: python\n\n >>> l = sorted(catalog.findRelations({'to_id': intids.getId(root['a'])}))\n >>> l\n [<...RelationValue object at ...>]\n\nWe look at this relation object again. We indeed go the right one:\n\n.. code-block:: python\n\n >>> rel = l[0]\n >>> rel.from_object.__name__\n 'b'\n >>> rel.to_object.__name__\n 'a'\n >>> rel.from_path\n 'b'\n >>> rel.to_path\n 'a'\n\nAsking for relations to ``b`` will result in an empty list, as no such\nrelations have been set up:\n\n.. code-block:: python\n\n >>> sorted(catalog.findRelations({'to_id': intids.getId(root['b'])}))\n []\n\nWe can also issue more specific queries, restricting it on the\nattribute used for the relation field and the interfaces provided by\nthe related objects. Here we look for all relations between ``b`` and\n``a`` that are stored in object attribute ``rel`` and are pointing\nfrom an object with interface ``IItem`` to another object with the\ninterface ``IItem``:\n\n.. code-block:: python\n\n >>> sorted(catalog.findRelations({\n ... 'to_id': intids.getId(root['a']),\n ... 'from_attribute': 'rel',\n ... 'from_interfaces_flattened': IItem,\n ... 'to_interfaces_flattened': IItem}))\n [<...RelationValue object at ...>]\n\nThere are no relations stored for another attribute:\n\n.. code-block:: python\n\n >>> sorted(catalog.findRelations({\n ... 'to_id': intids.getId(root['a']),\n ... 'from_attribute': 'foo'}))\n []\n\nThere are also no relations stored for a new interface we'll introduce\nhere:\n\n.. code-block:: python\n\n >>> class IFoo(IItem):\n ... pass\n\n >>> sorted(catalog.findRelations({\n ... 'to_id': intids.getId(root['a']),\n ... 'from_interfaces_flattened': IItem,\n ... 'to_interfaces_flattened': IFoo}))\n []\n\nChanging the relation\n=====================\n\nLet's create a new object ``c``:\n\n.. code-block:: python\n\n >>> root['c'] = Item()\n >>> c_id = intids.getId(root['c'])\n\nNothing points to ``c`` yet:\n\n.. code-block:: python\n\n >>> sorted(catalog.findRelations({'to_id': c_id}))\n []\n\nWe currently have a relation from ``b`` to ``a``:\n\n.. code-block:: python\n\n >>> sorted(catalog.findRelations({'to_id': intids.getId(root['a'])}))\n [<...RelationValue object at ...>]\n\nWe can change the relation to point at a new object ``c``:\n\n.. code-block:: python\n\n >>> root['b'].rel = RelationValue(c_id)\n\nWe need to send an ``IObjectModifiedEvent`` to let the catalog know we\nhave changed the relations:\n\n.. code-block:: python\n\n >>> from zope.event import notify\n >>> from zope.lifecycleevent import ObjectModifiedEvent\n >>> notify(ObjectModifiedEvent(root['b']))\n\nWe should find now a single relation from ``b`` to ``c``:\n\n.. code-block:: python\n\n >>> sorted(catalog.findRelations({'to_id': c_id}))\n [<...RelationValue object at ...>]\n\nThe relation to ``a`` should now be gone:\n\n.. code-block:: python\n\n >>> sorted(catalog.findRelations({'to_id': intids.getId(root['a'])}))\n []\n\n\nIf we store the relation in a non schema field it should persist\nthe ObjectModifiedEvent.\n\n.. code-block:: python\n\n >>> from z3c.relationfield.event import _setRelation\n >>> _setRelation(root['b'], 'my-fancy-relation', rel_to_a)\n >>> sorted(catalog.findRelations({'to_id': intids.getId(root['a'])}))\n [<...RelationValue object at ...>]\n\n >>> notify(ObjectModifiedEvent(root['b']))\n >>> rel = sorted(catalog.findRelations({'to_id': intids.getId(root['a'])}))\n >>> rel\n [<...RelationValue object at ...>]\n\n >>> catalog.unindex(rel[0])\n\nRemoving the relation\n=====================\n\nWe have a relation from ``b`` to ``c`` right now:\n\n.. code-block:: python\n\n >>> sorted(catalog.findRelations({'to_id': c_id}))\n [<...RelationValue object at ...>]\n\nWe can clean up an existing relation from ``b`` to ``c`` by setting it\nto ``None``:\n\n.. code-block:: python\n\n >>> root['b'].rel = None\n\nWe need to send an ``IObjectModifiedEvent`` to let the catalog know we\nhave changed the relations:\n\n.. code-block:: python\n\n >>> notify(ObjectModifiedEvent(root['b']))\n\nSetting the relation on ``b`` to ``None`` should remove that relation\nfrom the relation catalog, so we shouldn't be able to find it anymore:\n\n.. code-block:: python\n\n >>> sorted(catalog.findRelations({'to_id': intids.getId(root['c'])}))\n []\n\nLet's reestablish the removed relation:\n\n.. code-block:: python\n\n >>> root['b'].rel = RelationValue(c_id)\n >>> notify(ObjectModifiedEvent(root['b']))\n\n >>> sorted(catalog.findRelations({'to_id': c_id}))\n [<...RelationValue object at ...>]\n\n\nCopying an object with relations\n================================\n\nLet's copy an object with relations:\n\n.. code-block:: python\n\n >>> from zope.copypastemove.interfaces import IObjectCopier\n >>> IObjectCopier(root['b']).copyTo(root)\n 'b-2'\n >>> 'b-2' in root\n True\n\nTwo relations to ``c`` can now be found, one from the original, and\nthe other from the copy:\n\n.. code-block:: python\n\n >>> l = sorted(catalog.findRelations({'to_id': c_id}))\n >>> len(l)\n 2\n >>> l[0].from_path\n 'b'\n >>> l[1].from_path\n 'b-2'\n\n\nRelations are sortable\n======================\n\nRelations are sorted by default on a combination of the relation name,\nthe path of the object the relation is one and the path of the object\nthe relation is pointing to.\n\nLet's query all relations availble right now and sort them:\n\n.. code-block:: python\n\n >>> l = sorted(catalog.findRelations())\n >>> len(l)\n 2\n >>> l[0].from_attribute\n 'rel'\n >>> l[1].from_attribute\n 'rel'\n >>> l[0].from_path\n 'b'\n >>> l[1].from_path\n 'b-2'\n\n\nRemoving an object with relations\n=================================\n\nWe will remove ``b-2`` again. Its relation should automatically be remove\nfrom the catalog:\n\n.. code-block:: python\n\n >>> del root['b-2']\n >>> l = sorted(catalog.findRelations({'to_id': c_id}))\n >>> len(l)\n 1\n >>> l[0].from_path\n 'b'\n\n\nBreaking a relation\n===================\n\nWe have a relation from ``b`` to ``c`` right now:\n\n.. code-block:: python\n\n >>> sorted(catalog.findRelations({'to_id': c_id}))\n [<...RelationValue object at ...>]\n\nWe have no broken relations:\n\n.. code-block:: python\n\n >>> sorted(catalog.findRelations({'to_id': None}))\n []\n\nThe relation isn't broken:\n\n.. code-block:: python\n\n >>> b.rel.isBroken()\n False\n\nWe are now going to break this relation by removing ``c``:\n\n.. code-block:: python\n\n >>> del root['c']\n\nThe relation is broken now:\n\n.. code-block:: python\n\n >>> b.rel.isBroken()\n True\n\nThe original relation still has a ``to_path``:\n\n.. code-block:: python\n\n >>> b.rel.to_path\n 'c'\n\nIt's broken however as there is no ``to_object``:\n\n.. code-block:: python\n\n >>> b.rel.to_object is None\n True\n\nThe ``to_id`` is also gone:\n\n.. code-block:: python\n\n >>> b.rel.to_id is None\n True\n\nWe cannot find the broken relation in the catalog this way as it's not\npointing to ``c_id`` anymore:\n\n.. code-block:: python\n\n >>> sorted(catalog.findRelations({'to_id': c_id}))\n []\n\nWe can however find it by searching for relations that have a\n``to_id`` of ``None``:\n\n.. code-block:: python\n\n >>> sorted(catalog.findRelations({'to_id': None}))\n [<...RelationValue object at ...>]\n\nA broken relation isn't equal to ``None`` (this was a bug):\n\n.. code-block:: python\n\n >>> b.rel == None\n False\n\n\nRelationChoice\n==============\n\nA ``RelationChoice`` field is much like an ordinary ``Relation`` field\nbut can be used to render a special widget that allows a choice of\nselections.\n\nWe will first demonstrate a ``RelationChoice`` field has the same effect\nas a ``Relation`` field itself:\n\n.. code-block:: python\n\n >>> from z3c.relationfield import RelationChoice\n >>> class IChoiceItem(Interface):\n ... rel = RelationChoice(title=u\"Relation\", values=[])\n >>> @implementer(IChoiceItem, IHasRelations)\n ... class ChoiceItem(Persistent):\n ...\n ... def __init__(self):\n ... self.rel = None\n\nLet's create an object to point the relation to:\n\n.. code-block:: python\n\n >>> root['some_object'] = Item()\n >>> some_object_id = intids.getId(root['some_object'])\n\nAnd let's establish the relation:\n\n:.. code-block:: python\n\n >>> choice_item = ChoiceItem()\n >>> choice_item.rel = RelationValue(some_object_id)\n >>> root['choice_item'] = choice_item\n\nWe can query for this relation now:\n\n.. code-block:: python\n\n >>> l = sorted(catalog.findRelations({'to_id': some_object_id}))\n >>> l\n [<...RelationValue object at ...>]\n\nRelationList\n============\n\nLet's now experiment with the ``RelationList`` field which can be used\nto maintain a list of relations:\n\n.. code-block:: python\n\n >>> from z3c.relationfield import RelationList\n >>> class IMultiItem(Interface):\n ... rel = RelationList(title=u\"Relation\")\n\nWe also define a class ``MultiItem`` that implements both\n``IMultiItem`` and the special\n``z3c.relationfield.interfaces.IHasRelations`` interface:\n\n.. code-block:: python\n\n >>> @implementer(IMultiItem, IHasRelations)\n ... class MultiItem(Persistent):\n ...\n ... def __init__(self):\n ... self.rel = None\n\nWe set up a few object we can then create relations between:\n\n.. code-block:: python\n\n >>> root['multi1'] = MultiItem()\n >>> root['multi2'] = MultiItem()\n >>> root['multi3'] = MultiItem()\n\nLet's create a relation from ``multi1`` to both ``multi2`` and\n``multi3``:\n\n.. code-block:: python\n\n >>> multi1_id = intids.getId(root['multi1'])\n >>> multi2_id = intids.getId(root['multi2'])\n >>> multi3_id = intids.getId(root['multi3'])\n\n >>> root['multi1'].rel = [RelationValue(multi2_id),\n ... RelationValue(multi3_id)]\n\nWe need to notify that we modified the ObjectModifiedEvent\n\n.. code-block:: python\n\n >>> notify(ObjectModifiedEvent(root['multi1']))\n\nNow that this is set up, let's verify whether we can find the\nproper relations in in the catalog:\n\n.. code-block:: python\n\n >>> len(list(catalog.findRelations({'to_id': multi2_id})))\n 1\n >>> len(list(catalog.findRelations({'to_id': multi3_id})))\n 1\n >>> len(list(catalog.findRelations({'from_id': multi1_id})))\n 2\n\n\nTemporary relations\n===================\n\nIf we have an import procedure where we import relations from some\nexternal source such as an XML file, it may be that we read a relation\nthat points to an object that does not yet exist as it is yet to be\nimported. We provide a special ``TemporaryRelationValue`` for this\ncase. A ``TemporaryRelationValue`` just contains the path of what it\nis pointing to, but does not resolve it yet. Let's use\n``TemporaryRelationValue`` in a new object, creating a relation to\n``a``:\n\n.. code-block:: python\n\n >>> from z3c.relationfield import TemporaryRelationValue\n >>> root['d'] = Item()\n >>> root['d'].rel = TemporaryRelationValue('a')\n\nA modification event does not actually get this relation cataloged:\n\n.. code-block:: python\n\n >>> before = sorted(catalog.findRelations({'to_id': a_id}))\n >>> notify(ObjectModifiedEvent(root['d']))\n >>> after = sorted(catalog.findRelations({'to_id': a_id}))\n >>> len(before) == len(after)\n True\n\nWe will now convert all temporary relations on ``d`` to real ones:\n\n.. code-block:: python\n\n >>> from z3c.relationfield import realize_relations\n >>> realize_relations(root['d'])\n >>> notify(ObjectModifiedEvent(root['d']))\n\nWe can see the real relation object now:\n\n.. code-block:: python\n\n >>> root['d'].rel\n <...RelationValue object at ...>\n\nThe relation will also now show up in the catalog:\n\n.. code-block:: python\n\n >>> after2 = sorted(catalog.findRelations({'to_id': a_id}))\n >>> len(after2) > len(before)\n True\n\nTemporary relation values also work with ``RelationList`` objects:\n\n.. code-block:: python\n\n >>> root['multi_temp'] = MultiItem()\n >>> root['multi_temp'].rel = [TemporaryRelationValue('a')]\n\nLet's convert this to a real relation:\n\n.. code-block:: python\n\n >>> realize_relations(root['multi_temp'])\n >>> notify(ObjectModifiedEvent(root['multi_temp']))\n\nAgain we can see the real relation object when we look at it:\n\n.. code-block:: python\n\n >>> root['multi_temp'].rel\n [<...RelationValue object at ...>]\n\nAnd we will now see this new relation appear in the catalog:\n\n.. code-block:: python\n\n >>> after3 = sorted(catalog.findRelations({'to_id': a_id}))\n >>> len(after3) > len(after2)\n True\n\nBroken temporary relations\n==========================\n\nLet's create another temporary relation, this time a broken one that\ncannot be resolved:\n\n.. code-block:: python\n\n >>> root['e'] = Item()\n >>> root['e'].rel = TemporaryRelationValue('nonexistent')\n\nLet's try realizing this relation:\n\n.. code-block:: python\n\n >>> realize_relations(root['e'])\n\nWe end up with a broken relation:\n\n.. code-block:: python\n\n >>> root['e'].rel.isBroken()\n True\n\nIt's pointing to the nonexistent path:\n\n.. code-block:: python\n\n >>> root['e'].rel.to_path\n 'nonexistent'\n\nSetting up a releation catalog\n==============================\n\nThis package provides a RelationCatalog initialized with a set of indexes commonly useful for queries on RelationValue objects.\nThe default indexes are `from_id`, `to_id`, `from_attribute`, `from_interfaces_flattened` and `to_interfaces_flattened`.\n\nSometimes it is needed to define custom indexes or use less than the default ones.\nThe `zc.relationfield.index.RelationCatalog` class can be initialized with a list of dicts with keys `element` and `kwargs` to be passed to RelationCatalog `addValueIndex` method.\nAs `element` in general the attribute on the `IRelationValue` like `IRelationValue['from_id']` is expected.\nHowever, if theres a subclass of `IRelationValue` is used with additional fields, those fields can be added here as indexes.\n\nCHANGES\n*******\n\n2.0.0 (2024-11-30)\n==================\n\n- Revert: Consider RelationValue without source as broken.\n [ksuess]\n\n- Add support for Python 3.12, 3.13.\n\n- Drop support for Python 3.7.\n\n- Fix tests for Python 3.13.\n [petschki]\n\n1.1 (2023-08-17)\n================\n\n- Consider RelationValue without source as broken.\n [ksuess]\n\n\n1.0 (2023-02-22)\n================\n\nBreaking changes:\n\n- Drop support for Python 2.7, 3.5, 3.6.\n\nNew features:\n\n- Add support for Python 3.7, 3.8, 3.9, 3.10, 3.11.\n\n\n0.9.0 (2019-09-15)\n==================\n\nNew features:\n\n- Provide IRelationBrokenEvent to be able to distinguish the event when\n subscribing to IObjectModifiedEvent\n [vangheem]\n\n\n0.8.0 (2019-02-13)\n==================\n\nNew features:\n\n- Adresses `Still uses BTrees wrongly, screws up people changing Interfaces <https://github.com/zopefoundation/z3c.relationfield/issues/4>`_, allows third party software to define which indexes are used.\n [jensens]\n\nBug fixes:\n\n- Fix DeprecationWarnings in ``tests.py``.\n [jensens]\n\n\n0.7.1 (2018-11-08)\n==================\n\n- Python 3 compatibility: use the implementer decorator and fix ordering\n [ale-rt]\n\n- Python 3 compatibility: Make ``RelationValue`` hashable. [sallner]\n\n- Renamed ``README.txt``to ``README.rst`` and ``CHANGES.txt`` to\n ``CHANGES.rst``.\n [thet]\n\n- Update buildout / travis config\n [tomgross]\n\n- Fix issue where relations are cleared on modify if they are not stored as\n an class attribute. Usecase see https://github.com/plone/Products.CMFPlone/issues/2384\n [tomgross]\n\n0.7 (2015-03-13)\n================\n\n- Remove dependencies on zope.app.*\n [davisagli]\n\n\n0.6.3 (2014-04-15)\n==================\n\n* Remove dependency on grok.\n [pbauer, jensens]\n\n\n0.6.2 (2012-12-06)\n==================\n\n* Updated test setup and test to run with current versions of dependent\n packages, thus running with Python 2.6, too.\n\n* Added missing (test) dependencies.\n\n* Rename __neq__ method to __ne__ since __neq__ is not the right builtin\n name for != handlers.\n\n\n0.6.1 (2009-10-11)\n==================\n\n* Fixes broken release.\n\n0.6 (2009-10-11)\n================\n\n* Ensure that the value_type of a RelationList is not overwritten to be 'None'\n when the field is constructed.\n\n0.5 (2009-06-30)\n================\n\n* Move lxml and schema2xml dependencies to an [xml] extra so that people can\n use this package without having to install lxml, which still causes issues\n on some platforms. If z3c.schema2xml and lxml are not importable, the\n relevant adapters will not be defined, but everything else will still work.\n\n* Subscribe to IIntIdAddedEvent instead of IObjectAddedEvent to prevent\n errors due to subscriber ordering.\n\n\n0.4.3 (2009-06-04)\n==================\n\n* Add missing dependency for lxml.\n\n\n0.4.2 (2009-04-22)\n==================\n\n* Prevent the event failures from failing when utilities are missing or when\n objects do not implement IContained.\n\n\n0.4.1 (2009-02-12)\n==================\n\n* Don't handle ``IObjectModified`` events for objects that do not yet\n have a parent. There is no need to do so anyway, as these objects cannot\n have outgoing relations indexed.\n\n0.4 (2009-02-10)\n================\n\n* Introduce a ``RelationChoice`` field that behaves like\n ``schema.Choice`` but tracks relations. In combination with a source\n (such as created by ``RelationSourceFactory`` provided by\n ``z3c.relationfieldui``) this can be used to create drop-down\n selections for relations.\n\n* Clarify the way comparing and sorting of ``RelationValue`` objects is\n done in order to better support choice support.\n\n0.3.2 (2009-01-21)\n==================\n\n* When a relation is broken, properly re-catalog things.\n\n0.3.1 (2009-01-20)\n==================\n\n* Introduce sensible sort order for relations, based on a\n ``(from_attribute, from_path, to_path)`` tuple.\n\n* Relations will now never compare to ``None``.\n\n0.3 (2009-01-19)\n================\n\n* Introduce two new interfaces: ``IHasOutgoingRelations`` and\n ``IHasIncomingRelations``. ``IHasOutgoingRelations`` should be provided\n by objects that actually have relations set on them, so that\n they can be properly cataloged. ``IHasIncomingRelations`` should be\n set on objects that can be related to, so that broken relations\n can be properly tracked. ``IHasRelations`` now extends both,\n so if you provide those on your object you have an object that can\n have both outgoing as well as incoming relations.\n\n* Improve broken relations support. When you now break a relation (by\n removing the relation target), ``to_id`` and ``to_object`` become\n ``None``. ``to_path`` however will remain the path that the relation\n last pointed to. ``TemporaryRelation`` objects that when realized\n are broken relations can also be created.\n\n You can also for broken status by calling ``isBroken`` on a\n relation.\n\n* The signature of the top-level function ``create_relation``\n changed. It used to take the object to which the relation was to be\n created, but should now get the path (in ``IObjectPath`` terms).\n ``create_relation`` will now create a broken relation object if the\n path cannot be resolved.\n\n0.2 (2009-01-08)\n================\n\n* Added support for ``RelationList`` fields. This allows one to\n maintain a list of ``RelationValue`` objects that will be cataloged\n like the regular ``Relation`` fields.\n\n* Get rid of ``IRelationInfo`` adapter requirement. Just define a\n ``create_relation`` function that does the same work.\n\n* When looking for relations on an object be more tolerant if those\n cannot be found (just skip them) - this can happen when a schema is\n changed.\n\n0.1 (2008-12-05)\n================\n\n* Initial public release.\n\nDownload\n********\n",
"bugtrack_url": null,
"license": "ZPL 2.1",
"summary": "A relation field framework for Zope 3.",
"version": "2.0.0",
"project_urls": {
"Homepage": "https://github.com/zopefoundation/z3c.relationfield"
},
"split_keywords": [
"relation",
"field"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "64166b91ee992e4a01fe655fdbcdbf0a1d53cb084b5dedd49b1a9eee3eb5c824",
"md5": "4ad737e094c19ce2334675690b7164d6",
"sha256": "7a606fe2b0f5ca68819b1041d0cca407cb64be50fbab8bb16467889e91a16fca"
},
"downloads": -1,
"filename": "z3c.relationfield-2.0.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "4ad737e094c19ce2334675690b7164d6",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 26154,
"upload_time": "2024-11-30T03:02:21",
"upload_time_iso_8601": "2024-11-30T03:02:21.662766Z",
"url": "https://files.pythonhosted.org/packages/64/16/6b91ee992e4a01fe655fdbcdbf0a1d53cb084b5dedd49b1a9eee3eb5c824/z3c.relationfield-2.0.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "95d1dddc25d210e7fd2b0cfdc0b9bb313c7b7c029fc158d0cc1f34278623b68b",
"md5": "f7e847da8181f14312a14ffbdfb96eeb",
"sha256": "3c72aa77d9720c4b9b2ffb210e95d520c6b44118ebb546da7422f2bcff321ed2"
},
"downloads": -1,
"filename": "z3c_relationfield-2.0.0.tar.gz",
"has_sig": false,
"md5_digest": "f7e847da8181f14312a14ffbdfb96eeb",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 33217,
"upload_time": "2024-11-30T03:02:24",
"upload_time_iso_8601": "2024-11-30T03:02:24.138405Z",
"url": "https://files.pythonhosted.org/packages/95/d1/dddc25d210e7fd2b0cfdc0b9bb313c7b7c029fc158d0cc1f34278623b68b/z3c_relationfield-2.0.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-11-30 03:02:24",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "zopefoundation",
"github_project": "z3c.relationfield",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"tox": true,
"lcname": "z3c.relationfield"
}