[Checkins] SVN: z3c.relationfield/ Initial import.

Martijn Faassen faassen at infrae.com
Tue Apr 15 07:14:32 EDT 2008


Log message for revision 85370:
  Initial import.
  

Changed:
  A   z3c.relationfield/
  A   z3c.relationfield/trunk/
  A   z3c.relationfield/trunk/.installed.cfg
  A   z3c.relationfield/trunk/TODO.txt
  A   z3c.relationfield/trunk/bin/
  A   z3c.relationfield/trunk/bin/test
  A   z3c.relationfield/trunk/buildout.cfg
  A   z3c.relationfield/trunk/develop-eggs/
  A   z3c.relationfield/trunk/develop-eggs/inghist.relation.egg-link
  A   z3c.relationfield/trunk/develop-eggs/z3c.objpath.egg-link
  A   z3c.relationfield/trunk/develop-eggs/z3c.relationfield.egg-link
  A   z3c.relationfield/trunk/parts/
  A   z3c.relationfield/trunk/parts/test/
  A   z3c.relationfield/trunk/setup.py
  A   z3c.relationfield/trunk/src/
  A   z3c.relationfield/trunk/src/z3c/
  A   z3c.relationfield/trunk/src/z3c/__init__.py
  A   z3c.relationfield/trunk/src/z3c/relationfield/
  A   z3c.relationfield/trunk/src/z3c/relationfield/README.txt
  A   z3c.relationfield/trunk/src/z3c/relationfield/__init__.py
  A   z3c.relationfield/trunk/src/z3c/relationfield/configure.zcml
  A   z3c.relationfield/trunk/src/z3c/relationfield/event.py
  A   z3c.relationfield/trunk/src/z3c/relationfield/ftesting.zcml
  A   z3c.relationfield/trunk/src/z3c/relationfield/ftests.py
  A   z3c.relationfield/trunk/src/z3c/relationfield/index.py
  A   z3c.relationfield/trunk/src/z3c/relationfield/interfaces.py
  A   z3c.relationfield/trunk/src/z3c/relationfield/relation.py
  A   z3c.relationfield/trunk/src/z3c/relationfield/schema.py
  A   z3c.relationfield/trunk/src/z3c/relationfield/static/
  A   z3c.relationfield/trunk/src/z3c/relationfield/static/relation.js
  A   z3c.relationfield/trunk/src/z3c/relationfield/testing.py
  A   z3c.relationfield/trunk/src/z3c/relationfield/widget.py
  A   z3c.relationfield/trunk/z3c.objpath/
  A   z3c.relationfield/trunk/z3c.objpath/buildout.cfg
  A   z3c.relationfield/trunk/z3c.objpath/setup.py
  A   z3c.relationfield/trunk/z3c.objpath/src/
  A   z3c.relationfield/trunk/z3c.objpath/src/z3c/
  A   z3c.relationfield/trunk/z3c.objpath/src/z3c/__init__.py
  A   z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/
  A   z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/README.txt
  A   z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/__init__.py
  A   z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/interfaces.py
  A   z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/path.py
  A   z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/tests.py

-=-
Added: z3c.relationfield/trunk/.installed.cfg
===================================================================
--- z3c.relationfield/trunk/.installed.cfg	                        (rev 0)
+++ z3c.relationfield/trunk/.installed.cfg	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,27 @@
+[buildout]
+installed_develop_eggs = /home/faassen/buildout/z3c.relationfield/develop-eggs/z3c.relationfield.egg-link
+parts = test
+
+[test]
+__buildout_installed__ = /home/faassen/buildout/z3c.relationfield/parts/test
+	/home/faassen/buildout/z3c.relationfield/bin/test
+__buildout_signature__ = zc.recipe.testrunner-1.0.0-py2.4.egg zc.recipe.egg-1.0.0-py2.4.egg setuptools-0.6c8-py2.4.egg zope.testing-3.5.1-py2.4.egg zc.buildout-1.0.0-py2.4.egg zc.buildout-1.0.0-py2.4.egg
+_b = /home/faassen/buildout/z3c.relationfield/bin
+_d = /home/faassen/buildout/z3c.relationfield/develop-eggs
+_e = /home/faassen/buildout-eggs
+bin-directory = /home/faassen/buildout/z3c.relationfield/bin
+defaults = ['--tests-pattern', '^f?tests$', '-v']
+develop-eggs-directory = /home/faassen/buildout/z3c.relationfield/develop-eggs
+eggs = z3c.relationfield
+eggs-directory = /home/faassen/buildout-eggs
+executable = /home/faassen/bin/python2.4
+find-links = http://download.zope.org/distribution/
+location = /home/faassen/buildout/z3c.relationfield/parts/test
+recipe = zc.recipe.testrunner
+script = /home/faassen/buildout/z3c.relationfield/bin/test
+
+[buildout]
+installed_develop_eggs = /home/faassen/buildout/z3c.relationfield/develop-eggs/z3c.relationfield.egg-link
+
+[buildout]
+parts = test

Added: z3c.relationfield/trunk/TODO.txt
===================================================================
--- z3c.relationfield/trunk/TODO.txt	                        (rev 0)
+++ z3c.relationfield/trunk/TODO.txt	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,4 @@
+TODO
+====
+
+* port this to use grokcore.component instead of grok.

Added: z3c.relationfield/trunk/bin/test
===================================================================
--- z3c.relationfield/trunk/bin/test	                        (rev 0)
+++ z3c.relationfield/trunk/bin/test	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,121 @@
+#!/home/faassen/bin/python2.4
+
+import sys
+sys.path[0:0] = [
+  '/home/faassen/buildout/z3c.relationfield/src',
+  '/home/faassen/buildout-eggs/zope.testing-3.5.1-py2.4.egg',
+  '/home/faassen/buildout-eggs/setuptools-0.6c8-py2.4.egg',
+  '/home/faassen/buildout-eggs/zc.relation-1.0a1-py2.4.egg',
+  '/home/faassen/buildout/z3c.relationfield/z3c.objpath/src',
+  '/home/faassen/buildout-eggs/z3c.schema2xml-0.10-py2.4.egg',
+  '/home/faassen/buildout-eggs/grok-0.11.1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.interface-3.4.0-py2.4-linux-i686.egg',
+  '/home/faassen/buildout-eggs/ZODB3-3.8.0b2-py2.4-linux-i686.egg',
+  '/home/faassen/buildout-eggs/zc.sourcefactory-0.3.1-py2.4.egg',
+  '/home/faassen/buildout-eggs/lxml-1.3.6-py2.4-linux-i686.egg',
+  '/home/faassen/buildout-eggs/z3c.flashmessage-1.0b2-py2.4.egg',
+  '/home/faassen/buildout-eggs/zc.catalog-1.2b-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.testbrowser-3.4.1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.traversing-3.5.0a1.dev_r78730-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.security-3.4.0b5-py2.4-linux-i686.egg',
+  '/home/faassen/buildout-eggs/zope.schema-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.publisher-3.5.0a1.dev_r78838-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.proxy-3.4.0-py2.4-linux-i686.egg',
+  '/home/faassen/buildout-eggs/zope.pagetemplate-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.lifecycleevent-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.i18nmessageid-3.4.0a1-py2.4-linux-i686.egg',
+  '/home/faassen/buildout-eggs/zope.hookable-3.4.0-py2.4-linux-i686.egg',
+  '/home/faassen/buildout-eggs/zope.formlib-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.event-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.deprecation-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.dottedname-3.4.2-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.configuration-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.component-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.zcmlfiles-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.twisted-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.testing-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.securitypolicy-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.security-3.4.0a1_1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.renderer-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.publisher-3.5.0a2-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.publication-3.4.2-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.pagetemplate-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.keyreference-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.intid-3.4.0a2-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.folder-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.container-3.5.0a1-py2.4-linux-i686.egg',
+  '/home/faassen/buildout-eggs/zope.app.component-3.4.0b3-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.catalog-3.4.0a2-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.authentication-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.appsetup-3.4.1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.applicationcontrol-3.4.1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.apidoc-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.annotation-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/pytz-2007g-py2.4.egg',
+  '/home/faassen/buildout-eggs/simplejson-1.7.1-py2.4.egg',
+  '/home/faassen/buildout-eggs/martian-0.9.2-py2.4.egg',
+  '/home/faassen/buildout-eggs/zdaemon-2.0.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/ZConfig-2.5-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.dublincore-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.form-3.4.0b2-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.session-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/ClientForm-0.2.7-py2.4.egg',
+  '/home/faassen/buildout-eggs/mechanize-0.1.7b-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.i18n-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.location-3.4.0b2-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.exceptions-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.deferredimport-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.tal-3.4.0b1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.tales-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.schema-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.wsgi-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.rotterdam-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.basicskin-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.principalannotation-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.zopeappgenerations-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.locales-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.interface-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.generations-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.content-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.dependable-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.modulealias-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.zapi-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.server-3.4.0a1_4-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.copypastemove-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.debug-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.i18n-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.structuredtext-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/docutils-0.4-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.datetime-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.contenttype-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.exception-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.error-3.5.1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.http-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.error-3.5.1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.size-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.broken-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.filerepresentation-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.cachedescriptors-3.4.0-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.thread-3.4-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.index-3.4.1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.tree-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.skins-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.preference-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.onlinehelp-3.4.0a1-py2.4.egg',
+  '/home/faassen/buildout-eggs/zodbcode-3.4.0b1dev_r75670-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.server-3.5.0a2-py2.4.egg',
+  '/home/faassen/buildout-eggs/RestrictedPython-3.4.2-py2.4.egg',
+  '/home/faassen/buildout-eggs/zope.app.file-3.4.0a1-py2.4.egg',
+  ]
+
+import os
+sys.argv[0] = os.path.abspath(sys.argv[0])
+os.chdir('/home/faassen/buildout/z3c.relationfield/parts/test')
+
+
+import zope.testing.testrunner
+
+if __name__ == '__main__':
+    zope.testing.testrunner.run((['--tests-pattern', '^f?tests$', '-v']) + [
+  '--test-path', '/home/faassen/buildout/z3c.relationfield/src',
+  ])


Property changes on: z3c.relationfield/trunk/bin/test
___________________________________________________________________
Name: svn:executable
   + 

Added: z3c.relationfield/trunk/buildout.cfg
===================================================================
--- z3c.relationfield/trunk/buildout.cfg	                        (rev 0)
+++ z3c.relationfield/trunk/buildout.cfg	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,15 @@
+[buildout]
+develop = . z3c.objpath
+parts = test
+find-links = http://download.zope.org/distribution/
+extends = http://grok.zope.org/releaseinfo/grok-0.11.1.cfg
+versions = versions
+
+[versions]
+lxml = 1.3.6
+schema2xml = 0.8.1
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = z3c.relationfield
+defaults = ['--tests-pattern', '^f?tests$', '-v']

Added: z3c.relationfield/trunk/develop-eggs/inghist.relation.egg-link
===================================================================
--- z3c.relationfield/trunk/develop-eggs/inghist.relation.egg-link	                        (rev 0)
+++ z3c.relationfield/trunk/develop-eggs/inghist.relation.egg-link	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,2 @@
+/home/faassen/buildout/z3c.relationfield/src
+../
\ No newline at end of file

Added: z3c.relationfield/trunk/develop-eggs/z3c.objpath.egg-link
===================================================================
--- z3c.relationfield/trunk/develop-eggs/z3c.objpath.egg-link	                        (rev 0)
+++ z3c.relationfield/trunk/develop-eggs/z3c.objpath.egg-link	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,2 @@
+/home/faassen/buildout/z3c.relationfield/z3c.objpath/src
+../
\ No newline at end of file

Added: z3c.relationfield/trunk/develop-eggs/z3c.relationfield.egg-link
===================================================================
--- z3c.relationfield/trunk/develop-eggs/z3c.relationfield.egg-link	                        (rev 0)
+++ z3c.relationfield/trunk/develop-eggs/z3c.relationfield.egg-link	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,2 @@
+/home/faassen/buildout/z3c.relationfield/src
+../
\ No newline at end of file

Added: z3c.relationfield/trunk/setup.py
===================================================================
--- z3c.relationfield/trunk/setup.py	                        (rev 0)
+++ z3c.relationfield/trunk/setup.py	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,29 @@
+from setuptools import setup, find_packages
+import sys, os
+
+setup(
+    name='z3c.relationfield',
+    version='0.1dev',
+    description="A relation field framework.",
+    long_description="""z3c.relationfield defines a Zope 3 schema field to
+    manage relations, and a widget to set them. Relations are automatically
+    indexed and can be queried, using zc.relation as a base.""",
+    classifiers=[],
+    keywords='',
+    author='Martijn Faassen',
+    author_email='faassen at startifact.com',
+    url='http://dev.inghist.nl/eggs/',
+    license='',
+    packages=find_packages('src'),
+    package_dir={'': 'src'},
+    include_package_data=True,
+    zip_safe=False,
+    install_requires=[
+        'setuptools',
+        'grok',
+        'z3c.schema2xml',
+        'z3c.objpath',
+        'zc.relation',
+        ],
+    entry_points={},
+    )

Added: z3c.relationfield/trunk/src/z3c/__init__.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/__init__.py	                        (rev 0)
+++ z3c.relationfield/trunk/src/z3c/__init__.py	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,7 @@
+# this is a namespace package
+try:
+    import pkg_resources
+    pkg_resources.declare_namespace(__name__)
+except ImportError:
+    import pkgutil
+    __path__ = pkgutil.extend_path(__path__, __name__)

Added: z3c.relationfield/trunk/src/z3c/relationfield/README.txt
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/README.txt	                        (rev 0)
+++ z3c.relationfield/trunk/src/z3c/relationfield/README.txt	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,442 @@
+========
+Relation
+========
+
+This package implements a new schema field Relation, a widget for
+relations, and the Relation objects that store actual relations. In
+addition it can index these relations using the ``zc.relation``
+infractructure, and therefore efficiently answer questions about the
+relations.
+
+The Relation field
+------------------
+
+First, some bookkeeping that can go away as soon as we release a fixed
+Grok. We first need to grok ftests to make sure we have the right
+utilities registered::
+
+  >>> import grok
+  >>> grok.testing.grok('z3c.relationfield.ftests')
+
+We previously defined an interface ``IItem`` with a relation field in
+it. We also defined a class ``Item`` that implements both ``IItem``
+and the special ``z3c.relationfield.interfaces.IHasRelations``
+interface. This marker interface is needed to let the relations be
+cataloged.
+
+Let's set up a test application in a container::
+
+  >>> root = getRootFolder()['root'] = TestApp()
+
+We make sure that this is the current site, so we can look up local
+utilities in it and so on::
+
+  >>> from zope.app.component.hooks import setSite
+  >>> setSite(root)
+
+We'll add an item ``a`` to it::
+
+  >>> root['a'] = Item()
+
+All items, including the one we just created, should have unique int
+ids as this is required to link to them::
+
+  >>> from zope import component
+  >>> from zope.app.intid.interfaces import IIntIds
+  >>> intids = component.getUtility(IIntIds)
+  >>> a_id = intids.getId(root['a'])
+  >>> a_id >= 0
+  True
+
+The relation is currently ``None``::
+
+  >>> root['a'].rel is None
+  True
+
+Now we can create an item ``b`` that links to item ``a``::
+
+  >>> from z3c.relationfield.relation import Relation
+  >>> b = Item()
+  >>> b.rel = Relation(a_id)
+
+We now store the ``b`` object, which will also set up its relation::
+
+  >>> root['b'] = b
+
+Let's examine the relation. First we'll check which attribute of the
+pointing object ('b') this relation is pointing from::
+
+  >>> root['b'].rel.from_attribute
+  'rel'
+
+We can ask for the object it is pointing at::
+
+  >>> to_object = root['b'].rel.to_object
+  >>> to_object.__name__
+  u'a'
+
+We can also get the object that is doing the pointing; the RelationProperty
+took care of setting this::
+
+  >>> from_object = root['b'].rel.from_object
+  >>> from_object.__name__
+  u'b'
+ 
+This object is also known as the ``__parent__``; again the RelationProperty
+took care of setting this::
+
+  >>> parent_object = root['b'].rel.__parent__
+  >>> parent_object is from_object
+  True
+
+The relation also knows about the interfaces of both the pointing object
+and the object that is being pointed at::
+
+  >>> sorted(root['b'].rel.from_interfaces)
+  [<InterfaceClass zope.annotation.interfaces.IAttributeAnnotatable>, 
+   <InterfaceClass zope.app.container.interfaces.IContained>,
+   <InterfaceClass z3c.relationfield.interfaces.IHasRelations>, 
+   <InterfaceClass z3c.relationfield.ftests.IItem>, 
+   <InterfaceClass persistent.interfaces.IPersistent>]
+
+  >>> sorted(root['b'].rel.to_interfaces)
+  [<InterfaceClass zope.annotation.interfaces.IAttributeAnnotatable>, 
+   <InterfaceClass zope.app.container.interfaces.IContained>, 
+   <InterfaceClass z3c.relationfield.interfaces.IHasRelations>,
+   <InterfaceClass z3c.relationfield.ftests.IItem>, 
+   <InterfaceClass persistent.interfaces.IPersistent>]
+
+We can also get the interfaces in flattened form::
+
+  >>> sorted(root['b'].rel.from_interfaces_flattened)
+  [<InterfaceClass zope.annotation.interfaces.IAnnotatable>, 
+   <InterfaceClass zope.annotation.interfaces.IAttributeAnnotatable>, 
+   <InterfaceClass zope.app.container.interfaces.IContained>, 
+   <InterfaceClass z3c.relationfield.interfaces.IHasRelations>,   
+   <InterfaceClass z3c.relationfield.ftests.IItem>, 
+   <InterfaceClass zope.location.interfaces.ILocation>, 
+   <InterfaceClass persistent.interfaces.IPersistent>, 
+   <InterfaceClass zope.interface.Interface>]
+  >>> sorted(root['b'].rel.to_interfaces_flattened)
+  [<InterfaceClass zope.annotation.interfaces.IAnnotatable>, 
+   <InterfaceClass zope.annotation.interfaces.IAttributeAnnotatable>, 
+   <InterfaceClass zope.app.container.interfaces.IContained>, 
+   <InterfaceClass z3c.relationfield.interfaces.IHasRelations>,
+   <InterfaceClass z3c.relationfield.ftests.IItem>, 
+   <InterfaceClass zope.location.interfaces.ILocation>, 
+   <InterfaceClass persistent.interfaces.IPersistent>, 
+   <InterfaceClass zope.interface.Interface>]
+
+Paths
+-----
+
+We can also obtain the path of the relation (both from where it is
+pointing as well as to where it is pointing). The path should be a
+human-readable reference to the object we are pointing at, suitable
+for serialization. In order to work with paths, we first need to set
+up an ``IObjectPath`` utility.
+
+Since in this example we only place objects into a single flat root
+container, the paths in this demonstration can be extremely simple:
+just the name of the object we point to. In more sophisticated
+applications a path would typically be a slash separated path, like
+``/foo/bar``::
+
+  >>> from z3c.objpath.interfaces import IObjectPath
+  >>> class ObjectPath(grok.GlobalUtility):
+  ...   grok.provides(IObjectPath)
+  ...   def path(self, obj):
+  ...       return obj.__name__
+  ...   def resolve(self, path):
+  ...       return root[path]
+
+  >>> grok.testing.grok_component('ObjectPath', ObjectPath)
+  True
+
+After this, we can get the path of the object the relation points to::
+
+  >>> root['b'].rel.to_path
+  u'a'
+
+We can also get the path of the object that is doing the pointing::
+
+  >>> root['b'].rel.from_path
+  u'b'
+
+Relation queries
+----------------
+
+Now that we have set up and indexed a relationship between ``a`` and
+``b``, we can issue queries using the relation catalog. Let's first
+get the catalog::
+
+  >>> from zc.relation.interfaces import ICatalog
+  >>> catalog = component.getUtility(ICatalog)
+
+Let's ask the catalog about the relation from ``b`` to ``a``::
+
+  >>> l = sorted(catalog.findRelations({'to_id': intids.getId(root['a'])}))
+  >>> l
+  [<z3c.relationfield.relation.Relation object at ...>]
+
+We look at this relation object again. We indeed go the right one::
+
+  >>> rel = l[0]
+  >>> rel.from_object.__name__
+  u'b'
+  >>> rel.to_object.__name__
+  u'a'
+  >>> rel.from_path
+  u'b'
+  >>> rel.to_path
+  u'a'
+
+Asking for relations to ``b`` will result in an empty list, as no such
+relations have been set up::
+
+  >>> sorted(catalog.findRelations({'to_id': intids.getId(root['b'])}))
+  []
+ 
+We can also issue more specific queries, restricting it on the
+attribute used for the relation field and the interfaces provided by
+the related objects. Here we look for all relations between ``b`` and
+``a`` that are stored in object attribute ``rel`` and are pointing
+from an object with interface ``IItem`` to another object with the
+interface ``IItem``::
+
+  >>> sorted(catalog.findRelations({
+  ...   'to_id': intids.getId(root['a']),
+  ...   'from_attribute': 'rel',
+  ...   'from_interfaces_flattened': IItem,
+  ...   'to_interfaces_flattened': IItem}))
+  [<z3c.relationfield.relation.Relation object at ...>]
+
+There are no relations stored for another attribute::
+
+  >>> sorted(catalog.findRelations({
+  ...   'to_id': intids.getId(root['a']),
+  ...   'from_attribute': 'foo'}))
+  []
+
+There are also no relations stored for a new interface we'll introduce
+here::
+
+  >>> class IFoo(IItem):
+  ...   pass
+
+  >>> sorted(catalog.findRelations({
+  ...   'to_id': intids.getId(root['a']),
+  ...   'from_interfaces_flattened': IItem,
+  ...   'to_interfaces_flattened': IFoo}))
+  []
+
+Changing the relation
+---------------------
+
+Let's create a new object ``c``::
+
+  >>> root['c'] = Item()
+  >>> c_id = intids.getId(root['c'])
+
+Nothing points to ``c`` yet::
+
+  >>> sorted(catalog.findRelations({'to_id': c_id})) 
+  []
+
+We currently have a relation from ``b`` to ``a``::
+
+  >>> sorted(catalog.findRelations({'to_id': intids.getId(root['a'])})) 
+  [<z3c.relationfield.relation.Relation object at ...>]
+
+We can change the relation to point at a new object ``c``::
+
+  >>> root['b'].rel = Relation(c_id)
+
+We need to send an ``IObjectModifiedEvent`` to let the catalog know we
+have changed the relations::
+
+  >>> from zope.event import notify
+  >>> notify(grok.ObjectModifiedEvent(root['b']))
+
+We should find now a single relation from ``b`` to ``c``::
+
+  >>> sorted(catalog.findRelations({'to_id': c_id})) 
+  [<z3c.relationfield.relation.Relation object at ...>]
+
+The relation to ``a`` should now be gone::
+
+  >>> sorted(catalog.findRelations({'to_id': intids.getId(root['a'])})) 
+  []
+
+Removing the relation
+---------------------
+
+We have a relation from ``b`` to ``c`` right now::
+
+  >>> sorted(catalog.findRelations({'to_id': c_id})) 
+  [<z3c.relationfield.relation.Relation object at ...>]
+
+We can clean up an existing relation from ``b`` to ``c`` by setting it
+to ``None``::
+
+  >>> root['b'].rel = None
+
+We need to send an ``IObjectModifiedEvent`` to let the catalog know we
+have changed the relations::
+
+  >>> notify(grok.ObjectModifiedEvent(root['b']))
+
+Setting the relation on ``b`` to ``None`` should remove that relation
+from the relation catalog, so we shouldn't be able to find it anymore::
+
+  >>> sorted(catalog.findRelations({'to_id': intids.getId(root['c'])})) 
+  []
+
+Let's reestablish the removed relation::
+
+  >>> root['b'].rel = Relation(c_id)
+  >>> notify(grok.ObjectModifiedEvent(root['b']))
+
+  >>> sorted(catalog.findRelations({'to_id': c_id})) 
+  [<z3c.relationfield.relation.Relation object at ...>]
+          
+Copying an object with relations
+--------------------------------
+
+Let's copy an object with relations::
+
+  >>> from zope.copypastemove.interfaces import IObjectCopier
+  >>> IObjectCopier(root['b']).copyTo(root)
+  u'b-2'
+  >>> u'b-2' in root
+  True
+
+Two relations to ``c`` can now be found, one from the original, and
+the other from the copy::
+
+  >>> l = sorted(catalog.findRelations({'to_id': c_id})) 
+  >>> len(l)
+  2
+  >>> l[0].from_path
+  u'b'
+  >>> l[1].from_path
+  u'b-2'
+
+Removing an object with relations
+---------------------------------
+
+We will remove ``b-2`` again. Its relation should automatically be removed
+from the catalog::
+
+  >>> del root['b-2']
+  >>> l = sorted(catalog.findRelations({'to_id': c_id}))
+  >>> len(l)
+  1
+  >>> l[0].from_path
+  u'b'
+
+Temporary relations
+-------------------
+
+If we have an import procedure where we import relations from some
+external source such as an XML file, it may be that we read a relation
+that points to an object that does not yet exist as it is yet to be
+imported. We provide a special ``TemporaryRelation`` for this case.  A
+``TemporaryRelation`` just contains the path of what it is pointing
+to, but does not resolve it yet. Let's use ``TemporaryRelation`` in a new
+object, creating a relation to ``a``::
+
+  >>> from z3c.relationfield import TemporaryRelation
+  >>> root['d'] = Item()
+  >>> root['d'].rel = TemporaryRelation('a')
+
+A modification event does not actually get this relation cataloged::
+
+  >>> before = sorted(catalog.findRelations({'to_id': a_id}))
+  >>> notify(grok.ObjectModifiedEvent(root['d']))
+  >>> after = sorted(catalog.findRelations({'to_id': a_id}))
+  >>> len(before) == len(after)
+  True
+
+We will now convert all temporary relations on ``d`` to real ones::
+
+  >>> from z3c.relationfield import realize_relations
+  >>> realize_relations(root['d'])
+  >>> notify(grok.ObjectModifiedEvent(root['d']))
+
+The relation will now show up in the catalog::
+
+  >>> after2 = sorted(catalog.findRelations({'to_id': a_id}))
+  >>> len(after2) > len(before)
+  True
+
+The relation widget
+-------------------
+
+The relation widget can be looked up for a relation field. The widget
+will render with a button that can be used to set the
+relation. Pressing this button will show a pop up window. The URL
+implementing the popup window is defined on a special view that needs
+to be available on the context object (that the relation is defined
+on). This view must be named "explorerurl". We'll provide one here::
+
+  >>> from zope.interface import Interface
+  >>> class ExplorerUrl(grok.View):
+  ...   grok.context(Interface)
+  ...   def render(self):
+  ...      return 'http://grok.zope.org'
+
+XXX in order to grok a view in the tests we need to supply the
+``BuiltinModuleInfo`` class with a ``package_dotted_name`` attribute.
+This should be fixed in Martian::
+
+  >>> from martian.scan import BuiltinModuleInfo
+  >>> BuiltinModuleInfo.package_dotted_name = 'foo'
+
+Now we can Grok the view::
+
+  >>> grok.testing.grok_component('ExplorerUrl', ExplorerUrl)
+  True
+
+Let's take a look at the relation widget now::
+
+  >>> from zope.publisher.browser import TestRequest
+  >>> from z3c.relationfield.widget import RelationWidget
+  >>> request = TestRequest()
+  >>> widget = RelationWidget(IItem['rel'], request)
+  >>> print widget()
+  <input class="textType" id="field.rel" name="field.rel" size="20" type="text" value=""  /><input class="buttonType" onclick="Z3C.relation.popup(this.previousSibling, 'http://grok.zope.org')" type="button" value="get relation" />
+
+Relation display widget
+-----------------------
+
+The display widget for relation will render a URL to the object it relates
+to. What this URL will be exactly can be controlled by defining a view
+on the object called "relationurl". Without such a view, the display
+widget will link directly to the object::
+
+  >>> from z3c.relationfield.widget import RelationDisplayWidget
+  >>> widget = RelationDisplayWidget(IItem['rel'], request)
+
+We have to set the widget up with some data::
+
+  >>> widget._data = rel 
+
+The widget will point to the plain URL of ``rel``'s ``to_object``::
+
+  >>> print widget()
+  <a href="http://127.0.0.1/root/a">a</a>
+
+Now we register a special ``relationurl`` view::
+
+  >>> class RelationUrl(grok.View):
+  ...   grok.context(Interface)
+  ...   def render(self):
+  ...      return self.url('edit')
+  >>> grok.testing.grok_component('RelationUrl', RelationUrl)
+  True
+
+We should now see a link postfixed with ``/edit``::
+
+  >>> print widget()
+  <a href="http://127.0.0.1/root/a/edit">a</a>

Added: z3c.relationfield/trunk/src/z3c/relationfield/__init__.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/__init__.py	                        (rev 0)
+++ z3c.relationfield/trunk/src/z3c/relationfield/__init__.py	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,4 @@
+from relation import Relation, TemporaryRelation
+from schema import Relation as RelationField
+from event import realize_relations
+

Added: z3c.relationfield/trunk/src/z3c/relationfield/configure.zcml
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/configure.zcml	                        (rev 0)
+++ z3c.relationfield/trunk/src/z3c/relationfield/configure.zcml	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,10 @@
+<configure
+  xmlns="http://namespaces.zope.org/zope"
+  xmlns:grok="http://namespaces.zope.org/grok">
+
+  <include package="grok" file="meta.zcml" />
+  <include package="grok" />
+
+  <grok:grok package="."/>
+  
+</configure>

Added: z3c.relationfield/trunk/src/z3c/relationfield/event.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/event.py	                        (rev 0)
+++ z3c.relationfield/trunk/src/z3c/relationfield/event.py	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,96 @@
+import grok
+
+from zope.interface import providedBy
+from zope.schema import getFields
+from zope import component
+from zope.app.intid.interfaces import IIntIds
+from zc.relation.interfaces import ICatalog
+
+from z3c.relationfield.schema import Relation as RelationField
+from z3c.relationfield.relation import Relation
+from z3c.relationfield.interfaces import (IHasRelations,
+                                         IRelation, ITemporaryRelation)
+
+ at grok.subscribe(IHasRelations, grok.IObjectAddedEvent)
+def addRelations(obj, event):
+    """Register relations.
+
+    Any relation object on the object will be added.
+    """
+    for name, relation in _relations(obj):
+         _setRelation(obj, name, relation)
+
+ at grok.subscribe(IHasRelations, grok.IObjectRemovedEvent)
+def removeRelations(obj, event):
+    """Remove relations.
+
+    Any relation object on the object will be removed from the catalog.
+    """
+    catalog = component.getUtility(ICatalog)
+ 
+    for name, relation in _relations(obj):
+        if relation is not None:
+            catalog.unindex(relation)
+
+ at grok.subscribe(IHasRelations, grok.IObjectModifiedEvent)
+def updateRelations(obj, event):
+    """Re-register relations, after they have been changed.
+    """
+    catalog = component.getUtility(ICatalog)
+    intids = component.getUtility(IIntIds)
+
+    # remove previous relations coming from id (now have been overwritten)
+    for relation in catalog.findRelations({'from_id': intids.getId(obj)}):
+        catalog.unindex(relation)    
+
+    # add new relations
+    addRelations(obj, event)
+
+def realize_relations(obj):
+    """Given an object, convert any temporary relatiosn on it to real ones.
+    """
+    for name, relation in _potential_relations(obj):
+        if ITemporaryRelation.providedBy(relation):
+            setattr(obj, name, relation.convert())
+
+def _setRelation(obj, name, value):
+    """Set a relation on an object.
+
+    Sets up various essential attributes on the relation.
+    """
+    # if the Relation is None, we're done
+    if value is None:
+        return
+    # make sure relation has a __parent__ so we can make an intid for it
+    value.__parent__ = obj
+    # also set from_object to parent object
+    value.from_object = obj
+    # and the attribute to the attribute name
+    value.from_attribute = name
+    # now we can create an intid for the relation
+    intids = component.getUtility(IIntIds)
+    id = intids.register(value)
+    # and index the relation with the catalog
+    catalog = component.getUtility(ICatalog)
+    catalog.index_doc(id, value)
+
+def _relations(obj):
+    """Given an object, return tuples of name, relation.
+
+    Only real relations are returned, not temporary relations.
+    """
+    for name, relation in _potential_relations(obj):
+        if IRelation.providedBy(relation):
+            yield name, relation
+
+def _potential_relations(obj):
+    """Given an object return tuples of name, relation.
+
+    Returns both IRelation attributes as well as ITemporaryRelation
+    attributes.
+    """
+    for iface in providedBy(obj).flattened():
+        for name, field in getFields(iface).items():
+            if isinstance(field, RelationField):
+                relation = getattr(obj, name)
+                yield name, relation

Added: z3c.relationfield/trunk/src/z3c/relationfield/ftesting.zcml
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/ftesting.zcml	                        (rev 0)
+++ z3c.relationfield/trunk/src/z3c/relationfield/ftesting.zcml	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,34 @@
+<configure
+   xmlns="http://namespaces.zope.org/zope"
+   xmlns:grok="http://namespaces.zope.org/grok"
+   i18n_domain="z3c.relationfield"
+   package="z3c.relationfield"
+   >
+
+  <include package="grok" />
+  <include package="z3c.relationfield" />
+
+  <!-- Typical functional testing security setup -->
+  <securityPolicy
+      component="zope.app.securitypolicy.zopepolicy.ZopeSecurityPolicy"
+      />
+
+  <unauthenticatedPrincipal
+      id="zope.anybody"
+      title="Unauthenticated User"
+      />
+  <grant
+      permission="zope.View"
+      principal="zope.anybody"
+      />
+  <principal
+      id="zope.mgr"
+      title="Manager"
+      login="mgr"
+      password="mgrpw"
+      />
+
+  <role id="zope.Manager" title="Site Manager" />
+  <grantAll role="zope.Manager" />
+  <grant role="zope.Manager" principal="zope.mgr" />
+</configure>

Added: z3c.relationfield/trunk/src/z3c/relationfield/ftests.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/ftests.py	                        (rev 0)
+++ z3c.relationfield/trunk/src/z3c/relationfield/ftests.py	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,39 @@
+import unittest
+
+import grok
+
+import zope.interface
+
+from zope.app.intid import IntIds
+from zope.app.intid.interfaces import IIntIds
+
+from z3c.relationfield.index import RelationCatalog
+from z3c.relationfield.interfaces import IHasRelations
+from z3c.relationfield import schema
+
+import zope.testbrowser.browser
+import zope.testbrowser.testing
+from zope.app.testing.functional import FunctionalDocFileSuite
+from z3c.relationfield.testing import FunctionalLayer
+
+class IItem(zope.interface.Interface):
+    rel = schema.Relation(title=u"Relation")
+ 
+class Item(grok.Model):
+    grok.implements(IItem, IHasRelations)
+
+    def __init__(self):
+        self.rel = None
+
+class TestApp(grok.Application, grok.Container):
+    grok.local_utility(IntIds, provides=IIntIds)
+    grok.local_utility(RelationCatalog)
+  
+def test_suite():
+    globs = { 'TestApp': TestApp, 'IItem': IItem, 'Item': Item }
+    readme = FunctionalDocFileSuite(
+        'README.txt',
+        globs = globs,
+        )
+    readme.layer = FunctionalLayer
+    return unittest.TestSuite([readme])

Added: z3c.relationfield/trunk/src/z3c/relationfield/index.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/index.py	                        (rev 0)
+++ z3c.relationfield/trunk/src/z3c/relationfield/index.py	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,40 @@
+import grok
+
+import BTrees
+
+from zope import component
+from zope.app.intid.interfaces import IIntIds
+
+from zc.relation.catalog import Catalog
+from zc.relation.interfaces import ICatalog
+
+from z3c.relationfield.interfaces import IRelation
+
+def dump(obj, catalog, cache):
+    intids = cache.get('intids')
+    if intids is None:
+        intids = cache['intids'] = component.getUtility(IIntIds)
+    return intids.getId(obj)
+    
+def load(token, catalog, cache):
+    intids = cache.get('intids')
+    if intids is None:
+        intids = cache['intids'] = component.getUtility(IIntIds)
+    return intids.getObject(token)
+    
+class RelationCatalog(Catalog, grok.LocalUtility):
+    grok.provides(ICatalog)
+
+    def __init__(self):
+        Catalog.__init__(self, dump, load)
+        self.addValueIndex(IRelation['from_id'])
+        self.addValueIndex(IRelation['to_id'])
+        self.addValueIndex(IRelation['from_attribute'],
+                           btree=BTrees.family32.OI)
+        self.addValueIndex(IRelation['from_interfaces_flattened'],
+                           multiple=True,
+                           btree=BTrees.family32.OI)
+        self.addValueIndex(IRelation['to_interfaces_flattened'],
+                           multiple=True,
+                           btree=BTrees.family32.OI)
+

Added: z3c.relationfield/trunk/src/z3c/relationfield/interfaces.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/interfaces.py	                        (rev 0)
+++ z3c.relationfield/trunk/src/z3c/relationfield/interfaces.py	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,60 @@
+from zope.interface import Interface, Attribute
+
+class IRelation(Interface):
+    from_object = Attribute("The object this relation is pointing from.")
+
+    from_id = Attribute("Id of the object this relation is pointing from.")
+    
+    from_path = Attribute("The path of the from object.")
+
+    from_interfaces = Attribute("The interfaces of the from object.")
+
+    from_interfaces_flattened = Attribute(
+        "Interfaces of the from object, flattened. "
+        "This includes all base interfaces.")
+    
+    from_attribute = Attribute("The name of the attribute of the from object.")
+    
+    to_object = Attribute("The object this relation is pointing to.")
+
+    to_id = Attribute("Id of the object this relation is pointing to.")
+
+    to_path = Attribute("The path of the object this relation is pointing to.")
+
+    to_interfaces = Attribute("The interfaces of the to-object.")
+
+    to_interfaces_flattened = Attribute(
+        "The interfaces of the to object, flattened. "
+        "This includes all base interfaces.")
+
+class ITemporaryRelation(Interface):
+    """A temporary relation.
+
+    When importing relations from XML, we cannot resolve them into
+    true Relation objects yet, as it may be that the object that is
+    being related to has not yet been loaded. Instead we create
+    a TemporaryRelation object that can be converted into a real one
+    after the import has been concluded.
+    """
+    def convert():
+        """Convert temporary relation into a real one.
+
+        Returns real relation object
+        """
+
+class IHasRelations(Interface):
+    """Marker interface indicating that the object has relations.
+
+    Use this interface to make sure that the relations get added and
+    removed from the catalog when appropriate.
+    """
+
+class IRelationInfo(Interface):
+    """Relationship information for an object.
+    """
+
+    def createRelation():
+        """Create a relation object pointing to this object.
+
+        Returns an object that provides IRelation.
+        """

Added: z3c.relationfield/trunk/src/z3c/relationfield/relation.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/relation.py	                        (rev 0)
+++ z3c.relationfield/trunk/src/z3c/relationfield/relation.py	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,105 @@
+import grok
+
+from persistent import Persistent
+from zope.interface import implements, providedBy, Declaration
+from zope import component
+from zope.app.intid.interfaces import IIntIds
+
+from z3c.objpath.interfaces import IObjectPath
+
+from z3c.relationfield.interfaces import (IRelation, ITemporaryRelation,
+                                          IRelationInfo)
+
+class Relation(Persistent):
+    implements(IRelation)
+
+    def __init__(self, to_id):
+        self.to_id = to_id
+        # these will be set automatically by RelationProperty
+        self.from_object = None
+        self.__parent__ = None
+        self.from_attribute = None
+
+    @property
+    def from_id(self):
+        intids = component.getUtility(IIntIds)
+        return intids.getId(self.from_object)
+
+    @property
+    def from_path(self):
+        return _path(self.from_object)
+
+    @property
+    def from_interfaces(self):
+        return providedBy(self.from_object)
+
+    @property
+    def from_interfaces_flattened(self):
+        return _interfaces_flattened(self.from_interfaces)
+        
+    @property
+    def to_object(self):
+        return _object(self.to_id)
+
+    @property
+    def to_path(self):
+        return _path(self.to_object)
+
+    @property
+    def to_interfaces(self):
+        return providedBy(self.to_object)
+
+    @property
+    def to_interfaces_flattened(self):
+        return _interfaces_flattened(self.to_interfaces)
+
+    def __cmp__(self, other):
+        if other is None:
+            return cmp(self.to_id, None)
+        return cmp(self.to_id, other.to_id)
+
+class TemporaryRelation(Persistent):
+    """A relation that isn't fully formed yet.
+
+    It needs to be finalized afterwards, when we are sure all potential
+    target objects exist.
+    """
+    grok.implements(ITemporaryRelation)
+    
+    def __init__(self, to_path):
+        self.to_path = to_path
+
+    def convert(self):
+        object_path = component.getUtility(IObjectPath)
+        # XXX what if we have a broken relation?
+        to_object = object_path.resolve(self.to_path)
+        intids = component.getUtility(IIntIds)
+        to_id = intids.getId(to_object)
+        return Relation(to_id)
+    
+def _object(id):
+    intids = component.getUtility(IIntIds)
+    try:
+        return intids.getObject(id)
+    except KeyError:
+        # XXX catching this error is not the right thing to do.
+        # instead, breaking a relation by removing an object should
+        # be caught and the relation should be adjusted that way.
+        return None
+
+def _path(obj):
+    if obj is None:
+        return ''
+    object_path = component.getUtility(IObjectPath)
+    return object_path.path(obj)
+
+def _interfaces_flattened(interfaces):
+    return Declaration(*interfaces).flattened()
+
+class RelationInfoBase(grok.Adapter):
+    grok.baseclass()
+    grok.provides(IRelationInfo)
+    
+    def createRelation(self):
+        intids = component.getUtility(IIntIds)
+        return Relation(intids.getId(self.context))

Added: z3c.relationfield/trunk/src/z3c/relationfield/schema.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/schema.py	                        (rev 0)
+++ z3c.relationfield/trunk/src/z3c/relationfield/schema.py	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,34 @@
+import grok
+
+from lxml import etree
+
+from zope import schema
+from zope.interface import implements
+from zope.schema.interfaces import IField
+from zope.schema import Field
+
+from z3c.objpath.interfaces import IObjectPath
+import z3c.schema2xml
+
+from z3c.relationfield.relation import TemporaryRelation
+
+class IRelation(IField):
+    pass
+
+class Relation(Field):
+    implements(IRelation)
+
+class RelationGenerator(grok.Adapter):
+    grok.context(IRelation)
+    grok.implements(z3c.schema2xml.IXMLGenerator)
+
+    def output(self, container, value):
+        element = etree.SubElement(container, self.context.__name__)
+        if value is not None:
+            element.text = value.to_path
+
+    def input(self, element):
+        if element.text is None:
+            return None
+        path = element.text
+        return TemporaryRelation(path)

Added: z3c.relationfield/trunk/src/z3c/relationfield/static/relation.js
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/static/relation.js	                        (rev 0)
+++ z3c.relationfield/trunk/src/z3c/relationfield/static/relation.js	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,82 @@
+// create the top-level Z3C namespace if needed
+if (typeof Z3C == "undefined" || !Z3C) {
+    var Z3C = {};
+}
+
+// create a new namespace (under Z3C)
+Z3C.namespace = function(name) {
+    var ns = Z3C;
+    var parts = name.split(".");
+    if (parts[0] == "Z3C") {
+        parts = parts.slice(1);
+    }
+    for (var i = 0; i < parts.length; i++) {
+        var part = parts[i];
+        ns[part] = ns[part] || {};
+        ns = ns[part];
+    }
+    return ns;
+};
+
+(function() {
+    Z3C.namespace('relation');
+    
+    var winwidth = 750;
+    var winheight = 500;
+    var window_id = 0;
+
+    var features2string = function(features) {
+        var features_l = [];
+        for (key in features) {
+	    if (!features.hasOwnProperty(key)) {
+                continue;
+            }
+            features_l.push(key + '=' + features[key]);
+        };
+        return features_l.join(',');
+    }
+    
+    Z3C.relation.RelationCreator = function(el, url) {
+        this._el = el;
+        this._url = url;
+    };
+    
+    Z3C.relation.RelationCreator.prototype.show = function() {
+        var leftpos = (screen.width - winwidth) / 2;
+        var toppos = (screen.height - winheight) / 2;
+        
+        var features = {
+            'toolbar': 'yes',
+            'status': 'yes',
+            'scrollbars': 'yes',
+            'resizeable': 'yes',
+            'width': winwidth,
+            'height': winheight,
+            'left': leftpos,
+            'top': toppos
+        };
+
+        this._win = window.open(this._url, 'relation_window_' + window_id,
+                                features2string(features));
+        this._win.focus();
+        // the popup window has to call call relation_creator.setRelations
+        // with a list of strings to set the relations
+        this._win.relation_creator = this;
+        // increase window id so we open a new window each time
+        window_id++;
+    };
+
+    Z3C.relation.RelationCreator.prototype.setRelations = function(values) {
+        if (values.length > 0) {
+            this._el.setAttribute('value', values[0]);
+        }
+        // break potential circular reference
+        delete this._win.relation_creator;
+        this._win.close();
+    };
+
+    Z3C.relation.popup = function(el, url) {
+        var o = new Z3C.relation.RelationCreator(el, url);
+        o.show();
+    };
+})();

Added: z3c.relationfield/trunk/src/z3c/relationfield/testing.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/testing.py	                        (rev 0)
+++ z3c.relationfield/trunk/src/z3c/relationfield/testing.py	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,7 @@
+import os
+import z3c.relationfield
+from zope.app.testing.functional import ZCMLLayer
+
+ftesting_zcml = os.path.join(
+    os.path.dirname(z3c.relationfield.__file__), 'ftesting.zcml')
+FunctionalLayer = ZCMLLayer(ftesting_zcml, __name__, 'FunctionalLayer')

Added: z3c.relationfield/trunk/src/z3c/relationfield/widget.py
===================================================================
--- z3c.relationfield/trunk/src/z3c/relationfield/widget.py	                        (rev 0)
+++ z3c.relationfield/trunk/src/z3c/relationfield/widget.py	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,69 @@
+import grok
+from xml.sax.saxutils import escape
+
+from zope.app.form.interfaces import IInputWidget, IDisplayWidget
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.app.form.browser import TextWidget, DisplayWidget
+from zope import component
+from zope.component.interfaces import ComponentLookupError
+from zope.app.form.browser.widget import renderElement
+
+from z3c.objpath.interfaces import IObjectPath
+
+from z3c.relationfield.schema import IRelation
+from z3c.relationfield.interfaces import IRelationInfo
+
+class RelationWidget(grok.MultiAdapter, TextWidget):
+    grok.adapts(IRelation, IBrowserRequest)
+    grok.provides(IInputWidget)
+
+    def __call__(self):
+        result = TextWidget.__call__(self)
+        explorer_url = component.getMultiAdapter((self.context.context,
+                                                 self.request),
+                                                 name="explorerurl")()
+        result += renderElement(
+            'input', type='button', value='get relation',
+            onclick="Z3C.relation.popup(this.previousSibling, '%s')" %
+            explorer_url)
+        return result
+    
+    def _toFieldValue(self, input):
+        if not input:
+            return None
+        # convert path to Relation object
+        obj = self.resolve(input)
+        # XXX if obj is none, cannot create path
+        return IRelationInfo(obj).createRelation()
+
+    def _toFormValue(self, value):
+        if value is None:
+            return ''
+        return value.to_path
+
+    def resolve(self, path):
+        object_path = component.getUtility(IObjectPath)
+        return object_path.resolve(path)
+
+
+class RelationDisplayWidget(grok.MultiAdapter, DisplayWidget):
+    grok.adapts(IRelation, IBrowserRequest)
+    grok.provides(IDisplayWidget)
+
+    def __call__(self):
+        if self._renderedValueSet():
+            value = self._data
+        else:
+            value = self.context.default
+        if value == self.context.missing_value:
+            return ""
+        to_object = value.to_object
+        try:
+            to_url = component.getMultiAdapter((to_object, self.request),
+                                               name="relationurl")()
+        except ComponentLookupError:
+            to_url = grok.url(self.request, to_object)
+        return '<a href="%s">%s</a>' % (
+            to_url,
+            escape(value.to_path))
+

Added: z3c.relationfield/trunk/z3c.objpath/buildout.cfg
===================================================================
--- z3c.relationfield/trunk/z3c.objpath/buildout.cfg	                        (rev 0)
+++ z3c.relationfield/trunk/z3c.objpath/buildout.cfg	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,14 @@
+[buildout]
+develop = .
+parts = test devpython
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = z3c.objpath
+
+# installs bin/devpython to do simple interpreter tests
+[devpython]
+recipe = zc.recipe.egg
+interpreter = devpython
+eggs = z3c.objpath
+

Added: z3c.relationfield/trunk/z3c.objpath/setup.py
===================================================================
--- z3c.relationfield/trunk/z3c.objpath/setup.py	                        (rev 0)
+++ z3c.relationfield/trunk/z3c.objpath/setup.py	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,24 @@
+from setuptools import setup, find_packages
+import sys, os
+
+setup(
+    name='z3c.objpath',
+    version='0.1dev',
+    description="Paths to to objects.",
+    long_description="""""",
+    classifiers=[],
+    keywords='',
+    author='Martijn Faassen',
+    author_email='faassen at startifact.com',
+    url='http://dev.inghist.nl/eggs/',
+    license='',
+    packages=find_packages('src'),
+    package_dir={'': 'src'},
+    include_package_data=True,
+    zip_safe=False,
+    install_requires=[
+        'setuptools',
+        'zope.interface',
+        ],
+    entry_points={},
+    )

Added: z3c.relationfield/trunk/z3c.objpath/src/z3c/__init__.py
===================================================================
--- z3c.relationfield/trunk/z3c.objpath/src/z3c/__init__.py	                        (rev 0)
+++ z3c.relationfield/trunk/z3c.objpath/src/z3c/__init__.py	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,7 @@
+# this is a namespace package
+try:
+    import pkg_resources
+    pkg_resources.declare_namespace(__name__)
+except ImportError:
+    import pkgutil
+    __path__ = pkgutil.extend_path(__path__, __name__)

Added: z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/README.txt
===================================================================
--- z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/README.txt	                        (rev 0)
+++ z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/README.txt	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,100 @@
+ObjectPath
+==========
+
+This package contains two things::
+
+* the ``z3c.objpath.interfaces.IObjectPath`` interface.
+
+* some helper functions to construct (relative) object paths, in
+  ``z3c.objpath.path``.
+
+The idea is that a particular application can implement a utility that
+fulfills the ``IObjectPath`` interface, so that it is possible to
+construct paths to objects in a uniform way. The implementation may be
+done with ``zope.traversing``, but in some cases you want
+application-specific object paths. In this case, the functions in
+``z3c.objpath.path`` might be useful.
+
+We'll have a simple item::
+
+  >>> class Item(object):
+  ...   __name__ = None
+  ...   __parent__ = None
+  ...   def __repr__(self):
+  ...     return '<Item %s>' % self.__name__
+
+Let's create a simple container-like object::
+
+  >>> class Container(Item):
+  ...   def __init__(self):
+  ...     self._d = {}
+  ...   def __setitem__(self, name, obj):
+  ...     self._d[name] = obj
+  ...     obj.__name__ = name
+  ...     obj.__parent__ = self
+  ...   def __getitem__(self, name):
+  ...     return self._d[name]
+  ...   def __repr__(self):
+  ...     return '<Container %s>' % self.__name__
+
+Now let's create a structure::
+
+  >>> root = Container()
+  >>> root.__name__ = 'root'
+  >>> data = root['data'] = Container()
+  >>> a = data['a'] = Container()
+  >>> b = data['b'] = Container()
+  >>> c = data['c'] = Item()
+  >>> d = a['d'] = Item()
+  >>> e = a['e'] = Container()
+  >>> f = e['f'] = Item()
+  >>> g = b['g'] = Item()
+
+We will now exercise two functions, ``path`` and ``resolve``, which
+are inverses of each other::
+
+  >>> from z3c.objpath.path import path, resolve
+
+We can create a path to ``a`` from ``root``::
+
+  >>> path(root, a)
+  '/root/data/a'
+
+We can also resolve it again::
+
+  >>> resolve(root, '/root/data/a')
+  <Container a>
+
+We can also create a path to ``a`` from ``data``::
+
+  >>> path(data, a)
+  '/data/a'
+
+And resolve it again::
+
+  >>> resolve(data, '/data/a')
+  <Container a>
+
+We can make a deeper path::
+
+  >>> path(root, f)
+  '/root/data/a/e/f'
+
+And resolve it::
+
+  >>> resolve(root, '/root/data/a/e/f')
+  <Item f>
+
+We get an error if we cannot construct a path::
+
+  >>> path(e, a)
+  Traceback (most recent call last):
+   ...
+  ValueError: Cannot create path for <Container a>
+
+We also get an error if we cannot resolve a path::
+
+  >>> resolve(root, '/root/data/a/f/e')
+  Traceback (most recent call last):
+   ...
+  ValueError: Cannot resolve path /root/data/a/f/e

Added: z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/__init__.py
===================================================================
--- z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/__init__.py	                        (rev 0)
+++ z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/__init__.py	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1 @@
+from path import path, resolve

Added: z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/interfaces.py
===================================================================
--- z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/interfaces.py	                        (rev 0)
+++ z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/interfaces.py	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,27 @@
+from zope.interface import Interface
+
+class IObjectPath(Interface):
+    """Path representation for objects.
+    """
+    def path(obj):
+        """Give the path representation of obj.
+
+        obj - object in a hierarchy of IContainer objects.
+
+        The path is defined by the application and may be relative
+        to the application root.
+
+        Returns the path.
+        
+        If no path to the object can be made, raise a ValueError.
+        """
+
+    def resolve(path):
+        """Given a path resolve to an object.
+
+        path - a path as created with path()
+
+        Returns the object that the path referred to.
+
+        If the path cannot be resolved to an object, raise a ValueError.
+        """

Added: z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/path.py
===================================================================
--- z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/path.py	                        (rev 0)
+++ z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/path.py	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,33 @@
+"""This module contains some functions that may be helpful in the
+implementation of IObjectPath interface.
+"""
+
+def path(root, obj):
+    steps = []
+    orig_obj = obj
+    while obj is not None:
+        steps.append(obj.__name__)
+        if obj is root:
+            break
+        obj = obj.__parent__
+    else:
+        raise ValueError("Cannot create path for %s" % orig_obj)
+    steps.reverse()
+    return '/' + '/'.join(steps)
+
+def resolve(root, path):
+    steps = path.split('/')
+    assert steps[0] == ''
+    obj = root
+    if steps[1] == '':
+        return root
+    assert steps[1] == root.__name__
+    steps = steps[2:]
+    for step in steps:
+        try:
+            obj = obj[step]
+        except KeyError:
+            raise ValueError("Cannot resolve path %s" % path)
+    return obj
+
+        

Added: z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/tests.py
===================================================================
--- z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/tests.py	                        (rev 0)
+++ z3c.relationfield/trunk/z3c.objpath/src/z3c/objpath/tests.py	2008-04-15 11:14:31 UTC (rev 85370)
@@ -0,0 +1,15 @@
+import unittest
+
+from zope.testing import doctest
+
+def test_suite():
+    optionflags = (
+        doctest.ELLIPSIS
+        | doctest.REPORT_NDIFF
+        | doctest.NORMALIZE_WHITESPACE
+        )
+
+    return unittest.TestSuite([
+        doctest.DocFileSuite(
+            'README.txt', optionflags=optionflags)
+        ])



More information about the Checkins mailing list