-
+
+
>> t = searchView.typesForSearch()
>>> len(t)
- 16
+ 15
>>> t.getTermByToken('loops:resource:*').title
'Any Resource'
>>> t = searchView.conceptTypesForSearch()
>>> len(t)
- 13
+ 12
>>> t.getTermByToken('loops:concept:*').title
'Any Concept'
@@ -91,7 +91,7 @@ a controller attribute for the search view.
>>> searchView.submitReplacing('1.results', '1.search.form', pageView)
'submitReplacing("1.results", "1.search.form",
- "http://127.0.0.1/loops/views/page/.target99/@@searchresults.html");...'
+ "http://127.0.0.1/loops/views/page/.target96/@@searchresults.html");...'
Basic (text/title) search
-------------------------
@@ -177,7 +177,7 @@ of the concepts' titles:
>>> request = TestRequest(form=form)
>>> view = Search(page, request)
>>> view.listConcepts()
- u"{identifier: 'id', items: [{label: 'Zope (Thema)', name: 'Zope', id: '104'}, {label: 'Zope 2 (Thema)', name: 'Zope 2', id: '106'}, {label: 'Zope 3 (Thema)', name: 'Zope 3', id: '108'}]}"
+ u"{identifier: 'id', items: [{label: 'Zope (Thema)', name: 'Zope', id: '101'}, {label: 'Zope 2 (Thema)', name: 'Zope 2', id: '103'}, {label: 'Zope 3 (Thema)', name: 'Zope 3', id: '105'}]}"
Preset Concept Types on Search Forms
------------------------------------
@@ -219,13 +219,13 @@ and thus include the customer type in the preset search types.
>>> searchView.conceptsForType('loops:concept:customer')
[{'token': 'none', 'title': u'not selected'},
- {'token': '77', 'title': u'Customer 1'},
- {'token': '79', 'title': u'Customer 2'},
- {'token': '81', 'title': u'Customer 3'}]
+ {'token': '74', 'title': u'Customer 1'},
+ {'token': '76', 'title': u'Customer 2'},
+ {'token': '78', 'title': u'Customer 3'}]
Let's use this new search option for querying:
- >>> form = {'search.4.text_selected': u'77'}
+ >>> form = {'search.4.text_selected': u'74'}
>>> resultsView = SearchResults(page, TestRequest(form=form))
>>> results = list(resultsView.results)
>>> results[0].title
diff --git a/expert/standard.py b/expert/standard.py
new file mode 100644
index 0000000..4814019
--- /dev/null
+++ b/expert/standard.py
@@ -0,0 +1,54 @@
+#
+# Copyright (c) 2013 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
+#
+
+"""
+loops standard reports.
+"""
+
+from zope.cachedescriptors.property import Lazy
+
+from cybertools.util.jeep import Jeep
+from loops.browser.concept import ConceptView
+from loops.expert.field import Field, TargetField, DateField, StateField, \
+ TextField, HtmlTextField, UrlField
+from loops.expert.report import ReportInstance
+
+
+title = UrlField('title', u'Title',
+ description=u'A short descriptive text.',
+ executionSteps=['output'])
+
+
+class TypeInstances(ReportInstance):
+
+ fields = Jeep((title,))
+ defaultOutputFields = fields
+
+ @Lazy
+ def targets(self):
+ targetPredicate = self.view.conceptManager['querytarget']
+ return self.view.context.getChildren([targetPredicate])
+
+ def selectObjects(self, parts):
+ result = []
+ for t in self.targets:
+ for c in t.getChildren([self.view.typePredicate]):
+ result.append(c)
+ print '***', self.targets, result
+ return result
+
diff --git a/external/README.txt b/external/README.txt
index cfbdcb8..bcc1cf5 100644
--- a/external/README.txt
+++ b/external/README.txt
@@ -17,7 +17,7 @@ Let's set up a loops site with basic and example concepts and resources.
>>> concepts, resources, views = t.setup()
>>> loopsRoot = site['loops']
>>> len(concepts), len(resources), len(views)
- (34, 3, 1)
+ (33, 3, 1)
Importing loops Objects
@@ -44,7 +44,7 @@ Creating the corresponding objects
>>> loader = Loader(loopsRoot)
>>> loader.load(elements)
>>> len(concepts), len(resources), len(views)
- (35, 3, 1)
+ (34, 3, 1)
>>> from loops.common import adapted
>>> adMyquery = adapted(concepts['myquery'])
@@ -131,7 +131,7 @@ Extracting elements
>>> extractor = Extractor(loopsRoot, os.path.join(dataDirectory, 'export'))
>>> elements = list(extractor.extract())
>>> len(elements)
- 68
+ 66
Writing object information to the external storage
--------------------------------------------------
diff --git a/integrator/collection.py b/integrator/collection.py
index 35d4a9c..167974b 100644
--- a/integrator/collection.py
+++ b/integrator/collection.py
@@ -197,6 +197,8 @@ class DirectoryCollectionProvider(object):
extFileType = extFileTypes.get(contentType.split('/')[0] + '/*')
if extFileType is None:
extFileType = extFileTypes['*/*']
+ if extFileType is None:
+ extFileType = extFileTypes['image/*']
if extFileType is None:
getLogger('loops.integrator.collection.DirectoryCollectionProvider'
).warn('No external file type found for %r, '
diff --git a/interfaces.py b/interfaces.py
index a552743..1330a6a 100644
--- a/interfaces.py
+++ b/interfaces.py
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
+# Copyright (c) 2013 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
@@ -30,6 +30,7 @@ from zope.app.file.interfaces import IImage as IBaseAsset
from zope.component.interfaces import IObjectEvent
from zope.size.interfaces import ISized
+from cybertools.composer.schema.interfaces import FieldType
from cybertools.relation.interfaces import IDyadicRelation
from cybertools.tracking.interfaces import ITrackingStorage
from cybertools.util.format import toStr, toUnicode
@@ -37,6 +38,11 @@ from loops import util
from loops.util import _
+class HtmlText(schema.Text):
+
+ __typeInfo__ = ('html',)
+
+
# common interfaces
class ILoopsObject(Interface):
diff --git a/knowledge/README.txt b/knowledge/README.txt
index 994b7ae..1617034 100644
--- a/knowledge/README.txt
+++ b/knowledge/README.txt
@@ -170,28 +170,7 @@ For testing, we first have to provide the needed utilities and settings
Competence and Certification Management
=======================================
- >>> from cybertools.stateful.interfaces import IStatesDefinition
- >>> from loops.knowledge.qualification import qualificationStates
- >>> from loops.knowledge.interfaces import IQualificationRecords
- >>> from loops.knowledge.qualification import QualificationRecords
- >>> component.provideUtility(qualificationStates,
- ... provides=IStatesDefinition,
- ... name='knowledge.qualification')
- >>> component.provideAdapter(QualificationRecords,
- ... provides=IQualificationRecords)
-
- >>> qurecs = loopsRoot.getRecordManager()['qualification']
-
-We first create a training that provides knowledge in Python specials.
-
- >>> trainingPySpecC = concepts['trpyspec'] = Concept(
- ... u'Python Specials Training')
- >>> trainingPySpecC.assignParent(pySpecialsC)
-
-Then we record the need for John to acquire this knowledge.
-
- >>> from loops.knowledge.browser import CreateQualificationRecordForm
- >>> from loops.knowledge.browser import CreateQualificationRecord
+ >>> tCompetence = concepts['competence']
Glossaries
@@ -205,6 +184,15 @@ Glossary items are topic-like concepts that may be edited by end users.
>>> from loops.knowledge.glossary.browser import EditGlossaryItem
+Survey
+======
+
+ >>> from loops.knowledge.tests import importSurvey
+ >>> importSurvey(loopsRoot)
+
+ >>> from loops.knowledge.survey.browser import SurveyView
+
+
Fin de partie
=============
diff --git a/knowledge/browser.py b/knowledge/browser.py
index fd9a8ed..6126661 100644
--- a/knowledge/browser.py
+++ b/knowledge/browser.py
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
+# Copyright (c) 2013 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
@@ -30,10 +30,7 @@ from cybertools.typology.interfaces import IType
from loops.browser.action import DialogAction
from loops.browser.common import BaseView
from loops.browser.concept import ConceptView
-from loops.expert.browser.report import ResultsConceptView
from loops.knowledge.interfaces import IPerson, ITask
-from loops.knowledge.qualification import QualificationRecord
-from loops.organize.work.browser import CreateWorkItemForm, CreateWorkItem
from loops.organize.party import getPersonForUser
from loops.util import _
@@ -50,6 +47,7 @@ actions.register('createTopic', 'portlet', DialogAction,
typeToken='.loops/concepts/topic',
fixedType=True,
innerForm='inner_concept_form.html',
+ permission='loops.AssignAsParent',
)
actions.register('editTopic', 'portlet', DialogAction,
@@ -57,6 +55,7 @@ actions.register('editTopic', 'portlet', DialogAction,
description=_(u'Modify topic.'),
viewName='edit_concept.html',
dialogName='editTopic',
+ permission='zope.ManageContent',
)
actions.register('createQualification', 'portlet', DialogAction,
@@ -66,6 +65,7 @@ actions.register('createQualification', 'portlet', DialogAction,
dialogName='createQualification',
prerequisites=['registerDojoDateWidget', 'registerDojoNumberWidget',
'registerDojoTextarea'],
+ permission='loops.AssignAsParent',
)
@@ -111,25 +111,3 @@ class Candidates(ConceptView):
return self.template.macros['requirement_candidates']
-# qualification stuff
-
-class PersonQualificationView(ResultsConceptView):
-
- pass
-
-
-class CreateQualificationRecordForm(CreateWorkItemForm):
-
- macros = knowledge_macros
- recordManagerName = 'qualification'
- trackFactory = QualificationRecord
-
- @Lazy
- def macro(self):
- return self.macros['create_qualification']
-
-
-class CreateQualificationRecord(CreateWorkItem):
-
- pass
-
diff --git a/knowledge/configure.zcml b/knowledge/configure.zcml
index da2bb30..50d67c4 100644
--- a/knowledge/configure.zcml
+++ b/knowledge/configure.zcml
@@ -1,5 +1,3 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -120,5 +89,7 @@
+
+
diff --git a/knowledge/data/loops_knowledge_de.dmp b/knowledge/data/knowledge_de.dmp
similarity index 71%
rename from knowledge/data/loops_knowledge_de.dmp
rename to knowledge/data/knowledge_de.dmp
index 3b2ce7b..d1a0e36 100644
--- a/knowledge/data/loops_knowledge_de.dmp
+++ b/knowledge/data/knowledge_de.dmp
@@ -1,5 +1,6 @@
type(u'competence', u'Kompetenz', viewName=u'',
- typeInterface=u'', options=u'action.portlet:create_subtype,edit_concept')
+ typeInterface=u'loops.knowledge.qualification.interfaces.ICompetence',
+ options=u'action.portlet:create_subtype,edit_concept')
type(u'person', u'Person', viewName=u'',
typeInterface=u'loops.knowledge.interfaces.IPerson',
options=u'action.portlet:createQualification,editPerson')
@@ -9,9 +10,9 @@ type(u'task', u'Aufgabe', viewName=u'',
type(u'topic', u'Thema', viewName=u'',
typeInterface=u'loops.knowledge.interfaces.ITopic',
options=u'action.portlet:createTask,createTopic,editTopic')
-type(u'training', u'Schulung', viewName=u'',
- typeInterface=u'loops.organize.interfaces.ITask',
- options=u'action.portlet:edit_concept')
+#type(u'training', u'Schulung', viewName=u'',
+# typeInterface=u'loops.organize.interfaces.ITask',
+# options=u'action.portlet:edit_concept')
concept(u'general', u'Allgemein', u'domain')
concept(u'system', u'System', u'domain')
@@ -34,11 +35,12 @@ child(u'general', u'provides', u'standard')
child(u'general', u'requires', u'standard')
child(u'general', u'task', u'standard')
child(u'general', u'topic', u'standard')
-child(u'general', u'training', u'standard')
+#child(u'general', u'training', u'standard')
child(u'system', u'issubtype', u'standard')
-child(u'competence', u'training', u'issubtype', usePredicate=u'provides')
+child(u'competence', u'competence', u'issubtype')
+#child(u'competence', u'training', u'issubtype', usePredicate=u'provides')
# records
-records(u'qualification', u'loops.knowledge.qualification.QualificationRecord')
+#records(u'qualification', u'loops.knowledge.qualification.base.QualificationRecord')
diff --git a/knowledge/data/loops_knowledge_update_de.dmp b/knowledge/data/knowledge_update_de.dmp
similarity index 71%
rename from knowledge/data/loops_knowledge_update_de.dmp
rename to knowledge/data/knowledge_update_de.dmp
index 4080f43..403589f 100644
--- a/knowledge/data/loops_knowledge_update_de.dmp
+++ b/knowledge/data/knowledge_update_de.dmp
@@ -1,5 +1,6 @@
type(u'competence', u'Kompetenz', viewName=u'',
- typeInterface=u'', options=u'action.portlet:create_subtype,edit_concept')
+ typeInterface=u'loops.knowledge.qualification.interfaces.ICompetence',
+ options=u'action.portlet:create_subtype,edit_concept')
# type(u'person', u'Person', viewName=u'',
# typeInterface=u'loops.knowledge.interfaces.IPerson',
# options=u'action.portlet:editPerson')
@@ -9,9 +10,9 @@ type(u'competence', u'Kompetenz', viewName=u'',
# type(u'topic', u'Thema', viewName=u'',
# typeInterface=u'loops.knowledge.interfaces.ITopic',
# options=u'action.portlet:createTask,createTopic,editTopic')
-type(u'training', u'Schulung', viewName=u'',
- typeInterface=u'loops.organize.interfaces.ITask',
- options=u'action.portlet:edit_concept')
+#type(u'training', u'Schulung', viewName=u'',
+# typeInterface=u'loops.organize.interfaces.ITask',
+# options=u'action.portlet:edit_concept')
concept(u'general', u'Allgemein', u'domain')
concept(u'system', u'System', u'domain')
@@ -34,11 +35,12 @@ child(u'general', u'provides', u'standard')
child(u'general', u'requires', u'standard')
#child(u'general', u'task', u'standard')
#child(u'general', u'topic', u'standard')
-child(u'general', u'training', u'standard')
+#child(u'general', u'training', u'standard')
child(u'system', u'issubtype', u'standard')
-child(u'competence', u'training', u'issubtype', usePredicate=u'provides')
+child(u'competence', u'competence', u'issubtype')
+#child(u'competence', u'training', u'issubtype', usePredicate=u'provides')
# records
-records(u'qualification', u'loops.knowledge.qualification.QualificationRecord')
+#records(u'qualification', u'loops.knowledge.qualification.base.QualificationRecord')
diff --git a/knowledge/data/survey_de.dmp b/knowledge/data/survey_de.dmp
new file mode 100644
index 0000000..a094261
--- /dev/null
+++ b/knowledge/data/survey_de.dmp
@@ -0,0 +1,25 @@
+# survey types
+type(u'questionnaire', u'Fragebogen', viewName=u'survey.html',
+ typeInterface=u'loops.knowledge.survey.interfaces.IQuestionnaire',
+ options=u'action.portlet:create_subtype,edit_concept')
+type(u'questiongroup', u'Fragengruppe', viewName=u'',
+ typeInterface=u'loops.knowledge.survey.interfaces.IQuestionGroup',
+ options=u'action.portlet:create_subtype,edit_concept\nchildren_append\nshow_navigation')
+type(u'question', u'Frage', viewName=u'',
+ typeInterface=u'loops.knowledge.survey.interfaces.IQuestion',
+ options=u'action.portlet:edit_concept\nshow_navigation')
+ #options=u'action.portlet:create_subtype,edit_concept')
+type(u'feedbackitem', u'Feedback-Element', viewName=u'',
+ typeInterface=u'loops.knowledge.survey.interfaces.IFeedbackItem',
+ options=u'action.portlet:edit_concept\nshow_navigation')
+
+# subtypes
+#child(u'questionnaire', u'questionnaire', u'issubtype')
+#child(u'questionnaire', u'question', u'issubtype')
+child(u'questionnaire', u'questiongroup', u'issubtype')
+child(u'questiongroup', u'question', u'issubtype')
+child(u'questiongroup', u'feedbackitem', u'issubtype')
+#child(u'question', u'feedbackitem', u'issubtype')
+
+# records
+records(u'survey_responses', u'loops.knowledge.survey.response.Response')
diff --git a/knowledge/interfaces.py b/knowledge/interfaces.py
index f1aef6b..5233653 100644
--- a/knowledge/interfaces.py
+++ b/knowledge/interfaces.py
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
+# Copyright (c) 2013 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
@@ -22,18 +22,14 @@ Interfaces for knowledge management and elearning with loops.
from zope.interface import Interface, Attribute
from zope import interface, component, schema
-from zope.i18nmessageid import MessageFactory
-from zope.security.proxy import removeSecurityProxy
from cybertools.knowledge.interfaces import IKnowing, IRequirementProfile
from cybertools.knowledge.interfaces import IKnowledgeElement
-from cybertools.organize.interfaces import IWorkItem, IWorkItems
from loops.interfaces import IConceptSchema, ILoopsAdapter
from loops.organize.interfaces import IPerson as IBasePerson
from loops.organize.interfaces import ITask as IBaseTask
from loops.schema.base import Relation, RelationSet
-
-_ = MessageFactory('loops')
+from loops.util import _
class IPerson(IBasePerson, IKnowing):
@@ -62,13 +58,3 @@ class ITopic(IConceptSchema, IKnowledgeElement, ILoopsAdapter):
""" Just a topic, some general classification concept.
"""
-
-class IQualificationRecord(IWorkItem):
- """ Records needs for qualification (acqusition of competence)
- and corresponding participations in training events etc.
- """
-
-
-class IQualificationRecords(IWorkItems):
- """ Container for qualification records.
- """
diff --git a/knowledge/qualification.py b/knowledge/qualification.py
deleted file mode 100644
index f123c39..0000000
--- a/knowledge/qualification.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#
-# Copyright (c) 2012 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
-#
-
-"""
-Controlling qualification activities of persons.
-
-Central part of CCM competence and certification management framework.
-"""
-
-from zope.component import adapts
-from zope.interface import implementer, implements
-
-from cybertools.stateful.base import Stateful
-from cybertools.stateful.definition import StatesDefinition
-from cybertools.stateful.definition import State, Transition
-from cybertools.stateful.interfaces import IStatesDefinition
-from cybertools.tracking.interfaces import ITrackingStorage
-from loops.knowledge.interfaces import IQualificationRecord, \
- IQualificationRecords
-from loops.organize.work.base import WorkItem, WorkItems
-
-
-@implementer(IStatesDefinition)
-def qualificationStates():
- return StatesDefinition('qualification',
- State('new', 'new', ('assign',),
- color='grey'),
- State('open', 'open',
- ('register',
- #'pass', 'fail',
- 'cancel', 'modify'),
- color='red'),
- State('registered', 'registered',
- ('register', 'pass', 'fail', 'unregister', 'cancel', 'modify'),
- color='yellow'),
- State('passed', 'passed',
- ('cancel', 'close', 'modify', 'open', 'expire'),
- color='green'),
- State('failed', 'failed',
- ('register', 'cancel', 'modify', 'open'),
- color='green'),
- State('expired', 'expired',
- ('register', 'cancel', 'modify', 'open'),
- color='red'),
- State('cancelled', 'cancelled', ('modify', 'open'),
- color='grey'),
- State('closed', 'closed', ('modify', 'open'),
- color='lightblue'),
- # not directly reachable states:
- State('open_x', 'open', ('modify',), color='red'),
- State('registered_x', 'registered', ('modify',), color='yellow'),
- # transitions:
- Transition('assign', 'assign', 'open'),
- Transition('register', 'register', 'registered'),
- Transition('pass', 'pass', 'passed'),
- Transition('fail', 'fail', 'failed'),
- Transition('unregister', 'unregister', 'open'),
- Transition('cancel', 'cancel', 'cancelled'),
- Transition('modify', 'modify', 'open'),
- Transition('close', 'close', 'closed'),
- Transition('open', 'open', 'open'),
- #initialState='open')
- initialState='new') # TODO: handle assignment to competence
-
-
-class QualificationRecord(WorkItem):
-
- implements(IQualificationRecord)
-
- typeName = 'QualificationRecord'
- typeInterface = IQualificationRecord
- statesDefinition = 'knowledge.qualification'
-
- def doAction(self, action, userName, **kw):
- new = self.createNew(action, userName, **kw)
- new.userName = self.userName
- new.doTransition(action)
- new.reindex()
- return new
-
-
-class QualificationRecords(WorkItems):
- """ A tracking storage adapter managing qualification records.
- """
-
- implements(IQualificationRecords)
- adapts(ITrackingStorage)
-
diff --git a/knowledge/qualification/__init__.py b/knowledge/qualification/__init__.py
new file mode 100644
index 0000000..36a440f
--- /dev/null
+++ b/knowledge/qualification/__init__.py
@@ -0,0 +1 @@
+'''package loops.knowledge.qualification'''
\ No newline at end of file
diff --git a/knowledge/qualification/base.py b/knowledge/qualification/base.py
new file mode 100644
index 0000000..32c9910
--- /dev/null
+++ b/knowledge/qualification/base.py
@@ -0,0 +1,42 @@
+#
+# Copyright (c) 2013 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
+#
+
+"""
+Controlling qualification activities of persons.
+
+Central part of CCM competence and certification management framework.
+"""
+
+from zope.component import adapts
+from zope.interface import implementer, implements
+
+from loops.common import AdapterBase
+from loops.knowledge.qualification.interfaces import ICompetence
+from loops.type import TypeInterfaceSourceList
+
+
+TypeInterfaceSourceList.typeInterfaces += (ICompetence,)
+
+
+class Competence(AdapterBase):
+
+ implements(ICompetence)
+
+ _contextAttributes = list(ICompetence)
+
+
diff --git a/knowledge/qualification/browser.py b/knowledge/qualification/browser.py
new file mode 100644
index 0000000..29b53d6
--- /dev/null
+++ b/knowledge/qualification/browser.py
@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2013 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.
+"""
+
+from zope import interface, component
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.cachedescriptors.property import Lazy
+
+from loops.expert.browser.report import ResultsConceptView
+from loops.knowledge.browser import template, knowledge_macros
+from loops.knowledge.qualification.base import QualificationRecord
+
+
+class PersonQualificationView(ResultsConceptView):
+
+ pass
+
diff --git a/knowledge/qualification/configure.zcml b/knowledge/qualification/configure.zcml
new file mode 100644
index 0000000..d8cbc74
--- /dev/null
+++ b/knowledge/qualification/configure.zcml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/knowledge/qualification/interfaces.py b/knowledge/qualification/interfaces.py
new file mode 100644
index 0000000..85a002a
--- /dev/null
+++ b/knowledge/qualification/interfaces.py
@@ -0,0 +1,44 @@
+#
+# Copyright (c) 2013 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.
+"""
+
+from zope.interface import Interface, Attribute
+from zope import interface, component, schema
+
+from loops.interfaces import IConceptSchema
+from loops.util import _
+
+
+class ICompetence(IConceptSchema):
+ """ The competence of a person.
+
+ Maybe assigned to the person via a 'knows' relation or
+ work items of type 'checkup'.
+ """
+
+ validityPeriod = schema.Int(
+ title=_(u'Validity Period (Months)'),
+ description=_(u'Number of months the competence remains valid. '
+ u'Zero means unlimited validity.'),
+ default=0,
+ required=False)
+
+
diff --git a/knowledge/survey/__init__.py b/knowledge/survey/__init__.py
new file mode 100644
index 0000000..d227614
--- /dev/null
+++ b/knowledge/survey/__init__.py
@@ -0,0 +1 @@
+'''package loops.knowledge.survey'''
diff --git a/knowledge/survey/base.py b/knowledge/survey/base.py
new file mode 100644
index 0000000..cacdcc7
--- /dev/null
+++ b/knowledge/survey/base.py
@@ -0,0 +1,124 @@
+#
+# Copyright (c) 2013 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
+#
+
+"""
+Surveys used in knowledge management.
+"""
+
+from zope.component import adapts
+from zope.interface import implementer, implements
+
+from cybertools.knowledge.survey.questionnaire import Questionnaire, \
+ QuestionGroup, Question, FeedbackItem
+from loops.common import adapted, AdapterBase
+from loops.knowledge.survey.interfaces import IQuestionnaire, \
+ IQuestionGroup, IQuestion, IFeedbackItem
+from loops.type import TypeInterfaceSourceList
+
+
+TypeInterfaceSourceList.typeInterfaces += (IQuestionnaire,
+ IQuestionGroup, IQuestion, IFeedbackItem)
+
+
+class Questionnaire(AdapterBase, Questionnaire):
+
+ implements(IQuestionnaire)
+
+ _contextAttributes = list(IQuestionnaire)
+ _adapterAttributes = AdapterBase._adapterAttributes + (
+ 'questionGroups', 'questions', 'responses',)
+ _noexportAttributes = _adapterAttributes
+
+ @property
+ def questionGroups(self):
+ return [adapted(c) for c in self.context.getChildren()]
+
+ @property
+ def questions(self):
+ for qug in self.questionGroups:
+ for qu in qug.questions:
+ #qu.questionnaire = self
+ yield qu
+
+
+class QuestionGroup(AdapterBase, QuestionGroup):
+
+ implements(IQuestionGroup)
+
+ _contextAttributes = list(IQuestionGroup)
+ _adapterAttributes = AdapterBase._adapterAttributes + (
+ 'questionnaire', 'questions', 'feedbackItems')
+ _noexportAttributes = _adapterAttributes
+
+ @property
+ def questionnaire(self):
+ for p in self.context.getParents():
+ ap = adapted(p)
+ if IQuestionnaire.providedBy(ap):
+ return ap
+
+ @property
+ def subobjects(self):
+ return [adapted(c) for c in self.context.getChildren()]
+
+ @property
+ def questions(self):
+ return [obj for obj in self.subobjects if IQuestion.providedBy(obj)]
+
+ @property
+ def feedbackItems(self):
+ return [obj for obj in self.subobjects if IFeedbackItem.providedBy(obj)]
+
+
+class Question(AdapterBase, Question):
+
+ implements(IQuestion)
+
+ _contextAttributes = list(IQuestion)
+ _adapterAttributes = AdapterBase._adapterAttributes + (
+ 'text', 'questionnaire', 'answerRange', 'feedbackItems',)
+ _noexportAttributes = _adapterAttributes
+
+ @property
+ def text(self):
+ return self.context.description
+
+ @property
+ def questionGroup(self):
+ for p in self.context.getParents():
+ ap = adapted(p)
+ if IQuestionGroup.providedBy(ap):
+ return ap
+
+ @property
+ def questionnaire(self):
+ return self.questionGroup.questionnaire
+
+
+class FeedbackItem(AdapterBase, FeedbackItem):
+
+ implements(IFeedbackItem)
+
+ _contextAttributes = list(IFeedbackItem)
+ _adapterAttributes = AdapterBase._adapterAttributes + (
+ 'text',)
+ _noexportAttributes = _adapterAttributes
+
+ @property
+ def text(self):
+ return self.context.description
diff --git a/knowledge/survey/browser.py b/knowledge/survey/browser.py
new file mode 100644
index 0000000..6b6c767
--- /dev/null
+++ b/knowledge/survey/browser.py
@@ -0,0 +1,174 @@
+#
+# Copyright (c) 2013 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
+surveys and self-assessments.
+"""
+
+import csv
+from cStringIO import StringIO
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.cachedescriptors.property import Lazy
+from zope.i18n import translate
+
+from cybertools.knowledge.survey.questionnaire import Response
+from cybertools.util.date import formatTimeStamp
+from loops.browser.concept import ConceptView
+from loops.browser.node import NodeView
+from loops.common import adapted
+from loops.knowledge.survey.response import Responses
+from loops.organize.party import getPersonForUser
+from loops.util import getObjectForUid
+from loops.util import _
+
+
+template = ViewPageTemplateFile('view_macros.pt')
+
+class SurveyView(ConceptView):
+
+ tabview = 'index.html'
+ data = None
+ errors = None
+
+ @Lazy
+ def macro(self):
+ return template.macros['survey']
+
+ def results(self):
+ result = []
+ response = None
+ form = self.request.form
+ if 'submit' in form:
+ self.data = {}
+ response = Response(self.adapted, None)
+ for key, value in form.items():
+ if key.startswith('question_'):
+ uid = key[len('question_'):]
+ question = adapted(self.getObjectForUid(uid))
+ if value != 'none':
+ value = int(value)
+ self.data[uid] = value
+ response.values[question] = value
+ Responses(self.context).save(self.data)
+ self.errors = self.check(response)
+ if self.errors:
+ return []
+ if response is not None:
+ result = response.getGroupedResult()
+ return [dict(category=r[0].title, text=r[1].text,
+ score=int(round(r[2] * 100)))
+ for r in result]
+
+ def check(self, response):
+ errors = []
+ values = response.values
+ for qu in self.adapted.questions:
+ if qu.required and qu not in values:
+ errors.append('Please answer the obligatory questions.')
+ break
+ qugroups = {}
+ for qugroup in self.adapted.questionGroups:
+ qugroups[qugroup] = 0
+ for qu in values:
+ qugroups[qu.questionGroup] += 1
+ for qugroup, count in qugroups.items():
+ minAnswers = qugroup.minAnswers
+ if minAnswers in (u'', None):
+ minAnswers = len(qugroup.questions)
+ if count < minAnswers:
+ errors.append('Please answer the minimum number of questions.')
+ break
+ return errors
+
+ def getInfoText(self, qugroup):
+ lang = self.languageInfo.language
+ text = qugroup.description
+ info = None
+ if qugroup.minAnswers in (u'', None):
+ info = translate(_(u'Please answer all questions.'), target_language=lang)
+ elif qugroup.minAnswers > 0:
+ info = translate(_(u'Please answer at least $minAnswers questions.',
+ mapping=dict(minAnswers=qugroup.minAnswers)),
+ target_language=lang)
+ if info:
+ text = u'%s (%s)' % (text, info)
+ return text
+
+ def getValues(self, question):
+ setting = None
+ if self.data is None:
+ self.data = Responses(self.context).load()
+ if self.data:
+ setting = self.data.get(question.uid)
+ noAnswer = [dict(value='none', checked=(setting == None),
+ radio=(not question.required))]
+ return noAnswer + [dict(value=i, checked=(setting == i), radio=True)
+ for i in reversed(range(question.answerRange))]
+
+
+class SurveyCsvExport(NodeView):
+
+ encoding = 'ISO8859-15'
+
+ def encode(self, text):
+ text.encode(self.encoding)
+
+ @Lazy
+ def questions(self):
+ result = []
+ for idx1, qug in enumerate(adapted(self.virtualTargetObject).questionGroups):
+ for idx2, qu in enumerate(qug.questions):
+ result.append((idx1, idx2, qug, qu))
+ return result
+
+ @Lazy
+ def columns(self):
+ infoCols = ['Name', 'Timestamp']
+ dataCols = ['%02i-%02i' % (item[0], item[1]) for item in self.questions]
+ return infoCols + dataCols
+
+ def getRows(self):
+ for tr in Responses(self.virtualTargetObject).getAllTracks():
+ p = adapted(getObjectForUid(tr.userName))
+ name = p and p.title or u'???'
+ ts = formatTimeStamp(tr.timeStamp)
+ cells = [tr.data.get(qu.uid, -1)
+ for (idx1, idx2, qug, qu) in self.questions]
+ yield [name, ts] + cells
+
+ def __call__(self):
+ f = StringIO()
+ writer = csv.writer(f, delimiter=',')
+ writer.writerow(self.columns)
+ for row in self.getRows():
+ writer.writerow(row)
+ text = f.getvalue()
+ self.setDownloadHeader(text)
+ return text
+
+ def setDownloadHeader(self, text):
+ response = self.request.response
+ filename = 'survey_data.csv'
+ response.setHeader('Content-Disposition',
+ 'attachment; filename=%s' % filename)
+ response.setHeader('Cache-Control', '')
+ response.setHeader('Pragma', '')
+ response.setHeader('Content-Length', len(text))
+ response.setHeader('Content-Type', 'text/csv')
+
diff --git a/knowledge/survey/configure.zcml b/knowledge/survey/configure.zcml
new file mode 100644
index 0000000..423889d
--- /dev/null
+++ b/knowledge/survey/configure.zcml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/knowledge/survey/interfaces.py b/knowledge/survey/interfaces.py
new file mode 100644
index 0000000..7ce5849
--- /dev/null
+++ b/knowledge/survey/interfaces.py
@@ -0,0 +1,93 @@
+#
+# Copyright (c) 2013 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 surveys used in knowledge management.
+"""
+
+from zope.interface import Interface, Attribute
+from zope import interface, component, schema
+
+from cybertools.knowledge.survey import interfaces
+from loops.interfaces import IConceptSchema, ILoopsAdapter
+from loops.util import _
+
+
+class IQuestionnaire(IConceptSchema, interfaces.IQuestionnaire):
+ """ A collection of questions for setting up a survey.
+ """
+
+ defaultAnswerRange = schema.Int(
+ title=_(u'Answer Range'),
+ description=_(u'Number of items (answer options) to select from.'),
+ default=4,
+ required=True)
+
+ feedbackFooter = schema.Text(
+ title=_(u'Feedback Footer'),
+ description=_(u'Text that will appear at the end of the feedback page.'),
+ default=u'',
+ missing_value=u'',
+ required=False)
+
+
+class IQuestionGroup(IConceptSchema, interfaces.IQuestionGroup):
+ """ A group of questions within a questionnaire.
+ """
+
+ minAnswers = schema.Int(
+ title=_(u'Minimum Number of Answers'),
+ description=_(u'Minumum number of questions that have to be answered. '
+ 'Empty means all questions have to be answered.'),
+ default=None,
+ required=False)
+
+
+class IQuestion(IConceptSchema, interfaces.IQuestion):
+ """ A single question within a questionnaire.
+ """
+
+ required = schema.Bool(
+ title=_(u'Required'),
+ description=_(u'Question must be answered.'),
+ default=False,
+ required=False)
+
+ revertAnswerOptions = schema.Bool(
+ title=_(u'Negative'),
+ description=_(u'Value inversion: High selection means low value.'),
+ default=False,
+ required=False)
+
+
+class IFeedbackItem(IConceptSchema, interfaces.IFeedbackItem):
+ """ Some text (e.g. a recommendation) or some other kind of information
+ that may be deduced from the res)ponses to a questionnaire.
+ """
+
+
+class IResponse(interfaces.IResponse):
+ """ A set of response values given to the questions of a questionnaire
+ by a single person or party.
+ """
+
+
+class IResponses(Interface):
+ """ A container or manager of survey responses.
+ """
+
diff --git a/knowledge/survey/response.py b/knowledge/survey/response.py
new file mode 100644
index 0000000..b27979e
--- /dev/null
+++ b/knowledge/survey/response.py
@@ -0,0 +1,63 @@
+#
+# Copyright (c) 2013 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
+#
+
+"""
+Handling survey responses.
+"""
+
+from zope.component import adapts
+from zope.interface import implements
+
+from cybertools.tracking.btree import Track
+from cybertools.tracking.interfaces import ITrackingStorage
+from loops.knowledge.survey.interfaces import IResponse, IResponses
+from loops.organize.tracking.base import BaseRecordManager
+
+
+class Responses(BaseRecordManager):
+
+ implements(IResponses)
+
+ storageName = 'survey_responses'
+
+ def __init__(self, context):
+ self.context = context
+
+ def save(self, data):
+ if not self.personId:
+ return
+ self.storage.saveUserTrack(self.uid, 0, self.personId, data,
+ update=True, overwrite=True)
+
+ def load(self):
+ if self.personId:
+ tracks = self.storage.getUserTracks(self.uid, 0, self.personId)
+ if tracks:
+ return tracks[0].data
+ return {}
+
+ def getAllTracks(self):
+ return self.storage.query(taskId=self.uid)
+
+
+class Response(Track):
+
+ implements(IResponse)
+
+ typeName = 'Response'
+
diff --git a/knowledge/survey/view_macros.pt b/knowledge/survey/view_macros.pt
new file mode 100644
index 0000000..49731dc
--- /dev/null
+++ b/knowledge/survey/view_macros.pt
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
Private Comment