[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