diff --git a/concept.py b/concept.py index 6428da7..5898640 100644 --- a/concept.py +++ b/concept.py @@ -195,6 +195,15 @@ class Concept(Contained, Persistent): # TODO (?): avoid duplicates registry.register(rel) + def setChildren(self, predicate, concepts): + existing = self.getChildren([predicate]) + for c in existing: + if c not in concepts: + self.deassignChild(c, [predicate]) + for c in concepts: + if c not in existing: + self.assignChild(c, predicate) + def assignParent(self, concept, predicate=None, order=0, relevance=1.0): concept.assignChild(self, predicate, order, relevance) diff --git a/constraint/README.txt b/constraint/README.txt index 726d8e8..fa105ee 100644 --- a/constraint/README.txt +++ b/constraint/README.txt @@ -6,20 +6,49 @@ loops - Linked Objects for Organization and Processing Services >>> from zope import component >>> from zope.traversing.api import getName + >>> from loops.common import adapted + >>> from loops.concept import Concept + >>> from loops.setup import addAndConfigureObject 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 + >>> from loops.constraint.testsetup import TestSite >>> t = TestSite(site) >>> concepts, resources, views = t.setup() + >>> tType = concepts.getTypeConcept() + Constraints - Restrict Predicates and Types for Concept and Resource Assignments ================================================================================ - >>> from loops.constraint.base import StaticConstraint - >>> component.provideAdapter(StaticConstraint) +We create a person type and an institution type and a corresponding +predicate. + >>> tPerson = addAndConfigureObject(concepts, Concept, 'person', + ... title='Person', conceptType=tType) + >>> tInstitution = addAndConfigureObject(concepts, Concept, 'institution', + ... title='Institution', conceptType=tType) + >>> tPredicate = concepts.getPredicateType() + >>> isEmployedBy = addAndConfigureObject(concepts, Concept, 'isemployedby', + ... title='is Employed by', conceptType=tPredicate) + +The static constraint concept type has already been created during setup. +We create a simple constraint that + + >>> tStaticConstraint = concepts['staticconstraint'] + >>> cstr01 = addAndConfigureObject(concepts, Concept, 'cstr01', + ... title='Demo Constraint', conceptType=tStaticConstraint) + + >>> aCstr01 = adapted(cstr01) + >>> aCstr01.predicates = [isEmployedBy] + >>> aCstr01.types = [tInstitution] + +We can now use this constraint to control the parent concepts a person +may be assigned to. + + >>> from loops.constraint.base import hasConstraint + >>> tPerson.assignParent(cstr01, hasConstraint) diff --git a/constraint/base.py b/constraint/base.py index b4568c9..ca8fb7d 100644 --- a/constraint/base.py +++ b/constraint/base.py @@ -34,6 +34,11 @@ from loops.type import TypeInterfaceSourceList TypeInterfaceSourceList.typeInterfaces += (IStaticConstraint,) +isPredicate = 'ispredicateforconstraint' +isType = 'istypeforconstraint' +hasConstraint = 'hasconstraint' +hasChildConstraint = 'haschildconstraint' + class StaticConstraint(AdapterBase): @@ -43,13 +48,13 @@ class StaticConstraint(AdapterBase): implements(IStaticConstraint) def getPredicates(self): - return getattr(self.context, '_predicates', []) + return self.context.getChildren([isPredicate]) def setPredicates(self, value): - self.context._predicates = value + self.context.setChildren(isPredicate, value) predicates = property(getPredicates, setPredicates) def getTypes(self): - return getattr(self.context, '_types', []) + return self.context.getChildren([isType]) def setTypes(self, value): - self.context._types = value + self.context.setChildren(isType, value) types = property(getTypes, setTypes) diff --git a/constraint/configure.zcml b/constraint/configure.zcml index 6e46b30..889434f 100644 --- a/constraint/configure.zcml +++ b/constraint/configure.zcml @@ -15,4 +15,7 @@ set_schema="loops.constraint.interfaces.IStaticConstraint" /> + + diff --git a/constraint/setup.py b/constraint/setup.py new file mode 100644 index 0000000..8f81a5c --- /dev/null +++ b/constraint/setup.py @@ -0,0 +1,53 @@ +# +# Copyright (c) 2006 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 +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Automatic setup of a loops site for the constraint package. + +$Id$ +""" + +from zope.component import adapts +from zope.interface import implements, Interface + +from loops.concept import Concept +from loops.constraint.interfaces import IStaticConstraint +from loops.constraint.base import isPredicate, isType +from loops.constraint.base import hasConstraint, hasChildConstraint +from loops.setup import SetupManager as BaseSetupManager + + +class SetupManager(BaseSetupManager): + + def setup(self): + concepts = self.context.getConceptManager() + type = concepts.getTypeConcept() + predicate = concepts['predicate'] + # type concepts: + constraint = self.addAndConfigureObject(concepts, Concept, + 'staticconstraint', title=u'Constraint', + conceptType=type, typeInterface=IStaticConstraint) + self.addObject(concepts, Concept, isPredicate, + title=u'is Predicate for Constraint', conceptType=predicate) + self.addObject(concepts, Concept, isType, + title=u'is Type for Constraint', conceptType=predicate) + self.addObject(concepts, Concept, hasConstraint, + title=u'has Constraint', conceptType=predicate) + self.addObject(concepts, Concept, hasChildConstraint, + title=u'has Child Constraint', conceptType=predicate) + diff --git a/constraint/testsetup.py b/constraint/testsetup.py new file mode 100644 index 0000000..9a283f0 --- /dev/null +++ b/constraint/testsetup.py @@ -0,0 +1,32 @@ +""" +Set up a loops site for testing. + +$Id$ +""" + +import os +from zope import component +from zope.app.catalog.interfaces import ICatalog +from zope.app.catalog.field import FieldIndex + +from loops import util +from loops.concept import Concept +from loops.constraint.base import StaticConstraint +from loops.constraint.setup import SetupManager +from loops.setup import addAndConfigureObject +from loops.tests.setup import TestSite as BaseTestSite + + +class TestSite(BaseTestSite): + + def __init__(self, site): + self.site = site + + def setup(self): + component.provideAdapter(SetupManager, name='constraint') + concepts, resources, views = self.baseSetup() + + component.provideAdapter(StaticConstraint) + + return concepts, resources, views + diff --git a/interfaces.py b/interfaces.py index 217055a..b422ae7 100644 --- a/interfaces.py +++ b/interfaces.py @@ -153,6 +153,14 @@ class IConcept(IConceptSchema, ILoopsObject, IPotentialTarget): optionally restricting them to the predicates given. """ + def setChildren(predicate, concepts): + """ A convenience method for adding and removing a set of child + relations in one call. The relations with the predicate given + that already point to one of the concepts given as the second + argument are kept unchanged, new ones are assigned, and existing + ones not present in the concepts list are removed. + """ + def getResources(predicates=None): """ Return a sequence of resources assigned to self, optionally restricted to the predicates given. diff --git a/setup.py b/setup.py index 70c127f..4671754 100644 --- a/setup.py +++ b/setup.py @@ -99,6 +99,9 @@ class SetupManager(object): def addObject(self, container, class_, name, **kw): return addObject(container, class_, name, **kw) + def addAndConfigureObject(self, container, class_, name, **kw): + return addAndConfigureObject(container, class_, name, **kw) + def addObject(container, class_, name, **kw): if name in container: