provide basic functionality for loops.compound

git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@2262 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2007-12-21 12:34:19 +00:00
parent d40012fb18
commit 433d930315
5 changed files with 199 additions and 17 deletions

View file

@ -4,3 +4,73 @@ loops - Linked Objects for Organization and Processing Services
($Id$)
>>> from zope import component
>>> from zope.traversing.api import getName
Let's set up a loops site with basic and example concepts and resources.
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
>>> site = placefulSetUp(True)
>>> from loops.tests.setup import TestSite
>>> t = TestSite(site)
>>> concepts, resources, views = t.setup()
Compund Objects - Hierarchies with Ordered Components
=====================================================
>>> from loops.compound.base import Compound
>>> component.provideAdapter(Compound)
>>> tType = concepts.getTypeConcept()
>>> from loops.setup import addAndConfigureObject
>>> from loops.concept import Concept
>>> from loops.compound.interfaces import ICompound
We first create the compound type and one instance of the newly created
type. We also need an ``ispartof`` predicate.
>>> tCompound = addAndConfigureObject(concepts, Concept, 'compound',
... title=u'Compound',
... conceptType=tType, typeInterface=ICompound)
>>> c01 = addAndConfigureObject(concepts, Concept, 'c01',
... title=u'Compound #01', conceptType=tCompound)
>>> tPredicate = concepts.getPredicateType()
>>> isPartof = addAndConfigureObject(concepts, Concept, 'ispartof',
... title=u'is Part of', conceptType=tPredicate)
In order to access the compound concept's attributes we have to adapt
it.
>>> from loops.common import adapted
>>> aC01 = adapted(c01)
Now we are able to add resources to it.
>>> aC01.add(resources[u'd003.txt'])
>>> aC01.add(resources[u'd001.txt'])
>>> [getName(p) for p in aC01.getParts()]
[u'd003.txt', u'd001.txt']
>>> aC01.add(resources[u'd001.txt'], 0)
>>> [getName(p) for p in aC01.getParts()]
[u'd001.txt', u'd003.txt', u'd001.txt']
>>> aC01.add(resources[u'd002.txt'], -1)
>>> [getName(p) for p in aC01.getParts()]
[u'd001.txt', u'd003.txt', u'd002.txt', u'd001.txt']
We can reorder the parts of a compound.
>>> aC01.reorder([resources[u'd002.txt'], resources[u'd001.txt'], ])
>>> [getName(p) for p in aC01.getParts()]
[u'd002.txt', u'd001.txt', u'd003.txt', u'd001.txt']
And remove a part from the compound.
>>> aC01.remove(resources[u'd001.txt'], 1)
>>> [getName(p) for p in aC01.getParts()]
[u'd002.txt', u'd003.txt', u'd001.txt']

View file

@ -22,3 +22,90 @@ Compound objects like articles, blog posts, storyboard items, ...
$Id$
"""
from zope.cachedescriptors.property import Lazy
from zope.interface import implements
from zope.traversing.api import getName
from loops.common import AdapterBase
from loops.compound.interfaces import ICompound, compoundPredicateName
class Compound(AdapterBase):
implements(ICompound)
def getParts(self):
return self.context.getChildren([self.partOf])
def add(self, obj, position=None):
if position is None:
order = self.getMaxOrder() + 1
else:
order = self.getOrderForPosition(position)
self.context.assignChild(obj, self.partOf, order=order)
def remove(self, obj, position=None):
if position is None:
self.context.deassignChild(obj, [self.partOf])
else:
rel = self.getPartRelations()[position]
self.context.deassignChild(obj, [self.partOf], order=rel.order)
def reorder(self, parts):
existing = list(self.getPartRelations())
order = 1
for p in parts:
for idx, x in enumerate(existing):
if x.second == p:
x.order = order
order += 1
del existing[idx]
break
else:
raise ValueError("Part '%s' not in list of existing parts."
% getName(p))
for x in existing: # position the rest at the end
x.order = order
order += 1
# helper methods and properties
def getPartRelations(self):
return self.context.getChildRelations([self.partOf])
def getMaxOrder(self):
rels = self. getPartRelations()
if rels:
return max(r.order for r in rels)
return 1
def getOrderForPosition(self, pos):
rels = self. getPartRelations()
if pos < 0:
pos = len(rels) + pos
previous = 0
value = None
for idx, r in enumerate(rels):
if idx == pos: # position found
if previous < r.order - 1: # space for a new entry
value = previous + 1
break
value = r.order
r.order += 1
elif idx > pos:
if previous < r.order - 1: # no renumber necessary any more
break
r.order += 1
previous = r.order
if value is None: # pos was greater than length, use last order found
value = previous + 1
return value
@Lazy
def conceptManager(self):
return self.context.getConceptManager()
@Lazy
def partOf(self):
return self.conceptManager[compoundPredicateName]

View file

@ -19,6 +19,9 @@
"""
Compound objects like articles, blog posts, storyboard items, ...
This is somehow related to cybertools.composer - so maybe we should
move part of the code defined here to that more generic package.
$Id$
"""
@ -28,34 +31,55 @@ from zope import interface, component, schema
from loops.util import _
compoundPredicateName = 'ispartof'
class ICompound(Interface):
""" A compound is a concept that is built up of other objects, its
components.
parts or components.
These components are typically resources, but may also be other
concepts that should provide the ICompound interface as their
type interface. The components are assigned in an ordered way
so that the components are accessed in a reproducible order.
These parts are typically resources, but may also be other
concepts that may/should provide the ICompound interface as their
type interface. The parts are assigned in an ordered way
so that the parts are accessed in a reproducible order.
The components are bound to the compound via standard resource
The parts are bound to the compound via standard resource
or concept relations using a special predicate, ``ispartof``.
"""
components = Attribute('Objects (resources or other compounds) that '
'this object consists of')
def getParts():
""" Return the objects (resources or other compounds) that
this object consists of in the defined order.
"""
def add(obj, position=None):
""" Add a component to the compound.
""" Add a part to the compound.
If the ``position`` argument is None, append it to the end
of the current list of components, otherwise insert it before
of the current list of parts, otherwise insert it before
the position given. The numbering of the positions is like for
Python lists, so 0 means before the first one, -1 before the
last one.
"""
def reorder(components):
""" Change the order settings of the relations that connect the
components given (a sequence) to the compound so that they are
ordered according to this sequence.
def remove(obj, position=None):
""" Remove the object given from the context object's list of
parts.
If the object given is not present raise an error. If there
is more than one part relation to the object given
all of them will be removed, except if the position argument
is given in which case only the object at the given position
will be removed.
"""
def reorder(parts):
""" Change the order settings of the relations that connect the
parts given (a sequence) to the compound so that they are
ordered according to this sequence.
If the parts`` argument contains a value that is not
in the context objects`s list of parts raise an error. If the
context object has parts that are not in the ``parts`` argument
they will be moved to the end of the list.
"""

View file

@ -197,10 +197,11 @@ class Concept(Contained, Persistent):
def assignParent(self, concept, predicate=None, order=0, relevance=1.0):
concept.assignChild(self, predicate, order, relevance)
def deassignChild(self, child, predicates=None):
def deassignChild(self, child, predicates=None, order=None):
registry = component.getUtility(IRelationRegistry)
for rel in self.getChildRelations(predicates, child):
registry.unregister(rel)
if order is None or rel.order == order:
registry.unregister(rel)
def deassignParent(self, parent, predicates=None):
parent.deassignChild(self, predicates)

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by