[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