[Checkins] SVN: z3c.relationfield/trunk/ Trying another initial import.

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


Log message for revision 85372:
  Trying another initial import.
  

Changed:
  A   z3c.relationfield/trunk/
  A   z3c.relationfield/trunk/TODO.txt
  A   z3c.relationfield/trunk/buildout.cfg
  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

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

Added: z3c.relationfield/trunk/buildout.cfg
===================================================================
--- z3c.relationfield/trunk/buildout.cfg	                        (rev 0)
+++ z3c.relationfield/trunk/buildout.cfg	2008-04-15 11:15:55 UTC (rev 85372)
@@ -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/setup.py
===================================================================
--- z3c.relationfield/trunk/setup.py	                        (rev 0)
+++ z3c.relationfield/trunk/setup.py	2008-04-15 11:15:55 UTC (rev 85372)
@@ -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:15:55 UTC (rev 85372)
@@ -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:15:55 UTC (rev 85372)
@@ -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:15:55 UTC (rev 85372)
@@ -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:15:55 UTC (rev 85372)
@@ -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:15:55 UTC (rev 85372)
@@ -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:15:55 UTC (rev 85372)
@@ -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:15:55 UTC (rev 85372)
@@ -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:15:55 UTC (rev 85372)
@@ -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:15:55 UTC (rev 85372)
@@ -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:15:55 UTC (rev 85372)
@@ -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:15:55 UTC (rev 85372)
@@ -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:15:55 UTC (rev 85372)
@@ -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:15:55 UTC (rev 85372)
@@ -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:15:55 UTC (rev 85372)
@@ -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))
+



More information about the Checkins mailing list