[Checkins] SVN: lovely.relation/ Initial public version of lovely.relation

Juergen Kartnaller juergen at kartnaller.at
Mon Sep 3 08:13:16 EDT 2007


Log message for revision 79445:
  Initial public version of lovely.relation
  

Changed:
  A   lovely.relation/branches/
  A   lovely.relation/tags/
  A   lovely.relation/trunk/
  A   lovely.relation/trunk/CHANGES.txt
  A   lovely.relation/trunk/bootstrap.py
  A   lovely.relation/trunk/buildout.cfg
  A   lovely.relation/trunk/setup.py
  A   lovely.relation/trunk/src/
  A   lovely.relation/trunk/src/lovely/
  A   lovely.relation/trunk/src/lovely/__init__.py
  A   lovely.relation/trunk/src/lovely/relation/
  A   lovely.relation/trunk/src/lovely/relation/README.txt
  A   lovely.relation/trunk/src/lovely/relation/__init__.py
  A   lovely.relation/trunk/src/lovely/relation/app.py
  A   lovely.relation/trunk/src/lovely/relation/configurator.py
  A   lovely.relation/trunk/src/lovely/relation/configure.zcml
  A   lovely.relation/trunk/src/lovely/relation/demo/
  A   lovely.relation/trunk/src/lovely/relation/demo/README.txt
  A   lovely.relation/trunk/src/lovely/relation/demo/__init__.py
  A   lovely.relation/trunk/src/lovely/relation/demo/app.py
  A   lovely.relation/trunk/src/lovely/relation/demo/browser.py
  A   lovely.relation/trunk/src/lovely/relation/demo/configure.zcml
  A   lovely.relation/trunk/src/lovely/relation/demo/ftesting.zcml
  A   lovely.relation/trunk/src/lovely/relation/demo/interfaces.py
  A   lovely.relation/trunk/src/lovely/relation/demo/tests.py
  A   lovely.relation/trunk/src/lovely/relation/demo/vocabulary.py
  A   lovely.relation/trunk/src/lovely/relation/event.py
  A   lovely.relation/trunk/src/lovely/relation/i18n.py
  A   lovely.relation/trunk/src/lovely/relation/interfaces.py
  A   lovely.relation/trunk/src/lovely/relation/lovely.relation-configure.zcml
  A   lovely.relation/trunk/src/lovely/relation/o2o.zcml
  A   lovely.relation/trunk/src/lovely/relation/property.py
  A   lovely.relation/trunk/src/lovely/relation/property.txt
  A   lovely.relation/trunk/src/lovely/relation/testing.py
  A   lovely.relation/trunk/src/lovely/relation/tests.py

-=-
Added: lovely.relation/trunk/CHANGES.txt
===================================================================
--- lovely.relation/trunk/CHANGES.txt	                        (rev 0)
+++ lovely.relation/trunk/CHANGES.txt	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,91 @@
+===========================
+Changes for lovely.relation
+===========================
+
+After
+=====
+
+2007/09/03 1.0.0
+================
+
+- initial version on zope.org
+
+
+2007/08/27 0.3.0
+================
+
+- separated all o2o configuration into it's own configuration file. This is
+  done because of the event handler which adds a lot of overhead to projects
+  not using o2o.
+
+Existing projects need to include the file "o2o.zcml" in their application
+configuration !
+
+
+2007/08/27 0.2.3
+================
+
+- added missing zcml configuration for the event handler
+
+
+2007/08/27 0.2.2
+================
+
+- none alpha version of the package
+
+
+2007/08/22 0.2.2a4
+==================
+
+- fixed exception handling of readonly relation properties
+
+
+2007/08/20 0.2.2.a3
+===================
+
+- remove relations from O2O relationship containers on IntIdRemovedEvent
+
+
+2007/06/11 0.2.2.a2
+===================
+
+- extended property.txt to show some more usefull use cases
+
+- added a setUpPlugIns for testing setups
+
+
+2007/06/11 0.2.2.a1
+===================
+
+- access to the relation object using PropertyRelationManager
+
+- relation properties can now be used to provide uids instead of the objects.
+
+- provide a dump method which allows the use of intids for queries instead of
+  objects. This is now the default for all newly created relation containers.
+
+- set dependency to zc.relationship to <= 1.999
+
+- fixed a bug in findRelationTokens
+
+
+2007/06/11 0.2.1.a1
+===================
+
+- fix bug, deletion of relations in properties raised an exception
+
+
+2007/06/11 0.2a2
+================
+
+- fix bug, O2O util did not use the right class for relations
+
+2007/06/08 0.2a1
+================
+
+- property implementation for relations, see property.txt and
+  demo/README.txt
+
+
+
+


Property changes on: lovely.relation/trunk/CHANGES.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/bootstrap.py
===================================================================
--- lovely.relation/trunk/bootstrap.py	                        (rev 0)
+++ lovely.relation/trunk/bootstrap.py	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,56 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id$
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+try:
+    import pkg_resources
+except ImportError:
+    ez = {}
+    exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                         ).read() in ez
+    ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+    import pkg_resources
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+if sys.platform == 'win32':
+    cmd = '"%s"' % cmd # work around spawn lamosity on windows
+
+ws = pkg_resources.working_set
+assert os.spawnle(
+    os.P_WAIT, sys.executable, sys.executable,
+    '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
+    dict(os.environ,
+         PYTHONPATH=
+         ws.find(pkg_resources.Requirement.parse('setuptools')).location
+         ),
+    ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)
+


Property changes on: lovely.relation/trunk/bootstrap.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/buildout.cfg
===================================================================
--- lovely.relation/trunk/buildout.cfg	                        (rev 0)
+++ lovely.relation/trunk/buildout.cfg	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,9 @@
+[buildout]
+develop = .
+parts = test
+
+[test]
+recipe = zc.recipe.testrunner
+defaults = ['--tests-pattern', '^f?tests$']
+eggs = lovely.relation [test]
+

Added: lovely.relation/trunk/setup.py
===================================================================
--- lovely.relation/trunk/setup.py	                        (rev 0)
+++ lovely.relation/trunk/setup.py	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,53 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Setup for lovely.relation package
+
+$Id$
+"""
+
+import os
+
+from setuptools import setup, find_packages, Extension
+
+setup(name='lovely.relation',
+      version='1.0.0',
+      url='http://svn.zope.org/lovely.relation',
+      license='ZPL 2.1',
+      description='Lovely Relation Packages for Zope3',
+      author='Lovely Systems',
+      author_email='office at lovelysystems.com',
+      packages=find_packages('src'),
+      package_dir = {'': 'src'},
+      namespace_packages=['lovely',],
+      extras_require=dict(test=['zope.app.testing',
+                                'zope.app.intid',
+                                'z3c.testing',
+                                'zope.testbrowser',
+                                'zope.app.zcmlfiles',
+                                'zope.app.securitypolicy',
+                                ]),
+      install_requires=['setuptools',
+                        'ZODB3',
+                        'z3c.configurator',
+                        'zc.relationship <= 1.999',
+                        'zope.app.container',
+                        'zope.i18nmessageid',
+                        'zope.index', # needed by zc.relationship, but
+                                      # no dep
+                        'zope.schema',
+                        'zope.security',
+                        ],
+      include_package_data = True,
+      zip_safe = False,
+      )


Property changes on: lovely.relation/trunk/setup.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/__init__.py
===================================================================
--- lovely.relation/trunk/src/lovely/__init__.py	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/__init__.py	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,8 @@
+# this is a namespace package
+try:
+    import pkg_resources
+    pkg_resources.declare_namespace(__name__)
+except ImportError:
+    import pkgutil
+    __path__ = pkgutil.extend_path(__path__, __name__)
+


Property changes on: lovely.relation/trunk/src/lovely/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/README.txt
===================================================================
--- lovely.relation/trunk/src/lovely/relation/README.txt	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/README.txt	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,427 @@
+=========
+Relations
+=========
+
+This package provides functionality to realize relations using the
+zc.relationship package.
+
+  >>> from zope import component
+  >>> from zope.app.intid.interfaces import IIntIds
+  >>> intids = component.getUtility(IIntIds)
+
+
+Relation Type
+-------------
+
+A relation type define the relation.
+
+  >>> from lovely.relation.app import RelationType
+  >>> relType = RelationType(u'my targets')
+  >>> relType
+  <RelationType u'my targets'>
+
+
+Relationship
+------------
+
+By using relationships we can connect targets.
+
+  >>> from lovely.relation.app import Relationship
+
+First we need something to connect.
+
+  >>> class Source(object):
+  ...     def __init__(self, name):
+  ...         self.name = name
+  ...     def __repr__(self):
+  ...         return '<%s %r>' % (self.__class__.__name__, self.name)
+
+  >>> class Target(object):
+  ...     def __init__(self, name):
+  ...         self.name = name
+  ...     def __repr__(self):
+  ...         return '<%s %r>' % (self.__class__.__name__, self.name)
+
+  >>> source = Source('s1')
+  >>> relationship = Relationship(source, [relType], [])
+  >>> relationship.sources
+  <Source 's1'>
+
+  >>> relationship.relations
+  [<RelationType u'my targets'>]
+
+  >>> [o for o in relationship.targets]
+  []
+
+Now for the more interesting part of relations we need a relation container
+which holds all relations and allows queries.
+
+  >>> from lovely.relation.app import Relations
+  >>> relations = Relations()
+  >>> relations.add(relationship)
+
+  >>> [o for o in relations.findTargets(source)]
+  []
+
+The objects involved in a relation are registered in the IntIds utility.
+
+  >>> intids = component.getUtility(IIntIds)
+  >>> sourceId = intids.getId(source)
+  >>> sourceId is None
+  False
+
+Let us add an target to the relationship.
+
+  >>> target = Target('o1 of s1')
+  >>> relationship.targets = [target]
+  >>> targetId = intids.getId(target)
+  >>> targetId is None
+  False
+
+Now we can lookup the targets again.
+
+  >>> [o for o in relations.findTargets(source)]
+  [<Target 'o1 of s1'>]
+
+  >>> [o for o in relations.findTargetTokens(source)] == [intids.getId(target)]
+  True
+
+It is also possible to use intids for the queries.
+
+  >>> [o for o in relations.findTargets(sourceId)]
+  [<Target 'o1 of s1'>]
+
+The above lookup returns all targets of all existing relations. If we want to
+see only the targets of a specific relation then we need to provide the
+relation.
+
+  >>> [o for o in relations.findTargets(source, relType)]
+  [<Target 'o1 of s1'>]
+  >>> [o for o in relations.findTargets(sourceId, relType)]
+  [<Target 'o1 of s1'>]
+
+We can also ask the other way around.
+
+  >>> [s for s in relations.findSources(target, relType)]
+  [<Source 's1'>]
+  >>> [s for s in relations.findSources(targetId, relType)]
+  [<Source 's1'>]
+
+  >>> [s for s in relations.findSourceTokens(target, relType)] == [intids.getId(source)]
+  True
+
+We can also ask for all target of a relation without specifying the source.
+
+  >>> list(relations.findRelationTargets(relType))
+  [<Target 'o1 of s1'>]
+
+  >>> list(relations.findRelationTargetTokens(relType)) == [intids.getId(target)]
+  True
+
+And the same for sources.
+
+  >>> list(relations.findRelationSources(relType))
+  [<Source 's1'>]
+
+  >>> list(relations.findRelationSourceTokens(relType)) == [intids.getId(source)]
+  True
+
+Now lets create new targets and a new relationship.
+
+  >>> s2 = Source('s2')
+  >>> o2 = Target('o2 of s2')
+  >>> r2 = Relationship(s2, [relType], [target, o2])
+  >>> relations.add(r2)
+
+  >>> sorted([s for s in relations.findSources(target, relType)],
+  ...        key=lambda x:x.name)
+  [<Source 's1'>, <Source 's2'>]
+
+  >>> list(relations.findRelationTargets(relType))
+  [<Target 'o1 of s1'>, <Target 'o2 of s2'>]
+
+  >>> list(relations.findRelationSources(relType))
+  [<Source 's1'>, <Source 's2'>]
+
+
+Relation Types
+--------------
+
+Relation types can be providd to Realtions via a RealationTypes container. The
+container can then be registered as a utility.
+
+  >>> from lovely.relation.interfaces import IRelationTypes
+  >>> from lovely.relation.app import RelationTypes
+  >>> types = RelationTypes()
+  >>> types
+  <RelationTypes None>
+
+  >>> from zope import component
+  >>> component.provideUtility(types, IRelationTypes)
+
+Now we can put our relations into this container and use them by name.
+
+  >>> types['my targets'] = relType
+  >>> types['my targets']
+  <RelationType u'my targets'>
+
+Now we can use the relation name to lookup for reltated targets.
+
+  >>> sorted([s for s in relations.findSources(target, 'my targets')],
+  ...        key=lambda x:x.name)
+  [<Source 's1'>, <Source 's2'>]
+
+
+More Targets
+------------
+
+  >>> targets = []
+  >>> for i in range(1000):
+  ...     targets.append(Target('o%i'%i))
+  >>> for i in range(5):
+  ...     s = Source('s%i'%i)
+  ...     r = Relationship(s, [relType], targets)
+  ...     relations.add(r)
+
+  >>> sorted([s for s in relations.findSources(targets[44], 'my targets')],
+  ...        key=lambda x:x.name)
+  [<Source 's0'>, <Source 's1'>, <Source 's2'>, <Source 's3'>, <Source 's4'>]
+
+
+Optimized Relationship for One To Many Relation
+-----------------------------------------------
+
+A predefined one to many relationship using a btree to store and retrieve the
+many relation.
+
+  >>> from lovely.relation.app import OneToManyRelationship
+  >>> otmSource = Source(u'otm source')
+  >>> relType = RelationType(u'otm relation')
+  >>> types[u'otm relation'] = relType
+  >>> otm = OneToManyRelationship(otmSource, [relType])
+  >>> otm.sources
+  <Source u'otm source'>
+
+  >>> otm.relations
+  [<RelationType u'otm relation'>]
+
+  >>> [o for o in otm.targets]
+  []
+
+The one to many relationship provides an extended interface.
+
+  >>> from lovely.relation.interfaces import IOneToManyRelationship
+  >>> IOneToManyRelationship.providedBy(otm)
+  True
+
+This interface allows us to add and remove targets.
+
+  >>> otmTarget = Target(u'otm obj 1')
+  >>> otm.add(otmTarget)
+  >>> [o for o in otm.targets]
+  [<Target u'otm obj 1'>]
+
+We put the relationship into our relations container.
+
+  >>> relations.add(otm)
+  >>> sorted([s for s in relations.findSources(otmTarget, 'otm relation')])
+  [<Source u'otm source'>]
+
+  >>> otm.remove(otmTarget)
+  >>> [o for o in otm.targets]
+  []
+
+
+Access To Relationships
+-----------------------
+
+  >>> target44 = targets[44]
+  >>> targetRelations = list(relations.findTargetRelationships(target44))
+  >>> len(targetRelations)
+  5
+
+  >>> source = targetRelations[0].sources
+  >>> source
+  <Source 's0'>
+
+  >>> sourceRelations = list(relations.findSourceRelationships(source))
+  >>> len(sourceRelations)
+  1
+
+  >>> len(sourceRelations[0].targets)
+  1000
+
+  >>> target44 in sourceRelations[0].targets
+  True
+
+
+More Relations
+--------------
+
+A relationship can belong to more than just one relation type. First we need a
+new relation type. This time we do not add the type to the relation types
+container.
+
+  >>> otherRelType = RelationType(u'my other targets')
+  >>> otherRelType
+  <RelationType u'my other targets'>
+
+  >>> rel = targetRelations[0]
+  >>> rel.addRelation(otherRelType)
+
+Now we lookup the source for relation 'my targets'.
+
+  >>> sorted([s for s in relations.findSources(target44, 'my targets')],
+  ...        key=lambda x:x.name)
+  [<Source 's0'>, <Source 's1'>, <Source 's2'>, <Source 's3'>, <Source 's4'>]
+
+Now we can also lookup the sources for the new relation.
+
+  >>> sorted([s for s in relations.findSources(target44, 'my other targets')],
+  ...        key=lambda x:x.name)
+  Traceback (most recent call last):
+  ...
+  KeyError: 'my other targets'
+
+We get a KeyError because our new source is not stored in the relation types
+container, but if we use the relation type target we get the result.
+
+  >>> sorted([s for s in relations.findSources(target44, otherRelType)],
+  ...        key=lambda x:x.name)
+  [<Source 's0'>]
+
+And let's remove the relation.
+
+  >>> rel.removeRelation(otherRelType)
+  >>> sorted([s for s in relations.findSources(target44, otherRelType)],
+  ...        key=lambda x:x.name)
+  []
+
+
+More Relation Types
+-------------------
+
+Subclasses of Relationship and Relations can control which container relation
+types are looked up in by overriding the `relationtypes` property.
+
+  >>> from zope import interface
+  >>> from zope import component
+  >>> from lovely.relation.app import OneToOneRelationship
+  >>> from lovely.relation.app import OneToOneRelationships
+
+  >>> class IMyTypes(interface.Interface):
+  ...     pass
+
+  >>> class MyTypes(RelationTypes):
+  ...     interface.implements(IMyTypes)
+
+  >>> mytypes = MyTypes()
+  >>> mytypes[u'foo'] = RelationType(u'foo')
+  >>> mytypes[u'bar'] = RelationType(u'bar')
+  >>> mytypes[u'baz'] = RelationType(u'baz')
+
+Note that we don't need to register the utility for IRelationTypes
+
+  >>> component.provideUtility(mytypes, IMyTypes)
+
+  >>> class MyRelationship(OneToOneRelationship):
+  ...     @property
+  ...     def relationtypes(self):
+  ...         return component.getUtility(IMyTypes)
+
+  >>> class MyRelationships(OneToOneRelationships):
+  ...     @property
+  ...     def relationtypes(self):
+  ...         return component.getUtility(IMyTypes)
+
+Check Relationship
+
+  >>> myrelationship = MyRelationship(None, [u'foo'], None)
+  >>> myrelationship.relations
+  [<RelationType u'foo'>]
+
+  >>> myrelationship = MyRelationship(None, [u'bar', u'baz'], None)
+  >>> myrelationship.relations
+  [<RelationType u'bar'>, <RelationType u'baz'>]
+
+Check relationship container
+
+  >>> item1 = Source(u'Fred')
+  >>> item2 = Target(u'Barney')
+
+  >>> myrelations = MyRelationships()
+  >>> myrelationship = MyRelationship(item1, [u'foo'], item2)
+  >>> myrelations.add(myrelationship)
+
+Find sources
+
+  >>> [o for o in myrelations.findSources(item2)]
+  [<Source u'Fred'>]
+
+  >>> [o for o in myrelations.findSources(item2, relation=u'foo')]
+  [<Source u'Fred'>]
+
+  >>> [o for o in myrelations.findSources(item2, relation=u'bar')]
+  []
+
+Find targets
+
+  >>> [o for o in myrelations.findTargets(item1)]
+  [<Target u'Barney'>]
+
+  >>> [o for o in myrelations.findTargets(item1, relation=u'foo')]
+  [<Target u'Barney'>]
+
+  >>> [o for o in myrelations.findTargets(item1, relation=u'bar')]
+  []
+
+Find relationships
+
+  >>> [o for o in myrelations.findSourceRelationships(item1)]
+  [<MyRelationship ...>]
+
+  >>> [o for o in myrelations.findSourceRelationships(item1, relation=u'foo')]
+  [<MyRelationship ...>]
+
+  >>> [o for o in myrelations.findSourceRelationships(item1, relation=u'bar')]
+  []
+
+  >>> [o for o in myrelations.findTargetRelationships(item2)]
+  [<MyRelationship ...>]
+
+  >>> [o for o in myrelations.findTargetRelationships(item2, relation=u'foo')]
+  [<MyRelationship ...>]
+
+  >>> [o for o in myrelations.findTargetRelationships(item2, relation=u'bar')]
+  []
+
+Configurator
+------------
+
+There is also a configurator implemented for site objects which
+registers a IO2OStringTypeRelationships utility with a given name. The
+name is optional.
+
+  >>> from lovely.relation import configurator
+  >>> util = configurator.SetUpO2OStringTypeRelationships(root)
+  >>> util({'name':'myRelations'})
+  >>> root.getSiteManager()['default']['o2oStringTypeRelationships_myRelations']
+  <O2OStringTypeRelationships u'o2oStringTypeRelationships_myRelations'>
+
+We can run it twice, so it does nothing.
+
+  >>> util({'name':'myRelations'})
+
+We also have a method for testing which is doing the setup.
+
+  >>> from lovely.relation.testing import setUpPlugins
+  >>> setUpPlugins()
+
+An adapter has been registered.
+
+  >>> from z3c.configurator.interfaces import IConfigurationPlugin
+  >>> component.getAdapter(root,
+  ...                      IConfigurationPlugin,
+  ...                      name="lovely.relation.o2oStringTypeRelations")
+  <lovely.relation.configurator.SetUpO2OStringTypeRelationships object at ...>
+


Property changes on: lovely.relation/trunk/src/lovely/relation/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/__init__.py
===================================================================
--- lovely.relation/trunk/src/lovely/relation/__init__.py	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/__init__.py	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1 @@
+# Package


Property changes on: lovely.relation/trunk/src/lovely/relation/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/app.py
===================================================================
--- lovely.relation/trunk/src/lovely/relation/app.py	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/app.py	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,467 @@
+##############################################################################
+#
+# Copyright (c) 2006-2007 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Base implementations for relationship.
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+import random
+import persistent
+from persistent.list import PersistentList
+
+from zope import interface
+from zope import component
+
+from zope.schema.fieldproperty import FieldProperty
+from zope.cachedescriptors.property import CachedProperty
+
+from zope.app.container.contained import Contained
+from zope.app.container import btree
+from BTrees import OIBTree
+from zc.relationship import index
+from zope.security.proxy import removeSecurityProxy
+from lovely.relation.interfaces import (IRelationship,
+                                        IRelations,
+                                        IRelationTypes,
+                                        IRelationType,
+                                        IRelationTypeLookup,
+                                        IOneToOneRelationship,
+                                        IOneToOneRelationships,
+                                        IOneToManyRelationship,
+                                        IOneToManyRelationships,
+                                        IO2OStringTypeRelationship,
+                                        IO2OStringTypeRelationships,
+                                        )
+
+
+class RelationTypeLookup(object):
+    interface.implements(IRelationTypeLookup)
+
+    # Give subclasses the opportunity to override the
+    # relation types utility used for lookups.
+
+    @CachedProperty
+    def relationtypes(self):
+        # BBB: This should *really* use getUtility
+        return component.queryUtility(IRelationTypes)
+
+    def _lookup(self, relation):
+        if isinstance(relation, basestring):
+            if self.relationtypes is not None:
+                return self.relationtypes[relation]
+        return relation
+
+
+class Relationship(Contained, persistent.Persistent, RelationTypeLookup):
+    interface.implements(IRelationship)
+
+    def __init__(self, sources, relations, targets):
+        self._sources = removeSecurityProxy(sources)
+        self._targets = removeSecurityProxy(targets)
+        rels = PersistentList()
+        for relation in relations:
+            rels.append(self._lookup(relation))
+        self._relations = removeSecurityProxy(rels)
+        super(Relationship, self).__init__()
+
+    @apply
+    def sources():
+        def get(self):
+            return self._sources
+        def set(self, value):
+            value = removeSecurityProxy(value)
+            self._sources = value
+            if IRelations.providedBy(self.__parent__):
+                self.__parent__.reindex(self)
+        return property(get, set)
+
+    @apply
+    def targets():
+        def get(self):
+            return self._targets
+        def set(self, value):
+            value = removeSecurityProxy(value)
+            self._targets = value
+            if IRelations.providedBy(self.__parent__):
+                self.__parent__.reindex(self)
+        return property(get, set)
+
+    @apply
+    def relations():
+        def get(self):
+            return self._relations
+        def set(self, value):
+            self._relations = value
+            if IRelations.providedBy(self.__parent__):
+                self.__parent__.reindex(self)
+        return property(get, set)
+
+    def addRelation(self, relation):
+        if relation not in self.relations:
+            self.relations.append(relation)
+            if IRelations.providedBy(self.__parent__):
+                self.__parent__.reindex(self)
+
+    def removeRelation(self, relation):
+        if relation in self.relations:
+            self.relations.remove(relation)
+            if IRelations.providedBy(self.__parent__):
+                self.__parent__.reindex(self)
+
+    def __repr__(self):
+        return '<%s %r>'%(self.__class__.__name__, self.__name__)
+
+
+def intIdDump(obj, idx, cache):
+    # allows to use intids or objects for the tokenizer
+    # this allows to use intids instead of objects to be passed to all query
+    # functions.
+    if isinstance(obj, int):
+        return obj
+    return index.generateToken(obj, idx, cache)
+
+intIdLoad = index.resolveToken
+
+
+class Relations(btree.BTreeContainer, Contained, RelationTypeLookup):
+    interface.implements(IRelations)
+
+    def __init__(self, index=None):
+        idx = index
+        if idx is None:
+            idx = self._createIndex()
+        self.relationIndex = idx
+        idx.__parent__ = self
+        super(Relations, self).__init__()
+
+    def findTargets(self, source, relation=None, maxDepth=1):
+        if relation is not None:
+            return self.relationIndex.findValues(
+                'targets',
+                self.relationIndex.tokenizeQuery({'sources': source,
+                                                  'relations': self._lookup(relation),
+                                                  }),
+                maxDepth = maxDepth,
+                )
+        return self.relationIndex.findValues(
+            'targets', self.relationIndex.tokenizeQuery({'sources': source}),
+            maxDepth = maxDepth,
+            )
+
+    def findTargetTokens(self, source, relation=None, maxDepth=1):
+        if relation is not None:
+            return self.relationIndex.findValueTokens(
+                'targets',
+                self.relationIndex.tokenizeQuery({'sources': source,
+                                                  'relations': self._lookup(relation),
+                                                  }),
+                maxDepth = maxDepth,
+                )
+        return self.relationIndex.findValueTokens(
+            'targets', self.relationIndex.tokenizeQuery({'sources': source}),
+            maxDepth = maxDepth,
+            )
+
+    def findSources(self, target, relation=None, maxDepth=1):
+        if relation is not None:
+            return self.relationIndex.findValues(
+                'sources',
+                self.relationIndex.tokenizeQuery({'targets': target,
+                                                  'relations': self._lookup(relation),
+                                                  }),
+                maxDepth = maxDepth,
+                )
+        return self.relationIndex.findValues(
+            'sources',
+            self.relationIndex.tokenizeQuery({'targets': target}),
+            maxDepth = maxDepth,
+           )
+
+    def findSourceTokens(self, target, relation=None, maxDepth=1):
+        if relation is not None:
+            return self.relationIndex.findValueTokens(
+                'sources',
+                self.relationIndex.tokenizeQuery({'targets': target,
+                                                  'relations': self._lookup(relation),
+                                                  }),
+                maxDepth = maxDepth,
+                )
+        return self.relationIndex.findValueTokens(
+            'sources', self.relationIndex.tokenizeQuery({'targets': target}),
+            maxDepth = maxDepth,
+            )
+
+    def findRelationships(self, source, target, relation=None):
+        if relation is not None:
+            return self.relationIndex.findRelationships(
+                self.relationIndex.tokenizeQuery({'targets': target,
+                                                  'sources': source,
+                                                  'relations': self._lookup(relation),
+                                                  }),
+                )
+        return self.relationIndex.findRelationships(
+                self.relationIndex.tokenizeQuery({'sources': source,
+                                                  'targets': target,
+                                                  }),
+                )
+
+    def findRelationshipTokens(self, source, target, relation=None):
+        if relation is not None:
+            return self.relationIndex.findRelationshipTokenSet(
+                self.relationIndex.tokenizeQuery({'targets': target,
+                                                  'sources': source,
+                                                  'relations': self._lookup(relation),
+                                                  }),
+                )
+        return self.relationIndex.findRelationshipTokenSet(
+                self.relationIndex.tokenizeQuery({'sources': source,
+                                                  'targets': target,
+                                                  }),
+                )
+
+    def findRelationTokens(self, relation):
+        return self.relationIndex.findRelationshipTokenSet(
+            self.relationIndex.tokenizeQuery({'relations': self._lookup(relation)}),
+            )
+
+    def findTargetRelationships(self, target, relation=None):
+        if relation is not None:
+            return self.relationIndex.findRelationships(
+                   self.relationIndex.tokenizeQuery({'targets': target,
+                                                      'relations': self._lookup(relation),
+                                                      }),
+                    )
+        return self.relationIndex.findRelationships(
+                    self.relationIndex.tokenizeQuery({'targets': target}),
+                    )
+
+    def findTargetRelationshipTokens(self, target, relation=None):
+        if relation is not None:
+            return self.relationIndex.findRelationshipTokenSet(
+                    self.relationIndex.tokenizeQuery({'targets': target,
+                                                      'relations': self._lookup(relation),
+                                                      }),
+                    )
+        return self.relationIndex.findRelationshipTokenSet(
+                    self.relationIndex.tokenizeQuery({'targets': target}),
+                    )
+
+    def findSourceRelationships(self, source, relation=None):
+        if relation is not None:
+            return self.relationIndex.findRelationships(
+                    self.relationIndex.tokenizeQuery({'sources': source,
+                                                      'relations': self._lookup(relation),
+                                                      }),
+                    )
+        return self.relationIndex.findRelationships(
+                    self.relationIndex.tokenizeQuery({'sources': source}),
+                    )
+
+    def findSourceRelationshipTokens(self, source, relation=None):
+        if relation is not None:
+            return self.relationIndex.findRelationshipTokenSet(
+                    self.relationIndex.tokenizeQuery({'sources': source,
+                                                      'relations': self._lookup(relation),
+                                                      }),
+                    )
+        return self.relationIndex.findRelationshipTokenSet(
+                    self.relationIndex.tokenizeQuery({'sources': source}),
+                    )
+
+    def findRelationTargets(self, relation):
+        return self.relationIndex.findValues(
+          'targets', self.relationIndex.tokenizeQuery({'relations': relation}),
+          )
+
+    def findRelationTargetTokens(self, relation):
+        return self.relationIndex.findValueTokens(
+          'targets', self.relationIndex.tokenizeQuery({'relations': relation}),
+          )
+
+    def findRelationSources(self, relation):
+        return self.relationIndex.findValues(
+          'sources', self.relationIndex.tokenizeQuery({'relations': relation}),
+          )
+
+    def findRelationSourceTokens(self, relation):
+        return self.relationIndex.findValueTokens(
+          'sources', self.relationIndex.tokenizeQuery({'relations': relation}),
+          )
+
+    def _createIndex(self):
+        sources = {'element': IRelationship['sources'],
+                   'dump': intIdDump,
+                   'load': intIdLoad,
+                   'name': 'sources'}
+        relations = {'element': IRelationship['relations'],
+                     'name': 'relations',
+                     'multiple': True}
+        targets = {'element': IRelationship['targets'],
+                   'dump': intIdDump,
+                   'load': intIdLoad,
+                   'name': 'targets',
+                   'multiple': True}
+
+        return index.Index(
+            (sources, relations, targets),
+            index.TransposingTransitiveQueriesFactory('sources', 'targets'),
+            )
+
+    def add(self, item):
+        key = self._generate_id(item)
+        while key in self:
+            key = self._generate_id(item)
+        super(Relations, self).__setitem__(key, item)
+        self.relationIndex.index(item)
+
+    def remove(self, item):
+        key = item.__name__
+        if self[key] is not item:
+            raise ValueError("Relationship is not stored as its __name__")
+        self.relationIndex.unindex(item)
+        super(Relations, self).__delitem__(key)
+
+    def _generate_id(self, relationship):
+        return ''.join(random.sample(
+            "abcdefghijklmnopqrtstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_1234567890",
+            30)) # somewhat less than 64 ** 30 variations (64*63*...*35)
+
+    def reindex(self, target):
+        assert target.__parent__ is self
+        self.relationIndex.index(target)
+
+    @property
+    def __setitem__(self):
+        raise AttributeError
+    __delitem__ = __setitem__
+
+    def __repr__(self):
+        return '<%s %r>'%(self.__class__.__name__, self.__name__)
+
+
+class RelationTypes(btree.BTreeContainer, Contained):
+    interface.implements(IRelationTypes)
+
+    def __repr__(self):
+        return '<%s %r>'%(self.__class__.__name__, self.__name__)
+
+
+class RelationType(persistent.Persistent, Contained):
+    interface.implements(IRelationType)
+
+    title       = FieldProperty(IRelationType['title'])
+    description = FieldProperty(IRelationType['description'])
+
+    def __init__(self, title):
+        super(RelationType, self).__init__()
+        self.title = title
+
+    @property
+    def name(self):
+        return self.__name__
+
+    def __repr__(self):
+        return '<%s %r>'%(self.__class__.__name__, self.title)
+
+
+class OneToManyRelationships(Relations):
+    interface.implements(IOneToManyRelationships)
+
+
+class OneToManyRelationship(Relationship):
+    interface.implements(IOneToManyRelationship)
+
+    def __init__(self, source, relations, objs=[]):
+        super(OneToManyRelationship, self).__init__(source,
+                                                    relations,
+                                                    PersistentList())
+        if objs:
+            for obj in objs:
+                self.targets.append(obj)
+            if IRelations.providedBy(self.__parent__):
+                self.__parent__.reindex(self)
+
+    def add(self, obj):
+        if not obj in self.targets:
+            self.targets.append(obj)
+            if IRelations.providedBy(self.__parent__):
+                self.__parent__.reindex(self)
+
+    def remove(self, obj):
+        self.targets.remove(obj)
+        if IRelations.providedBy(self.__parent__):
+            self.__parent__.reindex(self)
+
+
+class OneToOneRelationships(Relations):
+    interface.implements(IOneToOneRelationships)
+
+    def _createIndex(self):
+        sources = {'element': IRelationship['sources'],
+                   'dump': intIdDump,
+                   'load': intIdLoad,
+                   'name': 'sources'}
+        relations = {'element': IRelationship['relations'],
+                     'name': 'relations',
+                     'multiple': True}
+        targets = {'element': IRelationship['targets'],
+                   'dump': intIdDump,
+                   'load': intIdLoad,
+                   'name': 'targets'}
+
+        return index.Index(
+            (sources, relations, targets),
+            index.TransposingTransitiveQueriesFactory('sources', 'targets'),
+            )
+
+
+class OneToOneRelationship(Relationship):
+    interface.implements(IOneToOneRelationship)
+
+
+def _dlRelations(v, i, c):
+    """fake dump and load for string based relations"""
+    return v
+
+class O2OStringTypeRelationships(Relations):
+
+    """relationships which relation types are strings"""
+
+    interface.implements(IO2OStringTypeRelationships)
+
+    def _createIndex(self):
+        sources = {'element': IRelationship['sources'],
+                   'dump': intIdDump,
+                   'load': intIdLoad,
+                   'name': 'sources'}
+        relations = {'element': IRelationship['relations'],
+                     'dump': _dlRelations,
+                     'load': _dlRelations,
+                     'btree': OIBTree,
+                     'name': 'relations',
+                     'multiple': True}
+        targets = {'element': IRelationship['targets'],
+                   'dump': intIdDump,
+                   'load': intIdLoad,
+                   'name': 'targets'}
+
+        return index.Index(
+            (sources, relations, targets),
+            index.TransposingTransitiveQueriesFactory('sources', 'targets'),
+            )
+
+class O2OStringTypeRelationship(Relationship):
+    interface.implements(IO2OStringTypeRelationship)
+


Property changes on: lovely.relation/trunk/src/lovely/relation/app.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/configurator.py
===================================================================
--- lovely.relation/trunk/src/lovely/relation/configurator.py	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/configurator.py	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,63 @@
+##############################################################################
+#
+# Copyright (c) 2006-2007 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+import interfaces
+import app
+from zope import component
+from zope import interface
+from zope.interface.interfaces import IMethod
+from zope import schema
+from z3c.configurator import configurator
+from zope.app.component.interfaces import ISite
+from zope.lifecycleevent import ObjectCreatedEvent
+import zope.event
+from zope.security.proxy import removeSecurityProxy
+
+class IUtilProperties(interface.Interface):
+
+    name = schema.TextLine(title=u'Name',
+                           required=False,
+                           default=u'')
+
+class SetUpO2OStringTypeRelationships(configurator.SchemaConfigurationPluginBase):
+    component.adapts(ISite)
+    schema = IUtilProperties
+
+    def __call__(self, data):
+        for name in self.schema:
+            field = self.schema[name]
+            if IMethod.providedBy(field):
+                continue
+            if data.get(name) is None:
+                data[name] = self.schema[name].default
+        name = data.get('name')
+        site = self.context
+        # we just wanna have one
+        util = component.queryUtility(interfaces.IO2OStringTypeRelationships,
+                                      context=site,
+                                      name=name)
+        if util is not None:
+            return
+        # needed to be called TTW
+        sm = removeSecurityProxy(site.getSiteManager())
+        default = sm['default']
+        util = app.O2OStringTypeRelationships()
+        zope.event.notify(ObjectCreatedEvent(util))
+        default[u'o2oStringTypeRelationships_' + name] = util
+        sm.registerUtility(util, interfaces.IO2OStringTypeRelationships,
+                           name=name)


Property changes on: lovely.relation/trunk/src/lovely/relation/configurator.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/configure.zcml
===================================================================
--- lovely.relation/trunk/src/lovely/relation/configure.zcml	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/configure.zcml	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,36 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+  <class class=".app.Relationship">
+    <require
+        permission="zope.View"
+        interface=".interfaces.IRelationship"
+        />
+    <require
+        permission="zope.ManageContent"
+        set_schema=".interfaces.IRelationship"
+        />
+  </class>
+
+  <class class=".app.Relations">
+    <require
+        permission="zope.View"
+        interface=".interfaces.IRelations"
+        />
+    <require
+        permission="zope.ManageContent"
+        set_schema=".interfaces.IRelations"
+        />
+  </class>
+
+  <class class=".app.RelationTypes">
+    <require
+        permission="zope.View"
+        interface=".interfaces.IRelationTypes"
+        />
+    <require
+        permission="zope.ManageContent"
+        set_schema=".interfaces.IRelationTypes"
+        />
+  </class>
+
+</configure>


Property changes on: lovely.relation/trunk/src/lovely/relation/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/demo/README.txt
===================================================================
--- lovely.relation/trunk/src/lovely/relation/demo/README.txt	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/demo/README.txt	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,63 @@
+=====================
+Lovely Relations Demo
+=====================
+
+This demo creates a view document class which can be related to other
+documents. All documents track their backreferences.
+
+In our test setup the relations utility is already created and
+registered. So we can start creating some objects.
+
+  >>> from zope.testbrowser.testing import Browser
+  >>> browser = Browser()
+  >>> browser.addHeader('Authorization','Basic mgr:mgrpw')
+  >>> browser.handleErrors=False
+  >>> for i in range(1,4):
+  ...     browser.open('http://localhost/@@contents.html')
+  ...     browser.getLink('Related Document').click()
+  ...     browser.getControl(name="new_value").value=u'doc%s' % i
+  ...     browser.getControl('Apply').click()
+
+Let us go to the edit form of the first document.
+
+  >>> browser.open('http://localhost/doc1/manage')
+  >>> browser.url
+  'http://localhost/doc1/@@edit.html'
+
+Note: We have a custom widget here, which lets us relate objects by
+providing a comma-seperated list of names. The standard zope widget
+uses javascript, so we can't test it with tesbrowser.
+
+  >>> browser.getControl('Related').value
+  ''
+  >>> browser.getControl('Related').value = 'doc2, doc3'
+  >>> browser.getControl('Change').click()
+
+  >>> '<p>Updated on ' in browser.contents
+  True
+
+  >>> browser.open('http://localhost/doc1/@@edit.html')
+  >>> browser.getControl('Related').value
+  'doc2, doc3'
+
+Change the order.
+
+  >>> browser.getControl('Related').value = 'doc3, doc2'
+  >>> browser.getControl('Change').click()
+  >>> browser.open('http://localhost/doc1/@@edit.html')
+  >>> browser.getControl('Related').value
+  'doc3, doc2'
+
+Let's have a look at the backrefs of doc2. This is a readonly
+attribute, so a display widget is rendered.
+
+  >>> browser.open('http://localhost/doc2/@@edit.html')
+  >>> print browser.contents
+  <...
+  <div class="row">
+      <div class="label">
+        <label for="field.backrefs" title="">Backreferences</label>
+      </div>
+      <div class="field"><ul id="field.backrefs" ><li>doc1</li></ul></div>
+  </div>...
+


Property changes on: lovely.relation/trunk/src/lovely/relation/demo/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/demo/__init__.py
===================================================================


Property changes on: lovely.relation/trunk/src/lovely/relation/demo/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/demo/app.py
===================================================================
--- lovely.relation/trunk/src/lovely/relation/demo/app.py	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/demo/app.py	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,36 @@
+##############################################################################
+#
+# Copyright (c) 2007 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+from zope import interface
+from persistent import Persistent
+from zope.app.container.contained import Contained
+from interfaces import IDocument, IRelatedByDocument
+from lovely.relation.property import FieldRelationManager
+from lovely.relation.property import RelationPropertyIn
+from lovely.relation.property import RelationPropertyOut
+
+documentRelated = FieldRelationManager(IDocument['related'],
+                                       IRelatedByDocument['backrefs'],
+                                       relType='demo.documentRelated')
+
+class Document(Persistent, Contained):
+    interface.implements(IDocument)
+
+    related = RelationPropertyOut(documentRelated)
+    backrefs = RelationPropertyIn(documentRelated)
+


Property changes on: lovely.relation/trunk/src/lovely/relation/demo/app.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/demo/browser.py
===================================================================
--- lovely.relation/trunk/src/lovely/relation/demo/browser.py	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/demo/browser.py	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,37 @@
+##############################################################################
+#
+# Copyright (c) 2006-2007 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+from zope.app.form.browser.textwidgets import TextWidget
+
+class NamesWidget(TextWidget):
+
+    def __init__(self, list, choice, request):
+        super(NamesWidget, self).__init__(list, request)
+        self.vocabulary = choice.vocabulary
+
+    def _toFieldValue(self, input):
+        value = super(NamesWidget, self)._toFieldValue(input)
+        if not value:
+            return u''
+        value = value.split(u',')
+        return [self.vocabulary.getTermByToken(t.strip()).value \
+                for t in value]
+
+    def _toFormValue(self, value):
+        return u', '.join([self.vocabulary.getTerm(v).token for v in value])
+


Property changes on: lovely.relation/trunk/src/lovely/relation/demo/browser.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/demo/configure.zcml
===================================================================
--- lovely.relation/trunk/src/lovely/relation/demo/configure.zcml	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/demo/configure.zcml	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,36 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:browser="http://namespaces.zope.org/browser">
+
+  <browser:addMenuItem
+      class=".app.Document"
+      permission="zope.ManageContent"
+      title="Related Document"
+      />
+
+  <browser:editform
+      schema=".interfaces.IDocument"
+      for=".interfaces.IDocument"
+      name="edit.html"
+      menu="zmi_views"
+      permission="zope.ManageContent"
+      title="Edit">
+    <widget
+        field="related"
+        class=".browser.NamesWidget"/>
+  </browser:editform>
+
+  <class class=".app.Document">
+    <require permission="zope.View"
+             interface=".interfaces.IDocument"/>
+    <require permission="zope.ManageContent"
+             set_schema=".interfaces.IDocument"/>
+  </class>
+
+  <utility
+      provides="zope.schema.interfaces.IVocabularyFactory"
+      component=".vocabulary.documentsInParentVocabulary"
+      name="demo.documentsInParent"
+  />
+
+  
+</configure>
\ No newline at end of file


Property changes on: lovely.relation/trunk/src/lovely/relation/demo/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/demo/ftesting.zcml
===================================================================
--- lovely.relation/trunk/src/lovely/relation/demo/ftesting.zcml	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/demo/ftesting.zcml	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,71 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:browser="http://namespaces.zope.org/browser"
+    xmlns:meta="http://namespaces.zope.org/meta"
+    xmlns:zcml="http://namespaces.zope.org/zcml"
+    i18n_domain="zope">
+
+  <include package="zope.app.securitypolicy" file="meta.zcml" />
+  <include
+      zcml:condition="installed zope.app.zcmlfiles"
+      package="zope.app.zcmlfiles"
+      />
+  <include
+      zcml:condition="not-installed zope.app.zcmlfiles"
+      package="zope.app"
+      />
+
+  <include package="zope.app.authentication" />
+  <securityPolicy
+    component="zope.app.securitypolicy.zopepolicy.ZopeSecurityPolicy" />
+
+  <include package="zope.app.securitypolicy" />
+
+  <role id="zope.Anonymous" title="Everybody"
+        description="All users have this role implicitly" />
+
+  <role id="zope.Manager" title="Site Manager" />
+  <grantAll role="zope.Manager" />
+
+  <principal
+   id="zope.manager"
+   title="Administrator"
+   login="mgr"
+   password="mgrpw" />
+  <grant
+   role="zope.Manager"
+   principal="zope.manager"
+   />
+
+  <unauthenticatedPrincipal
+    id="zope.anybody"
+    title="Unauthenticated User" />
+
+  <unauthenticatedGroup
+    id="zope.Anybody"
+    title="Unauthenticated Users"
+    />
+
+  <authenticatedGroup
+    id="zope.Authenticated"
+    title="Authenticated Users"
+    />
+
+  <everybodyGroup
+    id="zope.Everybody"
+    title="All Users"
+    />
+
+  <include package="zope.app.intid" />
+  <include package="zope.app.keyreference" />
+  <include package="zope.formlib"/>
+  <include package="lovely.portal"/>
+
+  <include package="lovely.relation" />
+  <include package="lovely.relation" file="o2o.zcml" />
+  <include package="lovely.relation.demo" />
+
+  <grant permission="zope.View"
+         role="zope.Anonymous" />
+
+</configure>


Property changes on: lovely.relation/trunk/src/lovely/relation/demo/ftesting.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/demo/interfaces.py
===================================================================
--- lovely.relation/trunk/src/lovely/relation/demo/interfaces.py	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/demo/interfaces.py	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,38 @@
+##############################################################################
+#
+# Copyright (c) 2006-2007 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+from zope import schema, interface
+
+class IRelatedByDocument(interface.Interface):
+
+    """things that can be refered to by documents, we use a vocabulary
+    here in order to work with existing widgets."""
+
+    backrefs = schema.List(
+            title=u"Backreferences",
+            value_type=schema.Choice(vocabulary='demo.documentsInParent'),
+            required=False, default=[],
+            readonly=True)
+
+class IDocument(IRelatedByDocument):
+    related = schema.List(
+            title=u"Related",
+            value_type=schema.Choice(vocabulary='demo.documentsInParent'),
+            required=False,
+            default=[])
+


Property changes on: lovely.relation/trunk/src/lovely/relation/demo/interfaces.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/demo/tests.py
===================================================================
--- lovely.relation/trunk/src/lovely/relation/demo/tests.py	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/demo/tests.py	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,43 @@
+##############################################################################
+#
+# Copyright (c) 2006-2007 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+import unittest
+from zope.app.testing import functional
+from zope.app.testing import setup
+from z3c.configurator import configurator
+from z3c.testing import layer
+
+def appSetUp(app):
+    configurator.configure(app, {},
+                           names=["lovely.relation.o2oStringTypeRelations"])
+
+layer.defineLayer('LovelyRelationsDemoLayer', zcml='ftesting.zcml',
+                  appSetUp=appSetUp,
+                  clean=True)
+
+
+def test_suite():
+    fsuites = (
+        functional.FunctionalDocFileSuite('README.txt')
+    )
+    for fsuite in fsuites:
+        fsuite.layer=LovelyRelationsDemoLayer
+    return unittest.TestSuite(fsuites)
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


Property changes on: lovely.relation/trunk/src/lovely/relation/demo/tests.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/demo/vocabulary.py
===================================================================
--- lovely.relation/trunk/src/lovely/relation/demo/vocabulary.py	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/demo/vocabulary.py	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,30 @@
+##############################################################################
+#
+# Copyright (c) 2006-2007 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+from zope.schema.vocabulary import SimpleVocabulary
+from interfaces import IDocument
+
+def documentsInParentVocabulary(context):
+
+    """a vocabulary that returns the child documents __name__for any
+    subobjects of parent"""
+
+    return SimpleVocabulary.fromItems(
+        [(k, v) for k, v in context.__parent__.items() \
+         if IDocument.providedBy(v)])
+


Property changes on: lovely.relation/trunk/src/lovely/relation/demo/vocabulary.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/event.py
===================================================================
--- lovely.relation/trunk/src/lovely/relation/event.py	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/event.py	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,42 @@
+##############################################################################
+#
+# Copyright (c) 2007 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+from zope import interface
+from zope import component
+
+from zope.app.intid.interfaces import IIntIdRemovedEvent
+
+from zope.component import getAllUtilitiesRegisteredFor
+
+import interfaces
+
+
+ at component.adapter(IIntIdRemovedEvent)
+def o2oIntIdRemoved(event):
+    """Subscriber for IIntIdRemovedEvent.
+
+    Remove all relations from the O2ORelation container for the removed object.
+    """
+    obj = event.object
+    for rels in getAllUtilitiesRegisteredFor(
+                                    interfaces.IO2OStringTypeRelationships):
+        relations = list(rels.findSourceRelationships(obj))
+        relations += list(rels.findTargetRelationships(obj))
+        for rel in relations:
+            rels.remove(rel)
+


Property changes on: lovely.relation/trunk/src/lovely/relation/event.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/i18n.py
===================================================================
--- lovely.relation/trunk/src/lovely/relation/i18n.py	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/i18n.py	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,23 @@
+##############################################################################
+#
+# Copyright (c) 2007 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""The i18n definitions.
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.i18nmessageid
+
+_ = zope.i18nmessageid.MessageFactory('lovely.relation')
+


Property changes on: lovely.relation/trunk/src/lovely/relation/i18n.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/interfaces.py
===================================================================
--- lovely.relation/trunk/src/lovely/relation/interfaces.py	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/interfaces.py	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,153 @@
+##############################################################################
+#
+# Copyright (c) 2007 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""The lovely relation interfaces.
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+from zope import interface
+from zope import schema
+from zope.schema.interfaces import IField
+from zope.app.container import constraints
+from zope.app.container.interfaces import IContainer
+
+from i18n import _
+
+
+class IFieldRelationManager(interface.Interface):
+
+    fOut = schema.Object(IField)
+    fIn = schema.Object(IField)
+    relType = interface.Attribute("The relation type identifier")
+    utilName = schema.TextLine(title=u'Relations Utility Name')
+
+
+class IPropertyRelationManager(interface.Interface):
+
+    def getRelations():
+        """Return the relations for a property"""
+
+    def getRelationTokens():
+        """Return the relation tokens for a property"""
+
+
+class IRelations(IContainer):
+    """A container to manage relations"""
+
+    def findTargets(source, relation=None):
+        """Find all targets related to the source.
+
+        The relation parameter allows to filter for specific relations.
+        If no relation is given all targets are returned.
+        """
+
+    def findSources(target, relation=None):
+        """Find all sources containing the target.
+
+        The relation parameter allowes to filter for specific relations.
+        If no relation is given all sources are returned.
+        """
+
+
+class IRelationType(interface.Interface):
+    """A relationship type
+
+    The relationship type is the verb of the relation.
+    """
+    constraints.containers('.IRelationTypes')
+
+    title = schema.TextLine(
+            title = _(u'title'),
+            description = _(u'The title of this relation'),
+            required = True,
+            default = u''
+            )
+
+    description = schema.Text(
+            title = _(u'description'),
+            description = _(u'The description for the relation.'),
+            required = False,
+            default = u''
+            )
+
+
+class IRelationTypes(IContainer):
+    """A container to manage relationtypes."""
+    constraints.contains(IRelationType)
+
+
+class IRelationTypeLookup(interface.Interface):
+    """Encapsulate relation type lookup."""
+
+    relationtypes = interface.Attribute(
+        """Property returning the relationtypes container used for lookups. Readonly.
+        """)
+
+    def _lookup(relation):
+        """Look up relation type objects
+
+        If `relation` is a string, the corresponding relationtype
+        object is looked up in the relationtypes container and returned.
+        If `self.relationtypes` is None or `relation` is not a string,
+        no lookup is performed and the argument is returned unchanged.
+        """
+
+
+class IRelationship(interface.Interface):
+    """A one to many relationship"""
+
+    __parent__ = interface.Attribute(
+        """The relationship container of which this relationship is a member
+        """)
+
+    sources = interface.Attribute(
+        """Sources pointing in the relationship.  Readonly.""")
+
+    relations = interface.Attribute(
+        """The relations the relation belongs to.""")
+
+    targets = interface.Attribute(
+        """Targets being pointed to in the relationship.  Readonly.""")
+
+
+class IOneToManyRelationship(interface.Interface):
+    """Allow the modification of the many part of a one-to-many relationship"""
+
+    def add(obj):
+        """A a target target to the relation"""
+
+    def remove(obj):
+        """Remove a target target from the relation"""
+
+
+class IOneToManyRelationships(IRelations):
+    """A container for one-to-many relationships"""
+    constraints.contains(IOneToManyRelationship)
+
+
+class IOneToOneRelationship(interface.Interface):
+    """A simple one-to-one relationship"""
+
+
+class IOneToOneRelationships(IRelations):
+    """A container for one-to-one relationships"""
+    constraints.contains(IOneToOneRelationship)
+
+class IO2OStringTypeRelationships(IRelations):
+    """A container for one-to-one relationships"""
+    constraints.contains('IO2OStringTypeRelationships')
+
+class IO2OStringTypeRelationship(IOneToOneRelationship):
+    """A one to one relationship with a string as type"""


Property changes on: lovely.relation/trunk/src/lovely/relation/interfaces.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/lovely.relation-configure.zcml
===================================================================
--- lovely.relation/trunk/src/lovely/relation/lovely.relation-configure.zcml	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/lovely.relation-configure.zcml	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1 @@
+<include package="lovely.relation"/>


Property changes on: lovely.relation/trunk/src/lovely/relation/lovely.relation-configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/o2o.zcml
===================================================================
--- lovely.relation/trunk/src/lovely/relation/o2o.zcml	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/o2o.zcml	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,21 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+  <!-- All o2o functionality is in it's own configuration especially because
+       of the event handler needed to clean up relation in case an object is
+       removed from the intid utility. -->
+
+  <class class=".app.O2OStringTypeRelationships">
+    <require
+        permission="zope.View"
+        interface=".interfaces.IO2OStringTypeRelationships"
+        />
+  </class>
+
+  <!-- configurator -->
+  <adapter factory=".configurator.SetUpO2OStringTypeRelationships"
+           name="lovely.relation.o2oStringTypeRelations"/>
+
+  <!-- event handler -->
+  <subscriber handler=".event.o2oIntIdRemoved" />
+
+</configure>


Property changes on: lovely.relation/trunk/src/lovely/relation/o2o.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/property.py
===================================================================
--- lovely.relation/trunk/src/lovely/relation/property.py	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/property.py	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,274 @@
+##############################################################################
+#
+# Copyright (c) 2007 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Relationship Properties
+
+$Id$
+"""
+
+import interfaces
+from zope import interface
+from zope import component
+from zope.schema.fieldproperty import FieldProperty
+from zope.schema.interfaces import IList, ISequence
+from app import O2OStringTypeRelationship
+
+_marker = object()
+
+def _ident(field):
+    if field is None:
+        return repr(field)
+    return '.'.join((field.__class__.__module__,
+                     field.__class__.__name__,
+                     field.__name__))
+
+
+class FieldRelationManager(object):
+
+    interface.implements(interfaces.IFieldRelationManager)
+    utilName = FieldProperty(interfaces.IFieldRelationManager['utilName'])
+
+    def __init__(self, source, target=None,
+                 relType=None,  utilName=u''):
+        self.fOut = source
+        self.fIn = target
+        if relType is None:
+            self.relType = ':'.join((_ident(self.fOut), _ident(self.fIn)))
+        else:
+            self.relType=relType
+        self.utilName = utilName
+
+    @property
+    def seqIn(self):
+        return ISequence.providedBy(self.fIn)
+
+    @property
+    def seqOut(self):
+        return ISequence.providedBy(self.fOut)
+
+    @property
+    def util(self):
+        return component.getUtility(interfaces.IO2OStringTypeRelationships,
+                                    name=self.utilName)
+
+    def getSourceTokens(self, target):
+        util = self.util
+        return util.findSourceTokens(target, self.relType)
+
+    def getTargetTokens(self, source):
+        util = self.util
+        return util.findTargetTokens(source, self.relType)
+
+    def getSourceRelations(self, obj):
+        util = self.util
+        return util.findSourceRelationships(obj, self.relType)
+
+    def getTargetRelations(self, obj):
+        util = self.util
+        return util.findTargetRelationships(obj, self.relType)
+
+    def getSourceRelationTokens(self, obj):
+        util = self.util
+        return util.findSourceRelationshipTokens(obj, self.relType)
+
+    def getTargetRelationTokens(self, obj):
+        util = self.util
+        return util.findTargetRelationshipTokens(obj, self.relType)
+
+    def setTargets(self, source, targets):
+        util = self.util
+        if targets is not None:
+            if not self.seqOut:
+                targets = [targets]
+            newTargetTokens = util.relationIndex.tokenizeValues(targets,
+                                                                'targets')
+        else:
+            newTargetTokens = []
+        sourceToken = util.relationIndex.tokenizeValues([source],
+                                                        'sources').next()
+        oldTargetTokens = util.findTargetTokens(source, self.relType)
+        newTT = set(newTargetTokens)
+        oldTT = set(oldTargetTokens)
+        addTT = newTT.difference(oldTT)
+        delTT = oldTT.difference(newTT)
+        for tt in delTT:
+            rel = util.relationIndex.findRelationships(
+                {'sources': sourceToken,
+                 'relations': self.relType,
+                 'targets': tt})
+            self.util.remove(rel.next())
+
+        for addT in list(
+            util.relationIndex.resolveValueTokens(addTT, 'targets')):
+            rel = O2OStringTypeRelationship(source, [self.relType],
+                                             addT)
+            self.util.add(rel)
+
+    def setSources(self, target, sources):
+        util = self.util
+        if sources is not None:
+            if not self.seqIn:
+                sources = [sources]
+            newSourceTokens = util.relationIndex.tokenizeValues(sources,
+                                                                'sources')
+        else:
+            newSourceTokens = []
+        targetToken = util.relationIndex.tokenizeValues([target],
+                                                        'targets').next()
+
+        oldSourceTokens = util.findSourceTokens(target, self.relType)
+        newST = set(newSourceTokens)
+        oldST = set(oldSourceTokens)
+        addST = newST.difference(oldST)
+        delST = oldST.difference(newST)
+        for st in delST:
+            rel = util.relationIndex.findRelationships(
+                {'targets': targetToken,
+                 'relations': self.relType,
+                 'sources': st})
+            self.util.remove(rel.next())
+
+        for addT in list(
+            util.relationIndex.resolveValueTokens(addST, 'sources')):
+            rel = O2OStringTypeRelationship(addST, [self.relType],
+                                       target)
+            self.util.add(rel)
+
+
+    def tokenizeValues(self, values, index):
+        return self.util.relationIndex.tokenizeValues(values, index)
+
+    def resolveValueTokens(self, tokens, index):
+        return self.util.relationIndex.resolveValueTokens(tokens, index)
+
+
+class PropertyRelationManager(object):
+    interface.implements(interfaces.IPropertyRelationManager)
+
+    def __init__(self, context, propertyName):
+        self.context = context
+        self._field = getattr(context.__class__, propertyName)
+
+    def getRelations(self):
+        manager = self._field._manager
+        if isinstance(self._field, RelationPropertyOut):
+            return manager.getSourceRelations(self.context)
+        else:
+            return manager.getTargetRelations(self.context)
+
+    def getRelationTokens(self):
+        manager = self._field._manager
+        if isinstance(self._field, RelationPropertyOut):
+            return manager.getSourceRelationTokens(self.context)
+        else:
+            return manager.getTargetRelationTokens(self.context)
+
+
+class RelationPropertyBase(object):
+
+    def __init__(self, manager, field, name=None, uids=False):
+        if name is None:
+            name = field.__name__
+        self._manager = manager
+        self._name = name
+        self._field = field
+        self._uids = uids
+        self._ordered = IList.providedBy(self._field)
+
+    def _setOrder(self, inst, value):
+        inst.__dict__['_o_' + self._name] = value
+
+    def _getOrder(self, inst):
+        return inst.__dict__.get('_o_' + self._name, [])
+
+    def _sort(self, inst, seq):
+        keys = list(self._getOrder(inst))
+        seq = list(seq)
+        if not keys:
+            return seq
+        def _key(i):
+            try:
+                return keys.index(i)
+            except ValueError:
+                return None
+        return sorted(seq, key=_key)
+
+
+class RelationPropertyOut(RelationPropertyBase):
+
+    def __init__(self, manager, name=None, uids=False):
+        super(RelationPropertyOut, self).__init__(manager,
+                                                 manager.fOut,
+                                                 name,
+                                                 uids)
+
+    def __get__(self, inst, klass):
+        if inst is None:
+            return self
+        tokens = self._manager.getTargetTokens(inst)
+        if self._ordered:
+            tokens = self._sort(inst, tokens)
+        if not self._uids:
+            tokens = self._manager.resolveValueTokens(tokens, 'targets')
+        tokens = list(tokens)
+        if self._manager.seqOut:
+            return tokens
+        else:
+            try:
+                return tokens[0]
+            except IndexError:
+                return None
+
+    def __set__(self, inst, value):
+        if self._field.readonly:
+            raise ValueError(self._name, 'field is readonly')
+        self._manager.setTargets(inst, value)
+        if self._ordered:
+            inst.__dict__['_o_' + self._name] = \
+                        list(self._manager.tokenizeValues(value, 'targets'))
+
+
+class RelationPropertyIn(RelationPropertyBase):
+
+    def __init__(self, manager, name=None, uids=False):
+        super(RelationPropertyIn, self).__init__(manager,
+                                                 manager.fIn,
+                                                 name,
+                                                 uids)
+
+    def __get__(self, inst, klass):
+        if inst is None:
+            return self
+        tokens = self._manager.getSourceTokens(inst)
+        if self._ordered:
+            tokens = self._sort(inst, tokens)
+        if not self._uids:
+            tokens = self._manager.resolveValueTokens(tokens, 'sources')
+        tokens = list(tokens)
+        if self._manager.seqIn:
+            return tokens
+        else:
+            try:
+                return tokens[0]
+            except IndexError:
+                return None
+
+    def __set__(self, inst, value):
+        if self._field.readonly:
+            raise ValueError(self._name, 'field is readonly')
+        self._manager.setSources(inst, value)
+        if self._ordered:
+            inst.__dict__['_o_' + self._name] = \
+                        list(self._manager.tokenizeValues(value, 'sources'))
+
+


Property changes on: lovely.relation/trunk/src/lovely/relation/property.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/property.txt
===================================================================
--- lovely.relation/trunk/src/lovely/relation/property.txt	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/property.txt	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,397 @@
+===================
+Relation Properties
+===================
+
+Relation properties allow to transparently relate objects by simple
+attributes.
+
+Let us create some content objects to demonstrate this.
+
+  >>> from zope import schema, interface
+  >>> from zope.schema.interfaces import IObject
+  >>> class IImage(interface.Interface):
+  ...     name = schema.TextLine(title=u'Name')
+
+  >>> class Image(object):
+  ...     interface.implements(IImage)
+  ...     def __init__(self, name):
+  ...         self.name = name
+  ...     def __repr__(self):
+  ...         return '<image %r>' % self.name
+
+  >>> class IRelatedByDocument(interface.Interface):
+  ...     """things that can be refered to by documents"""
+  ...     backrefs = schema.List(title=u"Backreferences",
+  ...                            value_type=schema.Object(IObject),
+  ...                            required=False,
+  ...                            default=[])
+
+  >>> class IDocument(IRelatedByDocument):
+  ...     teaser = schema.Object(IImage, title=u'Teaser Image')
+  ...     related = schema.List(title=u"Related",
+  ...                           value_type=schema.Object(IRelatedByDocument),
+  ...                           required=False,
+  ...                           default=[])
+
+  >>> from zope import component
+  >>> from zope.app.intid.interfaces import IIntIds
+  >>> intids = component.getUtility(IIntIds)
+
+
+Relation Managers
+=================
+
+Relation managers are responsible to handle all aspects of one
+particular relation. This package defines a relation manager which
+uses interface fields to describe a relation.
+
+The field based manager is instanciated by providing a source and
+target field, a relation type identifier and the name of a IRelations
+utility registration.
+
+Let us create the document to document relation manager.
+
+  >>> from lovely.relation.property import FieldRelationManager
+  >>> documentRelated = FieldRelationManager(IDocument['related'],
+  ...                                        IRelatedByDocument['backrefs'],
+  ...                                        relType='documentRelated')
+
+and the document to teaser relation manager. Note that except of the
+source, every argument to the constructor is optional. If no target
+field is provided, no backreferencing property can be defined by using
+this manager.
+
+  >>> documentTeaser = FieldRelationManager(IDocument['teaser'])
+
+If no explicit relation type is given, then a type identifier is
+generated by using the fields as basis.
+
+  >>> documentTeaser.relType
+  'zope.schema._field.Object.teaser:None'
+
+
+Properties
+==========
+
+Relationproperties are instanciated, with one argument. This can
+either be an object implementing IFieldRelationManager, or a string,
+which will be used too lookup a utility implementing
+IFieldRelationManager with that name.
+
+For now we directly pass the manager objects to the constructor of the
+properties.
+
+  >>> from lovely.relation.property import RelationPropertyIn
+  >>> from lovely.relation.property import RelationPropertyOut
+  >>> class Document(object):
+  ...     interface.implements(IDocument)
+  ...     teaser = RelationPropertyOut(documentTeaser)
+  ...     related = RelationPropertyOut(documentRelated)
+  ...     backrefs = RelationPropertyIn(documentRelated)
+  ...     def __init__(self, name):
+  ...         self.name = name
+  ...     def __repr__(self):
+  ...         return '<document %r>' % self.name
+
+There are two types of relations properties - "In" and "Out". The
+former means that the property is the source and the latter means the
+property is the target of the relation.
+
+  >>> doc1 = Document(u'Doc One')
+  >>> doc2 = Document(u'Doc Two')
+  >>> doc3 = Document(u'Doc Three')
+  >>> img1 = Image(u'Image One')
+
+Before we can set any relations, we have to register a special
+relations utility which does one to one relations and the
+relation types are defined as strings.
+
+  >>> from lovely.relation.app import O2OStringTypeRelationships
+  >>> from lovely.relation.interfaces import IO2OStringTypeRelationships
+  >>> rels = O2OStringTypeRelationships()
+  >>> component.provideUtility(rels, IO2OStringTypeRelationships)
+
+We can now define a teaser on doc1. Note that we have no list here
+because the field is an IObject field.
+
+  >>> doc1.teaser is None
+  True
+  >>> doc1.teaser = img1
+  >>> doc1.teaser
+  <image u'Image One'>
+  >>> doc1.teaser = None
+  >>> doc1.teaser is None
+  True
+  >>> doc1.teaser = img1
+
+The related property is a list because the field is defined as schema.List.
+
+  >>> doc1.related = [doc2, doc3]
+  >>> doc1.related
+  [<document u'Doc Two'>, <document u'Doc Three'>]
+
+  >>> doc1.related = [doc3]
+  >>> doc1.related
+  [<document u'Doc Three'>]
+
+Note that the order is kept. To demonstrate this we change the order
+of the related documents.
+
+  >>> doc1.related = [doc3, doc2]
+  >>> doc1.related
+  [<document u'Doc Three'>, <document u'Doc Two'>]
+
+It is also possible to use intids instead of real objects.
+
+  >>> doc2Id = intids.getId(doc2)
+
+The backrefs are automatically updated too.
+
+  >>> doc2.backrefs
+  [<document u'Doc One'>]
+
+  >>> doc3.related = [doc2Id]
+  >>> doc3.related
+  [<document u'Doc Two'>]
+
+  >>> doc2.backrefs
+  [<document u'Doc One'>, <document u'Doc Three'>]
+
+Note that the backrefs can be sorted too.
+
+  >>> doc2.backrefs = [doc3, doc1]
+  >>> doc2.backrefs
+  [<document u'Doc Three'>, <document u'Doc One'>]
+
+But the order of the forwardrefes still is kept.
+
+  >>> doc1.related
+  [<document u'Doc Three'>, <document u'Doc Two'>]
+
+We can also remove backrefs.
+
+  >>> doc2.backrefs = [doc1]
+  >>> doc2.backrefs
+  [<document u'Doc One'>]
+
+And the relations get updated. We see this here because doc3 had a
+relation to doc2 before and now the relations are gone.
+
+  >>> doc3.related
+  []
+  >>> doc1.related
+  [<document u'Doc Three'>, <document u'Doc Two'>]
+
+
+Note are different to documents. They are not allowed to change/remove backrefs.
+
+  >>> class INote(interface.Interface):
+  ...     """things that can be refered to by documents"""
+  ...     backrefs = schema.List(title=u"Backreferences",
+  ...                            value_type=schema.Object(IObject),
+  ...                            required=False,
+  ...                            readonly=True,
+  ...                            default=[])
+
+We create a new relation manager for notes.
+
+  >>> noteRelated = FieldRelationManager(IDocument['related'],
+  ...                                        INote['backrefs'],
+  ...                                        relType='documentRelated')
+
+  >>> class Note(object):
+  ...     interface.implements(INote)
+  ...     backrefs = RelationPropertyIn(noteRelated)
+  ...     def __init__(self, name):
+  ...         self.name = name
+  ...     def __repr__(self):
+  ...         return '<note %r>' % self.name
+
+Create a note and relate doc 3 to the note.
+
+  >>> n = Note(u"note")
+  >>> doc3.related = [n]
+  >>> doc3.related[0] is n
+  True
+  >>> n.backrefs[0] is doc3
+  True
+
+Try to remove the backref from the note.
+
+  >>> n.backrefs = []
+  Traceback (most recent call last):
+  ...
+  ValueError: ('backrefs', 'field is readonly')
+  >>> n.backrefs[0] is doc3
+  True
+
+Backref to doc 3 still exists.
+
+
+Use Of The Relationship Utility
+-------------------------------
+
+The relationship utility allows queries on instances on which it is not
+possible to set a property on. As in our image class we have no direct way to
+get the documents which refer to an image. This is only possible by using the
+utility.
+
+To get the correct realtionship utility the relation manager must be used.
+
+  >>> relUtil = documentRelated.util
+
+If we use the relationships utility to do queries we get all existing
+relations for a document.
+
+  >>> [t for t in relUtil.findTargets(doc1)]
+  [<image u'Image One'>, <document u'Doc Three'>, <document u'Doc Two'>]
+
+  >>> [s for s in relUtil.findSources(img1)]
+  [<document u'Doc One'>]
+
+It is also possible to do queries based on intids instead of using the object
+directly.
+
+  >>> img1Id = intids.getId(img1)
+
+  >>> [s for s in relUtil.findSources(img1Id)]
+  [<document u'Doc One'>]
+
+To get the result for a specific relation we need to provide the relation type
+to the query.
+
+  >>> list(relUtil.findTargets(doc1, documentTeaser.relType))
+  [<image u'Image One'>]
+
+
+Relations Using Uids
+--------------------
+
+Providing the object of relations can be very inefficient. It is possible to
+define relation properties which provide uids instead of the objects.
+
+  >>> class UidDocument(object):
+  ...     interface.implements(IDocument)
+  ...     teaser = RelationPropertyOut(documentTeaser, uids=True)
+  ...     related = RelationPropertyOut(documentRelated, uids=True)
+  ...     backrefs = RelationPropertyIn(documentRelated)
+  ...     def __init__(self, name):
+  ...         self.name = name
+  ...     def __repr__(self):
+  ...         return '<document %r>' % self.name
+
+  >>> doc11 = UidDocument(u'UidDoc One')
+  >>> doc12 = UidDocument(u'UidDoc Two')
+  >>> doc13 = UidDocument(u'UidDoc Three')
+  >>> img2 = Image(u'Image Two')
+
+  >>> doc11.teaser is None
+  True
+  >>> doc11.teaser = img2
+
+We get the intid for the teaser.
+
+  >>> intids.getObject(doc11.teaser)
+  <image u'Image Two'>
+  >>> doc11.teaser = None
+  >>> doc11.teaser is None
+  True
+
+  >>> doc11.related = [doc12, doc13]
+  >>> doc11.related == [intids.getId(doc12), intids.getId(doc13)]
+  True
+
+The backrefs provide the objects.
+
+  >>> doc12.backrefs
+  [<document u'UidDoc One'>]
+
+
+More Access to relations
+------------------------
+
+To be able to do more advanced queries in relations it is possible to get the
+relation manager for a property.
+
+First we look at our teaser.
+
+  >>> from lovely.relation.property import PropertyRelationManager
+  >>> manager = PropertyRelationManager(doc1, 'teaser')
+  >>> manager
+  <lovely.relation.property.PropertyRelationManager object at ...>
+
+  >>> relations = list(manager.getRelations())
+  >>> relations
+  [<O2OStringTypeRelationship u'...'>]
+  >>> list(manager.getRelationTokens()) == [intids.getId(relations[0])]
+  True
+
+  >>> relations[0].sources, relations[0].targets
+  (<document u'Doc One'>, <image u'Image One'>)
+
+  >>> doc1.teaser = None
+  >>> list(manager.getRelations())
+  []
+
+  >>> doc1.related
+  [<document u'Doc Three'>, <document u'Doc Two'>]
+
+Let's look at our related property.
+
+  >>> manager = PropertyRelationManager(doc1, 'related')
+  >>> manager
+  <lovely.relation.property.PropertyRelationManager object at ...>
+  >>> relations = list(manager.getRelations())
+  >>> relations
+  [<O2OStringTypeRelationship u'...'>, <O2OStringTypeRelationship u'...'>]
+  >>> relations[0].sources, relations[0].targets
+  (<document u'Doc One'>, <document u'Doc Three'>)
+  >>> relations[1].sources, relations[1].targets
+  (<document u'Doc One'>, <document u'Doc Two'>)
+
+And the backrefs.
+
+  >>> manager = PropertyRelationManager(doc2, 'backrefs')
+  >>> manager
+  <lovely.relation.property.PropertyRelationManager object at ...>
+  >>> relations = list(manager.getRelations())
+  >>> relations
+  [<O2OStringTypeRelationship u'...'>]
+  >>> relations[0].sources, relations[0].targets
+  (<document u'Doc One'>, <document u'Doc Two'>)
+
+
+Event Handlers
+--------------
+
+If an object containing relation properties is removed from the intid utility
+an event handler removes all relationships.
+
+  >>> from lovely.relation.event import o2oIntIdRemoved
+  >>> component.provideHandler(o2oIntIdRemoved)
+
+  >>> root['doc1'] = doc1
+  >>> root['doc2'] = doc2
+  >>> root['doc3'] = doc3
+  >>> root['img1'] = img1
+
+  >>> doc1.teaser = img1
+  >>> doc1.teaser
+  <image u'Image One'>
+
+Deleting the image removes the teaser from the document.
+
+  >>> del root['img1']
+  >>> doc1.teaser is None
+  True
+
+Deleting a document also removes all references to it.
+
+  >>> doc2.backrefs
+  [<document u'Doc One'>]
+
+  >>> del root['doc1']
+
+  >>> doc2.backrefs
+  []
+


Property changes on: lovely.relation/trunk/src/lovely/relation/property.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/testing.py
===================================================================
--- lovely.relation/trunk/src/lovely/relation/testing.py	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/testing.py	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,28 @@
+##############################################################################
+#
+# Copyright (c) 2007 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+
+__docformat__ = "reStructuredText"
+
+from zope import component
+
+import configurator
+
+
+def setUpPlugins():
+    component.provideAdapter(configurator.SetUpO2OStringTypeRelationships,
+                             name="lovely.relation.o2oStringTypeRelations")
+


Property changes on: lovely.relation/trunk/src/lovely/relation/testing.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: lovely.relation/trunk/src/lovely/relation/tests.py
===================================================================
--- lovely.relation/trunk/src/lovely/relation/tests.py	                        (rev 0)
+++ lovely.relation/trunk/src/lovely/relation/tests.py	2007-09-03 12:13:15 UTC (rev 79445)
@@ -0,0 +1,65 @@
+##############################################################################
+#
+# Copyright (c) 2007 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Menu tests
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+import unittest
+
+from zope import component
+
+from zope.testing import doctest
+from zope.testing.doctestunit import DocFileSuite
+
+from zope.app.intid.interfaces import IIntIds
+from zope.app.intid import IntIds
+from zope.app import intid
+from zope.app.keyreference import testing
+
+from zope.app.testing import setup
+
+
+def setUp(test):
+    root = setup.placefulSetUp(site=True)
+    test.globs['root'] = root
+    sm = root.getSiteManager()
+    sm['intids'] = IntIds()
+    component.provideUtility(sm['intids'], IIntIds)
+    setup.setUpAnnotations()
+    component.provideAdapter(testing.SimpleKeyReference)
+    component.provideHandler(intid.addIntIdSubscriber)
+    component.provideHandler(intid.removeIntIdSubscriber)
+
+
+def tearDown(test):
+    setup.placefulTearDown()
+
+
+def test_suite():
+    return unittest.TestSuite((
+        DocFileSuite('README.txt',
+             setUp=setUp, tearDown=tearDown,
+             optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+             ),
+        DocFileSuite('property.txt',
+             setUp=setUp, tearDown=tearDown,
+             optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+             ),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
+


Property changes on: lovely.relation/trunk/src/lovely/relation/tests.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native



More information about the Checkins mailing list