diff --git a/README.txt b/README.txt
index 3cea3e4..033a5e6 100755
--- a/README.txt
+++ b/README.txt
@@ -142,8 +142,12 @@ a special concept type.
>>> from loops.concept import PredicateSourceList
>>> predicates = PredicateSourceList(cc1)
+
+Note that the 'hasType' predicate is suppressed from this list as the
+corresponding relation is only assigned via the conceptType attribute:
+
>>> sorted(t.title for t in predicates)
- [u'has type', u'subconcept']
+ [u'subconcept']
Concept Views
-------------
@@ -209,8 +213,7 @@ types and predicates.
(u'Unknown Type', '.loops/concepts/unknown')]
>>> sorted((t.title, t.token) for t in view.predicates())
- [(u'has type', '.loops/concepts/hasType'),
- (u'subconcept', '.loops/concepts/standard')]
+ [(u'subconcept', '.loops/concepts/standard')]
Index attributes adapter
------------------------
diff --git a/browser/concept.py b/browser/concept.py
index 29e85ba..910228d 100644
--- a/browser/concept.py
+++ b/browser/concept.py
@@ -272,6 +272,10 @@ class ConceptRelationView(BaseView):
return ':'.join((self.loopsRoot.getLoopsUri(self.context),
self.loopsRoot.getLoopsUri(self.predicate)))
+ @Lazy
+ def isProtected(self):
+ return zapi.getName(self.predicate) == 'hasType'
+
@Lazy
def predicateTitle(self):
return self.predicate.title
diff --git a/browser/relation_macros.pt b/browser/relation_macros.pt
index 9b2e3f0..925489c 100644
--- a/browser/relation_macros.pt
+++ b/browser/relation_macros.pt
@@ -32,6 +32,7 @@
diff --git a/concept.py b/concept.py
index 2556599..fb8beae 100644
--- a/concept.py
+++ b/concept.py
@@ -271,11 +271,12 @@ class PredicateSourceList(object):
typePred = cm.getTypePredicate()
if defPred is not None and typePred is not None:
result.append(defPred)
- result.append(typePred)
+ #result.append(typePred)
predType = defPred.conceptType
if predType is not None and predType != cm.getTypeConcept():
result.extend(p for p in predType.getChildren([typePred])
- if p not in result)
+ if p not in result
+ and p != typePred)
return result
def __len__(self):
diff --git a/knowledge/README.txt b/knowledge/README.txt
new file mode 100644
index 0000000..bf2a566
--- /dev/null
+++ b/knowledge/README.txt
@@ -0,0 +1,207 @@
+===============================================================
+loops - Linked Objects for Organization and Processing Services
+===============================================================
+
+ ($Id$)
+
+Note: This package depends on cybertools.knowledge and cybertools.organize.
+
+Let's do some basic set up
+
+ >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
+ >>> site = placefulSetUp(True)
+
+ >>> from zope import component, interface
+ >>> from zope.app import zapi
+
+and setup a simple loops site with a concept manager and some concepts
+(with all the type machinery, what in real life is done via standard
+ZCML setup):
+
+ >>> from loops import Loops
+ >>> from loops.concept import ConceptManager, Concept
+ >>> from loops.resource import ResourceManager
+ >>> from loops.interfaces import IResource, IConcept, ITypeConcept
+
+ >>> loopsRoot = site['loops'] = Loops()
+
+ >>> from cybertools.relation.interfaces import IRelationRegistry
+ >>> from cybertools.relation.registry import DummyRelationRegistry
+ >>> relations = DummyRelationRegistry()
+ >>> component.provideUtility(relations, IRelationRegistry)
+
+ >>> from cybertools.typology.interfaces import IType
+ >>> from loops.type import ConceptType, TypeConcept
+ >>> component.provideAdapter(ConceptType, (IConcept,), IType)
+ >>> component.provideAdapter(TypeConcept, (IConcept,), ITypeConcept)
+
+ >>> concepts = loopsRoot['concepts'] = ConceptManager()
+ >>> resources = loopsRoot['resources'] = ResourceManager()
+
+ >>> hasType = concepts['hasType'] = Concept(u'has type')
+ >>> type = concepts['type'] = Concept(u'Type')
+ >>> type.conceptType = type
+
+ >>> predicate = concepts['predicate'] = Concept(u'Predicate')
+ >>> predicate.conceptType = type
+ >>> hasType.conceptType = predicate
+
+We need some predicates to set up the relationships between our concepts:
+
+ >>> standard = concepts['standard'] = Concept(u'subobject')
+ >>> depends = concepts['depends'] = Concept(u'depends')
+ >>> knows = concepts['knows'] = Concept(u'knows')
+ >>> requires = concepts['requires'] = Concept(u'requires')
+ >>> provides = concepts['provides'] = Concept(u'provides')
+
+ >>> for p in (standard, depends, knows, requires, provides):
+ ... p.conceptType = predicate
+
+And last not least we need some type concepts for controlling the
+meaning of the concepts objects:
+
+ >>> from cybertools.knowledge.interfaces import IKnowledgeElement
+ >>> topic = concepts['topic'] = Concept(u'Topic')
+ >>> topic.conceptType = type
+ >>> ITypeConcept(topic).typeInterface = IKnowledgeElement
+
+ >>> from loops.knowledge.interfaces import IPerson
+ >>> person = concepts['person'] = Concept(u'Person')
+ >>> person.conceptType = type
+ >>> ITypeConcept(person).typeInterface = IPerson
+
+ >>> from loops.knowledge.interfaces import ITask
+ >>> task = concepts['task'] = Concept(u'Task')
+ >>> task.conceptType = type
+ >>> ITypeConcept(task).typeInterface = ITask
+
+
+Manage knowledge and knowledge requirements
+===========================================
+
+The classes used in this package are just adapters to IConcept.
+
+ >>> from loops.knowledge.knowledge import Person, Topic, Task
+ >>> component.provideAdapter(Person, (IConcept,), IPerson)
+ >>> component.provideAdapter(Topic, (IConcept,), IKnowledgeElement)
+ >>> component.provideAdapter(Task, (IConcept,), ITask)
+
+First we want to set up a tree of knowledge elements (topics) and their
+interdependencies. Note that in order to discern the concepts created
+from their typeInterface adapters we here append a 'C' to the name of
+the variables:
+
+ >>> progLangC = concepts['progLang'] = Concept(u'Programming Language')
+ >>> ooProgC = concepts['ooProg'] = Concept(u'Object-oriented Programming')
+ >>> pythonC = concepts['python'] = Concept(u'Python')
+ >>> pyBasicsC = concepts['pyBasics'] = Concept(u'Python Basics')
+ >>> pyOoC = concepts['pyOo'] = Concept(u'OO Programming with Python')
+ >>> pySpecialsC = concepts['pySpecials'] = Concept(u'Python Specials')
+
+ >>> topicConcepts = (progLangC, ooProgC, pythonC, pyBasicsC, pyOoC, pySpecialsC)
+
+ >>> for c in topicConcepts: c.conceptType = topic
+
+ >>> progLang, ooProg, python, pyBasics, pyOo, pySpecials = (IKnowledgeElement(c)
+ ... for c in topicConcepts)
+
+ >>> python.parent = progLang
+ >>> pyBasics.parent = python
+ >>> pyOo.parent = python
+ >>> pySpecials.parent = python
+
+ >>> pyOo.dependsOn(ooProg)
+ >>> pyOo.dependsOn(pyBasics)
+
+We now create a person and assign some knowledge to it:
+
+ >>> johnC = concepts['john'] = Concept(u'John')
+ >>> johnC.conceptType = person
+
+ >>> john = IPerson(johnC)
+ >>> john.knows(pyBasics)
+ >>> list(john.getKnowledge())[0].title
+ u'Python Basics'
+
+Now let's get to tasks - a task is used as a requirement profile, i.e.
+it requires a certain set of knowledge elements:
+
+ >>> task01C = concepts['task01C'] = Concept('Task: needs Python OO')
+ >>> task01C.conceptType = task
+
+ >>> task01 = ITask(task01C)
+ >>> task01.requires(pyOo)
+
+Now we can ask what knowledge john is lacking if he would like to take
+a position with the requirement profile:
+
+ >>> missing = john.getMissingKnowledge(task01)
+ >>> [m.title for m in missing]
+ [u'Object-oriented Programming', u'OO Programming with Python']
+
+Luckily there are a few elearning content objects out there that
+provide some of the knowledge needed:
+
+ >>> from cybertools.knowledge.interfaces import IKnowledgeProvider
+ >>> from loops.knowledge.knowledge import ConceptKnowledgeProvider
+ >>> component.provideAdapter(ConceptKnowledgeProvider, (IConcept,))
+ >>> from loops.knowledge.knowledge import ResourceKnowledgeProvider
+ >>> component.provideAdapter(ResourceKnowledgeProvider, (IResource,))
+
+ >>> doc01C = concepts['doc01'] = Concept('Objectorienting Programming')
+ >>> doc01 = IKnowledgeProvider(doc01C)
+ >>> from loops.resource import Document
+ >>> doc02D = resources['doc02'] = Document('oopython.pdf')
+ >>> doc02 = IKnowledgeProvider(doc02D)
+
+ >>> doc01.provides(ooProg)
+ >>> doc02.provides(pyOo)
+
+So that we are now able to find out what john has to study in order to
+fulfill the position offered:
+
+ >>> prov = list(john.getProvidersNeeded(task01))
+ >>> len(prov)
+ 2
+ >>> [list(d)[0].title for k, d in prov]
+ ['Objectorienting Programming', 'oopython.pdf']
+
+
+Views that make use of the knowledge management modules
+-------------------------------------------------------
+
+One of the practical applications of this stuff is searching for missing
+knowledge and corresponding knowledge providers for the user currently
+logged in.
+
+For testing, we first have to provide the needed utilities and settings
+(in real life this is all done during Zope startup):
+
+ >>> from zope.app.security.interfaces import IAuthentication
+ >>> from zope.app.security.principalregistry import PrincipalRegistry
+ >>> auth = PrincipalRegistry()
+ >>> component.provideUtility(auth, IAuthentication)
+
+ >>> from zope.app.principalannotation.interfaces import IPrincipalAnnotationUtility
+ >>> from zope.app.principalannotation import PrincipalAnnotationUtility
+ >>> principalAnnotations = PrincipalAnnotationUtility()
+ >>> component.provideUtility(principalAnnotations, IPrincipalAnnotationUtility)
+
+ >>> principal = auth.definePrincipal('users.john', u'John', login='john')
+ >>> john.userId = 'users.john'
+
+ >>> from zope.publisher.browser import TestRequest
+ >>> request = TestRequest()
+ >>> request.setPrincipal(principal)
+
+ >>> from loops.knowledge.browser import MyKnowledge
+ >>> view = MyKnowledge(task01C, request)
+ >>> prov = view.myKnowledgeProvidersForTask()
+
+
+
+Fin de partie
+=============
+
+ >>> placefulTearDown()
+
diff --git a/knowledge/__init__.py b/knowledge/__init__.py
new file mode 100644
index 0000000..4bc90fb
--- /dev/null
+++ b/knowledge/__init__.py
@@ -0,0 +1,4 @@
+"""
+$Id$
+"""
+
diff --git a/knowledge/browser.py b/knowledge/browser.py
new file mode 100644
index 0000000..3803227
--- /dev/null
+++ b/knowledge/browser.py
@@ -0,0 +1,64 @@
+#
+# Copyright (c) 2004 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
+#
+
+"""
+Definition of view classes and other browser related stuff for the
+loops.knowledge package.
+
+$Id$
+"""
+
+from zope import interface, component
+from zope.app import zapi
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.cachedescriptors.property import Lazy
+from zope.formlib.namedtemplate import NamedTemplate
+from zope.i18nmessageid import MessageFactory
+
+from cybertools.typology.interfaces import IType
+from loops.browser.common import BaseView
+from loops.knowledge.interfaces import IPerson, ITask
+from loops.organize.browser import getPersonForLoggedInUser
+
+_ = MessageFactory('zope')
+
+
+class MyKnowledge(BaseView):
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+ person = getPersonForLoggedInUser(request)
+ if person is not None:
+ person = IPerson(person)
+ self.person = person
+
+ def myKnowledge(self):
+ if self.person is None:
+ return ()
+ knowledge = self.person.getKnowledge()
+ return knowledge
+
+ def myKnowledgeProvidersForTask(self):
+ if self.person is None:
+ return ()
+ task = ITask(self.context)
+ # TODO: check correct conceptType for context!
+ providers = self.person.getProvidersNeeded(task)
+ return providers
+
diff --git a/knowledge/configure.zcml b/knowledge/configure.zcml
new file mode 100644
index 0000000..26e038d
--- /dev/null
+++ b/knowledge/configure.zcml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/knowledge/interfaces.py b/knowledge/interfaces.py
new file mode 100644
index 0000000..f76699f
--- /dev/null
+++ b/knowledge/interfaces.py
@@ -0,0 +1,45 @@
+#
+# 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
+#
+
+"""
+Interfaces for knowledge management and elearning with loops.
+
+$Id$
+"""
+
+from zope.interface import Interface, Attribute
+from zope import interface, component, schema
+from zope.app import zapi
+from zope.i18nmessageid import MessageFactory
+from zope.security.proxy import removeSecurityProxy
+
+from cybertools.knowledge.interfaces import IKnowing, IRequirementProfile
+from cybertools.organize.interfaces import IPerson as IBasePerson
+from cybertools.organize.interfaces import ITask as IBaseTask
+
+_ = MessageFactory('zope')
+
+
+class IPerson(IBasePerson, IKnowing):
+ """ A person, including knowledge/learning management features.
+ """
+
+
+class ITask(IBaseTask, IRequirementProfile):
+ """ A task, also acting as a knowledge requirement profile.
+ """
diff --git a/knowledge/knowledge.py b/knowledge/knowledge.py
new file mode 100644
index 0000000..10a6108
--- /dev/null
+++ b/knowledge/knowledge.py
@@ -0,0 +1,183 @@
+#
+# 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
+#
+
+"""
+Adapters for IConcept providing interfaces from the
+cybertools.knowledge package.
+
+$Id$
+"""
+
+from zope import interface, component
+from zope.app import zapi
+from zope.component import adapts
+from zope.interface import implements
+from zope.cachedescriptors.property import Lazy
+
+from cybertools.typology.interfaces import IType
+from cybertools.knowledge.interfaces import IKnowledgeElement, IKnowledgeProvider
+from cybertools.knowledge.knowing import Knowing
+from loops.interfaces import IConcept
+from loops.knowledge.interfaces import IPerson, ITask
+from loops.organize.party import Person as BasePerson
+from loops.organize.task import Task as BaseTask
+from loops.type import TypeInterfaceSourceList, AdapterBase
+
+
+# register type interfaces - (TODO: use a function for this)
+
+TypeInterfaceSourceList.typeInterfaces += (IPerson, IKnowledgeElement, ITask)
+
+
+class KnowledgeAdapterMixin(object):
+
+ @Lazy
+ def conceptManager(self):
+ return self.context.getLoopsRoot().getConceptManager()
+
+ @Lazy
+ def standardPred(self):
+ return self.conceptManager.getDefaultPredicate()
+
+ @Lazy
+ def dependsPred(self):
+ return self.conceptManager['depends']
+
+ @Lazy
+ def knowsPred(self):
+ return self.conceptManager['knows']
+
+ @Lazy
+ def requiresPred(self):
+ return self.conceptManager['requires']
+
+ @Lazy
+ def providesPred(self):
+ return self.conceptManager['provides']
+
+ def __eq__(self, other):
+ return self.context == other.context
+
+
+class Person(BasePerson, Knowing, KnowledgeAdapterMixin):
+ """ A typeInterface adapter for concepts of type 'person', including
+ knowledge/learning management features.
+ """
+
+ implements(IPerson)
+
+ def getKnowledge(self):
+ return (IKnowledgeElement(c)
+ for c in self.context.getParents((self.knowsPred,)))
+
+ def knows(self, obj):
+ self.context.assignParent(obj.context, self.knowsPred)
+
+ def removeKnowledge(self, obj):
+ self.context.deassignParent(obj.context, (self.knowsPred,))
+
+
+class Topic(AdapterBase, KnowledgeAdapterMixin):
+ """ A typeInterface adapter for concepts of type 'topic' that
+ may act as a knowledge element.
+ """
+
+ implements(IKnowledgeElement)
+ _attributes = ('context', '__parent__', 'parent')
+
+ def getParent(self):
+ parents = self.context.getParents((self.standardPred,))
+ return parents and IKnowledgeElement(parents[0]) or None
+ def setParent(self, obj):
+ old = self.getParent()
+ if old is not None and old.context != self.context:
+ self.context.deassignParent(old.context, (self.standardPred,))
+ self.context.assignParent(obj.context, self.standardPred)
+ parent = property(getParent, setParent)
+
+ def getDependencies(self):
+ return (IKnowledgeElement(c)
+ for c in self.context.getParents((self.dependsPred,)))
+
+ def dependsOn(self, obj):
+ self.context.assignParent(obj.context, self.dependsPred)
+
+ def removeDependency(self, obj):
+ self.context.deassignParent(obj.context, (self.dependsPred,))
+
+ def getDependents(self):
+ return (IKnowledgeElement(c)
+ for c in self.context.getChildren((self.dependsPred,)))
+
+ def getKnowers(self):
+ return (IPerson(c)
+ for c in self.context.getChildren((self.knowsPred,)))
+
+ def getProviders(self):
+ return (IKnowledgeProvider(c)
+ for c in self.context.getChildren((self.providesPred,))
+ + self.context.getResources((self.providesPred,)))
+
+
+class Task(BaseTask, KnowledgeAdapterMixin):
+ """ A typeInterface adapter for concepts of type 'task' that
+ may act as a knowledge requirement profile.
+ """
+
+ implements(ITask)
+
+ def getRequirements(self):
+ return (IKnowledgeElement(c)
+ for c in self.context.getParents((self.requiresPred,)))
+
+ def requires(self, obj):
+ self.context.assignParent(obj.context, self.requiresPred)
+
+ def removeRequirement(self, obj):
+ self.context.deassignParent(obj.context, (self.requiresPred,))
+
+
+class ConceptKnowledgeProvider(AdapterBase, KnowledgeAdapterMixin):
+
+ implements(IKnowledgeProvider)
+
+ def getProvidedKnowledge(self):
+ return (IKnowledgeElement(c)
+ for c in self.context.getParents((self.providesPred,)))
+
+ def provides(self, obj):
+ self.context.assignParent(obj.context, self.providesPred)
+
+ def removeProvidedKnowledge(self, obj):
+ self.context.deassignParent(obj.context, (self.providesPred,))
+
+
+class ResourceKnowledgeProvider(AdapterBase, KnowledgeAdapterMixin):
+
+ implements(IKnowledgeProvider)
+
+ def getProvidedKnowledge(self):
+ return (IKnowledgeElement(c)
+ for c in self.context.getConcepts((self.providesPred,)))
+
+ def provides(self, obj):
+ self.context.assignConcept(obj.context, self.providesPred)
+
+ def removeProvidedKnowledge(self, obj):
+ self.context.deassignConcept(obj.context, (self.providesPred,))
+
diff --git a/knowledge/tests.py b/knowledge/tests.py
new file mode 100755
index 0000000..888ff78
--- /dev/null
+++ b/knowledge/tests.py
@@ -0,0 +1,24 @@
+# $Id$
+
+import unittest, doctest
+from zope.testing.doctestunit import DocFileSuite
+from zope.app.testing import ztapi
+from zope.interface.verify import verifyClass
+from loops.organize.party import Person
+
+class Test(unittest.TestCase):
+ "Basic tests for the knowledge sub-package."
+
+ def testSomething(self):
+ pass
+
+
+def test_suite():
+ flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
+ return unittest.TestSuite((
+ unittest.makeSuite(Test),
+ DocFileSuite('README.txt', optionflags=flags),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
diff --git a/organize/browser.py b/organize/browser.py
index 849eda2..18aab9c 100644
--- a/organize/browser.py
+++ b/organize/browser.py
@@ -44,13 +44,17 @@ from loops.organize.interfaces import IMemberRegistration
_ = MessageFactory('zope')
+def getPersonForLoggedInUser(request):
+ pa = annotations(request.principal)
+ return pa.get(ANNOTATION_KEY, None)
+
+
class MyStuff(ConceptView):
def __init__(self, context, request):
self.context = context
self.request = request
- pa = annotations(request.principal)
- self.person = pa.get(ANNOTATION_KEY, None)
+ self.person = getPersonForLoggedInUser(request)
if self.person is not None:
self.context = self.person
diff --git a/organize/party.py b/organize/party.py
index 041fdfb..165087f 100644
--- a/organize/party.py
+++ b/organize/party.py
@@ -41,7 +41,7 @@ from loops.organize.interfaces import IPerson, ANNOTATION_KEY
from loops.type import TypeInterfaceSourceList, AdapterBase
-# register IPerson as a type interface - (TODO: use a function for this)
+# register type interfaces - (TODO: use a function for this)
TypeInterfaceSourceList.typeInterfaces += (IPerson, IAddress)
diff --git a/type.py b/type.py
index f5f73ad..67cf412 100644
--- a/type.py
+++ b/type.py
@@ -224,7 +224,7 @@ class AdapterBase(object):
adapts(IConcept)
_attributes = ('context', '__parent__', )
- _schemas = (IConcept,)
+ _schemas = list(IConcept)
def __init__(self, context):
self.context = context # to get the permission stuff right