Welcome, guest | Sign In | My Account | Store | Cart

Notice! PyPM is being replaced with the ActiveState Platform, which enhances PyPM’s build and deploy capabilities. Create your free Platform account to download ActivePython or customize Python with the packages you require and get automatic updates.

Download
ActivePython
INSTALL>
pypm install infrae.plone.relations.schema

How to install infrae.plone.relations.schema

  1. Download and install ActivePython
  2. Open Command Prompt
  3. Type pypm install infrae.plone.relations.schema
 Python 2.7Python 3.2Python 3.3
Windows (32-bit)
1.0 Available View build log
Windows (64-bit)
1.0 Available View build log
Mac OS X (10.5+)
1.0 Available View build log
Linux (32-bit)
1.0 Available View build log
Linux (64-bit)
1.0 Available View build log
 
License
GPL
Lastest release
version 1.0 on Jan 5th, 2011

The purpose of this extension is to provide a Zope 3 schema for plone relations. This has been tested with Plone 2.5 and Plone 3.

Interface definition

A new field:

>>> from infrae.plone.relations.schema import PloneRelation

A simple interface with an field:

>>> from zope.interface import Interface, implements
>>> class IContent(Interface):

System Message: ERROR/3 (<string>, line 22)

Inconsistent literal block quoting.

... """Sample interface.""" ... relation = PloneRelation(relation="my relation")

I can get the field from the interface:

>>> r_field = IContent.get('relation')

And this is a field:

>>> from zope.interface.verify import verifyObject
>>> from zope.schema.interfaces import IField
>>> verifyObject(IField, r_field)

System Message: ERROR/3 (<string>, line 34)

Inconsistent literal block quoting.

True

Now, a field to look up reverse relations:

>>> class IBackContent(Interface):

System Message: ERROR/3 (<string>, line 39)

Inconsistent literal block quoting.

... """Sample interface.""" ... relation = PloneRelation(relation="my relation", ... reverse=True)

Create a simple content for test purpose

And a simple implementation:

>>> from OFS.Folder import Folder
>>> class BaseContent(Folder):

System Message: ERROR/3 (<string>, line 51)

Inconsistent literal block quoting.

... def __init__(self, id): ... super(BaseContent, self).__init__() ... self.id = id ... def UID(self): ... return 'uid-%s' % self.id

UID are used by the context factory.

Standard base class:

>>> class MyContent(BaseContent):

System Message: ERROR/3 (<string>, line 62)

Inconsistent literal block quoting.

... implements(IContent)

And:

>>> class MyBackContent(BaseContent):

System Message: ERROR/3 (<string>, line 67)

Inconsistent literal block quoting.

... implements(IBackContent)

Now, create some items:

>>> for id in range(1, 5):

System Message: ERROR/3 (<string>, line 73)

Inconsistent literal block quoting.

... name = 'it%d' % id ... item = MyContent(name) ... self.portal._setObject(name, item) 'it1' 'it2' 'it3' 'it4' >>> it1 = self.portal.it1 >>> it2 = self.portal.it2 >>> it3 = self.portal.it3 >>> it4 = self.portal.it4

>>> itb1 = MyBackContent('itb1')
>>> self.portal._setObject('itb1', itb1)
'itb1'
>>> itb1 = self.portal.itb1
Utility to display relation

An helper to display a relation:

>>> def display(rels):

System Message: ERROR/3 (<string>, line 97)

Inconsistent literal block quoting.

... for rel in rels: ... print "Objects: %s" % list(rel['objects']) ... if rel.has_key("context"): ... print "Context: %s" % rel['context'] ... if not rels: ... print "Empty"

Simple field use

Direct set, and reverse access

And try some validation on data. Data is a list of dictionary, representing all relations of the field. In the dictionary:

  • objects: represent a list of object for the relation;
  • context: may be an object stored as context of the relation.

Example:

>>> bad_relation1 = [{'bad': None},]
>>> r_field.validate(bad_relation1)

System Message: ERROR/3 (<string>, line 122)

Inconsistent literal block quoting.

Traceback (most recent call last): ... ValidationError: Invalid structure >>> good_relation = [{'objects': [it2, itb1]},] >>> r_field.validate(good_relation)

And set the field:

>>> r_field.set(it1, good_relation)

And get data from the field:

>>> relation = r_field.get(it1)
>>> relation

System Message: ERROR/3 (<string>, line 136)

Inconsistent literal block quoting.

[{'objects': <plone.relations.relationships.IntIdSubObjectWrapper object at ...>}] >>> display(relation) Objects: [<MyContent at /plone/it2>, <MyBackContent at /plone/itb1>]

Now, we can ask from itb1 content, which has a reverse field:

>>> rb_field = IBackContent.get('relation')
>>> relation = rb_field.get(itb1)
>>> relation

System Message: ERROR/3 (<string>, line 145)

Inconsistent literal block quoting.

[{'objects': <plone.relations.relationships.IntIdSubObjectWrapper object at ...>}] >>> display(relation) Objects: [<MyContent at /plone/it1>]

We update the relation:

>>> good_relation = [{'objects': [it2, it3]},]
>>> r_field.set(it1, good_relation)

So change is reflected:

>>> display(r_field.get(it1))

System Message: ERROR/3 (<string>, line 157)

Inconsistent literal block quoting.

Objects: [<MyContent at /plone/it2>, <MyContent at /plone/it3>]

And there is no more relation in the reverse field:

>>> rb_field = IBackContent.get('relation')
>>> rb_field.get(itb1)

System Message: ERROR/3 (<string>, line 164)

Inconsistent literal block quoting.

[]

Now, set on reverse field:

>>> good_relation = [{'objects': [it1, it2]}]
>>> rb_field.set(itb1, good_relation)
>>> display(rb_field.get(itb1))

System Message: ERROR/3 (<string>, line 171)

Inconsistent literal block quoting.

Objects: [<MyContent at /plone/it1>, <MyContent at /plone/it2>]

And on the normal:

>>> display(r_field.get(it1))

System Message: ERROR/3 (<string>, line 176)

Inconsistent literal block quoting.

Objects: [<MyContent at /plone/it2>, <MyContent at /plone/it3>] Objects: [<MyBackContent at /plone/itb1>]

Deletion

You can delete value by setting the relation to an empty list []:

>>> display(r_field.get(it2))

System Message: ERROR/3 (<string>, line 186)

Inconsistent literal block quoting.

Objects: [<MyBackContent at /plone/itb1>] >>> r_field.set(it2, []) >>> display(r_field.get(it2)) Empty >>> display(rb_field.get(itb1)) Objects: [<MyContent at /plone/it1>]

And:

>>> display(r_field.get(it1))

System Message: ERROR/3 (<string>, line 196)

Inconsistent literal block quoting.

Objects: [<MyContent at /plone/it2>, <MyContent at /plone/it3>] Objects: [<MyBackContent at /plone/itb1>] >>> r_field.set(it1, []) >>> display(r_field.get(it1)) Empty >>> display(rb_field.get(itb1)) Empty

Field independence

One other relation schema:

>>> class IComplexContent(Interface):

System Message: ERROR/3 (<string>, line 211)

Inconsistent literal block quoting.

... """A content with two relation.""" ... relation1 = PloneRelation(relation="relation1") ... relation2 = PloneRelation(relation="relation2")

And the related content:

>>> class MyComplexContent(BaseContent):

System Message: ERROR/3 (<string>, line 218)

Inconsistent literal block quoting.

... implements(IComplexContent)

Create three objects like this:

>>> itcx1 = MyComplexContent("itcx1")
>>> self.portal._setObject("itcx1", itcx1)

System Message: ERROR/3 (<string>, line 224)

Inconsistent literal block quoting.

'itcx1' >>> itcx1 = self.portal.itcx1 >>> itcx2 = MyComplexContent("itcx2") >>> self.portal._setObject("itcx2", itcx2) 'itcx2' >>> itcx2 = self.portal.itcx2 >>> itcx3 = MyComplexContent("itcx3") >>> self.portal._setObject("itcx3", itcx3) 'itcx3' >>> itcx3 = self.portal.itcx3

Now, add relation:

>>> r1_field = IComplexContent.get("relation1")
>>> r1_field.set(itcx1, [{'objects': [itcx2,]}])
>>> display(r1_field.get(itcx1))

System Message: ERROR/3 (<string>, line 240)

Inconsistent literal block quoting.

Objects: [<MyComplexContent at /plone/itcx2>] >>> r2_field = IComplexContent.get("relation2") >>> r2_field.set(itcx1, [{'objects': [itcx3,]}]) >>> display(r2_field.get(itcx1)) Objects: [<MyComplexContent at /plone/itcx3>]

And delete one:

>>> r2_field.set(itcx1, [])
>>> display(r2_field.get(itcx1))

System Message: ERROR/3 (<string>, line 250)

Inconsistent literal block quoting.

Empty >>> display(r1_field.get(itcx1)) Objects: [<MyComplexContent at /plone/itcx2>]

More Constraints

Now, you have to give at least 1 value, and no more than 3:

>>> class ILengthContent(Interface):

System Message: ERROR/3 (<string>, line 261)

Inconsistent literal block quoting.

... """Sample interface with length control.""" ... relation = PloneRelation(relation="my relation", ... min_length=1, ... max_length=3)

The field implements IMinMaxLen:

>>> from zope.schema.interfaces import IMinMaxLen
>>> rl_field = ILengthContent.get('relation')
>>> verifyObject(IMinMaxLen, rl_field)

System Message: ERROR/3 (<string>, line 272)

Inconsistent literal block quoting.

True

Ok, now some bad tries:

>>> bad_relation = []
>>> rl_field.validate(bad_relation)

System Message: ERROR/3 (<string>, line 278)

Inconsistent literal block quoting.

Traceback (most recent call last): ... TooSmall: Less than 1 values

>>> bad_relation = [{'objects': [it2,]},
...                 {'objects': [it3,]},
...                 {'objects': [it4,]},
...                 {'objects': [itb1,]},]
>>> rl_field.validate(bad_relation)
Traceback (most recent call last):
...
TooBig: More than 3 values

And now, one correct:

>>> good_relation = [{'objects': [it2,]},]
>>> rl_field.validate(good_relation)

But we want also to have uniques objects in the relation:

>>> class IUniqueContent(Interface):

System Message: ERROR/3 (<string>, line 299)

Inconsistent literal block quoting.

... """Sample interface only one item per relation.""" ... relation = PloneRelation(relation="my relation", ... unique=True) >>> ru_field = IUniqueContent.get('relation')

Some tries now:

>>> bad_relation = [{'objects': [it2, it3,]}]
>>> ru_field.validate(bad_relation)

System Message: ERROR/3 (<string>, line 308)

Inconsistent literal block quoting.

Traceback (most recent call last): ... ValidationError: Not uniques values in relation

>>> good_relation = [{'objects': [it2,]}]
>>> ru_field.validate(good_relation)

We want that every object in the relation implements a particular interface:

>>> class IConstraintContent(Interface):

System Message: ERROR/3 (<string>, line 319)

Inconsistent literal block quoting.

... """Sample interface with constraint on relation.""" ... relation = PloneRelation(relation="my relation", ... relation_schema=IUniqueContent) >>> rs_field = IConstraintContent.get('relation')

Use of context object

Two interfaces let you work with context objects:

>>> from infrae.plone.relations.schema import IPloneRelationContext
>>> from infrae.plone.relations.schema import IPloneRelationContextFactory

This two next import are helpers, but you can use them since it's good content start:

>>> from infrae.plone.relations.schema import BasePloneRelationContext
>>> from infrae.plone.relations.schema import BasePloneRelationContextFactory

The following context interface:

>>> class IContextObject(IPloneRelationContext):

System Message: ERROR/3 (<string>, line 342)

Inconsistent literal block quoting.

... """Simple context object."""

And its corresponding object:

>>> class MyContextObject(BasePloneRelationContext):

System Message: ERROR/3 (<string>, line 347)

Inconsistent literal block quoting.

... implements(IContextObject)

We will declare the field like this:

>>> class IContentWithContext(Interface):

System Message: ERROR/3 (<string>, line 352)

Inconsistent literal block quoting.

... """Simple content with a context.""" ... relation = PloneRelation(relation="context relation", ... context_schema=IContextObject)

We want an object with this schema:

>>> class MyContentWithContext(BaseContent):

System Message: ERROR/3 (<string>, line 359)

Inconsistent literal block quoting.

... implements(IContentWithContext)

Create the object:

>>> itc1 = MyContentWithContext('itc1')
>>> self.portal._setObject('itc1', itc1)

System Message: ERROR/3 (<string>, line 365)

Inconsistent literal block quoting.

'itc1' >>> itc1 = self.portal.itc1

Prepare one context object:

>>> ctxt_fac = BasePloneRelationContextFactory(MyContextObject, IContextObject)
>>> verifyObject(IPloneRelationContextFactory, ctxt_fac)

System Message: ERROR/3 (<string>, line 372)

Inconsistent literal block quoting.

True >>> ctxt1 = ctxt_fac(itc1, it1, dict()) >>> ctxt1 <MyContextObject at /plone/itc1/uid-it1> >>> verifyObject(IContextObject, ctxt1) True

Get the field:

>>> rc_field = IContentWithContext.get('relation')

Now we can try this relation:

>>> bad_relation = [{'objects': [it2, itb1,], 'context': it3,}]
>>> rc_field.validate(bad_relation)

System Message: ERROR/3 (<string>, line 387)

Inconsistent literal block quoting.

Traceback (most recent call last): ... ValidationError: Invalid context >>> good_relation = [{'objects': [it2, itb1,], 'context': ctxt1,}] >>> rc_field.validate(good_relation) >>> rc_field.set(itc1, good_relation)

If we consult the relation:

>>> display(rc_field.get(itc1))

System Message: ERROR/3 (<string>, line 397)

Inconsistent literal block quoting.

Objects: [<MyContent at /plone/it2>, <MyBackContent at /plone/itb1>] Context: <MyContextObject at uid-it1>

Many to Many Relation Interface

This interface provides a more generic way to edit relations than the one provided by plone.app.relations, to let the Zope 3 schema work in both way (normal access to the relation, and reverse access).

Create simple content:

>>> from OFS.SimpleItem import SimpleItem
>>> class BaseContent(SimpleItem):

System Message: ERROR/3 (<string>, line 412)

Inconsistent literal block quoting.

... def __init__(self, id): ... super(BaseContent, self).__init__() ... self.id = id

>>> for num in range(1, 20):
...    id = 'it%02d' % num
...    it = BaseContent(id)
...    _ = self.portal._setObject(id, it)
>>> self.portal.it01
<BaseContent at /plone/it01>

Contents must be IPersistent:

>>> from persistent import IPersistent
>>> from zope.interface.verify import verifyObject
>>> verifyObject(IPersistent, self.portal.it01)

System Message: ERROR/3 (<string>, line 430)

Inconsistent literal block quoting.

True

Simple test of the interface

We have a new adapter to work on your relation:

>>> from infrae.plone.relations.schema import IManyToManyRelationship
>>> manager = IManyToManyRelationship(self.portal.it01)
>>> verifyObject(IManyToManyRelationship, manager)

System Message: ERROR/3 (<string>, line 441)

Inconsistent literal block quoting.

True

Ok, try to add relation:

>>> rel = manager.createRelationship((self.portal.it11, self.portal.it12,),

System Message: ERROR/3 (<string>, line 446)

Inconsistent literal block quoting.

... sources=(self.portal.it02,), ... relation='test') >>> list(rel.sources) [<BaseContent at /plone/it01>, <BaseContent at /plone/it02>] >>> list(rel.targets) [<BaseContent at /plone/it11>, <BaseContent at /plone/it12>]

Now, we can retrieve a list of relation:

>>> list(manager.getRelationships())

System Message: ERROR/3 (<string>, line 456)

Inconsistent literal block quoting.

[<Relationship 'test' from (<BaseContent at /plone/it01>, <BaseContent at /plone/it02>) to (<BaseContent at /plone/it11>, <BaseContent at /plone/it12>)>]

Direction

You can reverse the way a relation works, with the setDirection method:

>>> rel = manager.createRelationship(self.portal.it05, relation='reverse')
>>> list(rel.targets)

System Message: ERROR/3 (<string>, line 467)

Inconsistent literal block quoting.

[<BaseContent at /plone/it05>] >>> manager.setDirection(False) >>> rel = manager.createRelationship(self.portal.it04, relation='reverse') >>> list(rel.targets) [<BaseContent at /plone/it01>]

You have also the transitivity for search:

>>> manager = IManyToManyRelationship(self.portal.it04)
>>> list(manager.getRelationshipChains(relation='reverse',

System Message: ERROR/3 (<string>, line 477)

Inconsistent literal block quoting.

... target=self.portal.it05, ... maxDepth=2)) [(<Relationship 'reverse' from (<BaseContent at /plone/it04>,) to (<BaseContent at /plone/it01>,)>, <Relationship 'reverse' from (<BaseContent at /plone/it01>,) to (<BaseContent at /plone/it05>,)>)]

But relation are always followed from source to target. So if we reverse the search, we won't found a result:

>>> manager.setDirection(False)
>>> list(manager.getRelationshipChains(relation='reverse',

System Message: ERROR/3 (<string>, line 487)

Inconsistent literal block quoting.

... target=self.portal.it05, ... maxDepth=2)) []

Direction just change the meaning of source or target on the relation object. It's doesn't change the relation itself.

Bigger example with transitivity

Taking back the first test, and add a suite:

>>> manager = IManyToManyRelationship(self.portal.it16)
>>> manager.setDirection(False)
>>> rel = manager.createRelationship((self.portal.it12, self.portal.it14),

System Message: ERROR/3 (<string>, line 503)

Inconsistent literal block quoting.

... relation='test') >>> manager.setDirection(True) >>> rel = manager.createRelationship((self.portal.it17, self.portal.it18), ... sources=(self.portal.it19,), ... relation='test')

New chain try:

>>> manager = IManyToManyRelationship(self.portal.it02)
>>> list(manager.getRelationshipChains(relation='test',

System Message: ERROR/3 (<string>, line 513)

Inconsistent literal block quoting.

... target=self.portal.it18, ... maxDepth=3)) [(<Relationship 'test' from (<BaseContent at /plone/it01>, <BaseContent at /plone/it02>) to (<BaseContent at /plone/it11>, <BaseContent at /plone/it12>)>, <Relationship 'test' from (<BaseContent at /plone/it12>, <BaseContent at /plone/it14>) to (<BaseContent at /plone/it16>,)>, <Relationship 'test' from (<BaseContent at /plone/it16>, <BaseContent at /plone/it19>) to (<BaseContent at /plone/it17>, <BaseContent at /plone/it18>)>)]

Accessor

getTargets returns a lazy list of objects having a relation with the given object as source, and getSources returns a lazy list of objects having a relation with the given object as target:

>>> manager = IManyToManyRelationship(self.portal.it16)
>>> list(manager.getTargets())

System Message: ERROR/3 (<string>, line 527)

Inconsistent literal block quoting.

[<BaseContent at /plone/it17>, <BaseContent at /plone/it18>] >>> list(manager.getSources()) [<BaseContent at /plone/it12>, <BaseContent at /plone/it14>]

If we reverse the direction:

>>> manager.setDirection(False)
>>> list(manager.getTargets())

System Message: ERROR/3 (<string>, line 535)

Inconsistent literal block quoting.

[<BaseContent at /plone/it12>, <BaseContent at /plone/it14>] >>> list(manager.getSources()) [<BaseContent at /plone/it17>, <BaseContent at /plone/it18>]

Deletion

Delete relation:

>>> manager.setDirection(True)
>>> manager.deleteRelationship()
>>> list(manager.getRelationships())

System Message: ERROR/3 (<string>, line 548)

Inconsistent literal block quoting.

[]

>>> manager = IManyToManyRelationship(self.portal.it19)
>>> list(manager.getRelationships())
[<Relationship 'test' from (<BaseContent at /plone/it19>,) to (<BaseContent at /plone/it17>, <BaseContent at /plone/it18>)>]
>>> manager.deleteRelationship(target=self.portal.it17)
>>> list(manager.getRelationships())
[<Relationship 'test' from (<BaseContent at /plone/it19>,) to (<BaseContent at /plone/it18>,)>]
>>> manager.deleteRelationship()
>>> list(manager.getRelationships())
[]
>>> manager = IManyToManyRelationship(self.portal.it01)
>>> manager.deleteRelationship(remove_all_sources=True, multiple=True)
>>> manager = IManyToManyRelationship(self.portal.it02)
>>> list(manager.getRelationships())
[]

Changes

1.0
  • First release.

Credits

Powered by the Flemish government of Belgium, for the application <http://www.zonderisgezonder.be>.

Subscribe to package updates

Last updated Jan 5th, 2011

Download Stats

Last month:1

What does the lock icon mean?

Builds marked with a lock icon are only available via PyPM to users with a current ActivePython Business Edition subscription.

Need custom builds or support?

ActivePython Enterprise Edition guarantees priority access to technical support, indemnification, expert consulting and quality-assured language builds.

Plan on re-distributing ActivePython?

Get re-distribution rights and eliminate legal risks with ActivePython OEM Edition.