diff --git a/cyberapps/ccmkg/README.txt b/cyberapps/ccmkg/README.txt
new file mode 100644
index 0000000..4eb970e
--- /dev/null
+++ b/cyberapps/ccmkg/README.txt
@@ -0,0 +1,86 @@
+=======================
+CyberConcepts Marketing
+=======================
+
+
+Note: This package depends on loops.
+
+Let's do some basic set up
+
+ >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
+ >>> site = placefulSetUp(True)
+
+ >>> from zope import component, interface
+
+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.interfaces import ILoops, IConcept
+ >>> from loops.concept import Concept
+ >>> from loops.setup import ISetupManager
+
+from loops.knowledge.setup import SetupManager
+component.provideAdapter(SetupManager, (ILoops,), ISetupManager,
+ name='knowledge')
+
+ >>> from loops.tests.setup import TestSite
+ >>> t = TestSite(site)
+ >>> concepts, resources, views = t.setup()
+
+
+Project References
+==================
+
+ >>> from cyberapps.ccmkg.interfaces import IProjectReference
+ >>> from cyberapps.ccmkg.data import ProjectReferenceAdapter
+ >>> component.provideAdapter(ProjectReferenceAdapter)
+ >>> typeConcept = concepts.getTypeConcept()
+ >>> from loops.setup import addAndConfigureObject
+
+We can now create the project reference concept type...
+
+ >>> tProjRef = addAndConfigureObject(concepts, Concept, 'projectreference',
+ ... title=u'Project Reference', conceptType=typeConcept,
+ ... typeInterface=IProjectReference)
+
+... and a few projectreferences.
+
+ >>> ref1 = addAndConfigureObject(concepts, Concept, 'ref1',
+ ... title=u'Reference #1', conceptType=tProjRef,
+ ... timeRange=u'2006-06', task=u'Development',
+ ... customerInfo=u'Goggle Inc', technology=u'Zope 3')
+ >>> ref2 = addAndConfigureObject(concepts, Concept, 'ref2',
+ ... title=u'Reference #2', conceptType=tProjRef,
+ ... timeRange=u'2007-01 to 2007-04', task=u'Development',
+ ... customerInfo=u'San Narciso College', technology=u'Python')
+ >>> ref3 = addAndConfigureObject(concepts, Concept, 'ref3',
+ ... title=u'Reference #3', conceptType=tProjRef,
+ ... timeRange=u'2007-01 to 2007-05', task=u'Consulting',
+ ... customerInfo=u'MASA', technology=u'Linux')
+
+The Project Listing view
+------------------------
+
+ >>> from cybertools.reporter.resultset import ContentRow
+ >>> from cybertools.reporter.interfaces import IRow
+ >>> component.provideAdapter(ContentRow, provides=IRow)
+
+ >>> from cyberapps.ccmkg.browser import ProjectListing
+ >>> from zope.publisher.browser import TestRequest
+ >>> from loops.view import Node
+
+ >>> node = views['n1'] = Node()
+ >>> node.target = tProjRef
+
+ >>> #view = ProjectListing(node, TestRequest())
+ >>> view = ProjectListing(tProjRef, TestRequest())
+
+ >>> rs = view.resultSet
+ >>> rows = list(rs.getRows())
+ >>> for r in rows:
+ ... data = r.applyTemplate()
+ ... print(data['title'], data['timeRange'], data['customerInfo'])
+ Reference #3 2007-01 to 2007-05 MASA
+ Reference #2 2007-01 to 2007-04 San Narciso College
+ Reference #1 2006-06 Goggle Inc
diff --git a/cyberapps/ccmkg/__init__.py b/cyberapps/ccmkg/__init__.py
new file mode 100644
index 0000000..38314f3
--- /dev/null
+++ b/cyberapps/ccmkg/__init__.py
@@ -0,0 +1,3 @@
+"""
+$Id$
+"""
diff --git a/cyberapps/ccmkg/browser.py b/cyberapps/ccmkg/browser.py
new file mode 100644
index 0000000..2f77f86
--- /dev/null
+++ b/cyberapps/ccmkg/browser.py
@@ -0,0 +1,93 @@
+#
+# Copyright (c) 2007 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
+#
+
+"""
+View classes for cyberconcepts marketing.
+
+$Id$
+"""
+
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.cachedescriptors.property import Lazy
+
+from cybertools.composer.schema import Schema
+from cybertools.composer.schema import Field
+from cybertools.reporter.browser.report import DetailView, ListingView
+from cybertools.reporter.resultset import ResultSet, Cell
+#from loops.browser.node import NodeView
+from loops.browser.action import DialogAction
+from loops.browser.concept import ConceptView
+from loops.common import adapted
+from loops import util
+
+
+class ProjectDetail(ConceptView, DetailView):
+
+ def getActions(self, category='object', page=None, target=None):
+ actions = []
+ if category == 'portlet':
+ actions.append(DialogAction(self, title='Edit Project Reference...',
+ description='Modify project reference.',
+ viewName='edit_concept.html',
+ dialogName='editProjectReference',
+ page=page, target=target))
+ return actions
+
+
+
+listingTemplate = ViewPageTemplateFile('macros.pt')
+
+
+class ProjectListing(ConceptView, ListingView):
+
+ @property
+ def macro(self):
+ return listingTemplate.macros['listing']
+
+ def getActions(self, category='object', page=None, target=None):
+ actions = []
+ if category == 'portlet':
+ actions.append(DialogAction(self, title='Create Project Reference...',
+ description='Create a new project reference.',
+ viewName='create_concept.html',
+ dialogName='createProjectReference',
+ typeToken='.loops/concepts/projectreference',
+ fixedType=True,
+ innerForm='inner_concept_form.html',
+ page=page, target=target))
+ return actions
+
+ @property
+ def children(self):
+ objs = sorted((adapted(c) for c in self.context.getChildren(sort=None)),
+ key=lambda x: x.timeRange, reverse=True)
+ return objs
+
+ @Lazy
+ def resultSet(self):
+ result = ResultSet(self.children)
+ result.schema = Schema(
+ Field(u'title'),
+ Field(u'timeRange', u'Zeitraum'),
+ Field(u'customerInfo', u'Kunde'),
+ Field(u'task', u'Taetigkeit'),
+ Field(u'technology', u'Technik'),
+ )
+ result.view = self
+ return result
+
diff --git a/cyberapps/ccmkg/configure.zcml b/cyberapps/ccmkg/configure.zcml
new file mode 100644
index 0000000..d98d96f
--- /dev/null
+++ b/cyberapps/ccmkg/configure.zcml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cyberapps/ccmkg/data.py b/cyberapps/ccmkg/data.py
new file mode 100644
index 0000000..ae32985
--- /dev/null
+++ b/cyberapps/ccmkg/data.py
@@ -0,0 +1,22 @@
+# cyberapps.ccmkg.data
+
+""" Classes for Cyberconcepts Marketing.
+"""
+
+from zope.component import adapts
+from zope.interface import implementer
+
+from cyberapps.ccmkg.interfaces import IProjectReference
+from loops.common import AdapterBase
+from loops.interfaces import IConcept
+from loops.type import TypeInterfaceSourceList
+
+
+TypeInterfaceSourceList.typeInterfaces += (IProjectReference,)
+
+
+@implementer(IProjectReference)
+class ProjectReferenceAdapter(AdapterBase):
+
+ _contextAttributes = list(IProjectReference) + list(IConcept)
+
diff --git a/cyberapps/ccmkg/interfaces.py b/cyberapps/ccmkg/interfaces.py
new file mode 100644
index 0000000..ccc6fa3
--- /dev/null
+++ b/cyberapps/ccmkg/interfaces.py
@@ -0,0 +1,55 @@
+#
+# Copyright (c) 2007 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 organizational stuff like persons, addresses, tasks, ...
+
+$Id$
+"""
+
+from zope.interface import Interface, Attribute
+from zope import schema
+
+from loops.interfaces import IConceptSchema, ILoopsAdapter
+from loops.util import _
+
+
+class IProjectReference(IConceptSchema, ILoopsAdapter):
+ """ Project information to be used as customer reference for
+ marketing purposes.
+ """
+
+ customerInfo = schema.TextLine(
+ title=_(u'Customer'),
+ description=_(u'Short description or - if appropriate - '
+ 'name of the customer'),
+ required=False,)
+ timeRange = schema.TextLine(
+ title=_(u'Time range'),
+ description=_(u'Info about the time range of the project'),
+ required=False,)
+ task = schema.TextLine(
+ title=_(u'Task'),
+ description=_(u'Short description of our task in the project'),
+ required=False,)
+ technology = schema.TextLine(
+ title=_(u'Technology'),
+ description=_(u'Info about the technology employed in '
+ 'the project'),
+ required=False,)
+
diff --git a/cyberapps/ccmkg/macros.pt b/cyberapps/ccmkg/macros.pt
new file mode 100644
index 0000000..2b7f19a
--- /dev/null
+++ b/cyberapps/ccmkg/macros.pt
@@ -0,0 +1,34 @@
+
+
+
+
+ Something
+
+
+
+
+
+
+
+
+
+
+
+
+ Fieldname :
+
+ Value
+
+
+
+
+
+
+
+
+
diff --git a/cyberapps/ccmkg/tests.py b/cyberapps/ccmkg/tests.py
new file mode 100755
index 0000000..2f5f103
--- /dev/null
+++ b/cyberapps/ccmkg/tests.py
@@ -0,0 +1,23 @@
+# cyberapps.ccmkg.tests
+
+import unittest, doctest
+from zope.testing.doctestunit import DocFileSuite
+from zope.interface.verify import verifyClass
+
+
+class Test(unittest.TestCase):
+ "Basic tests for the cyberapps.ccmkg 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/cyberapps/knowledge/README.txt b/cyberapps/knowledge/README.txt
new file mode 100644
index 0000000..b95e219
--- /dev/null
+++ b/cyberapps/knowledge/README.txt
@@ -0,0 +1,62 @@
+=============================
+Knowledge Management Specials
+=============================
+
+Note: This package depends on loops.
+
+Let's do some basic set up
+
+ >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
+ >>> site = placefulSetUp(True)
+
+ >>> from zope import component, interface
+
+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.interfaces import ILoops, IConcept
+ >>> from loops.concept import Concept
+
+ >>> from loops.tests.setup import TestSite
+ >>> t = TestSite(site)
+ >>> concepts, resources, views = t.setup()
+ >>> loopsRoot = site['loops']
+
+We then import a loops .dmp file containing all necessary types and
+predicates.
+
+ >>> from loops.knowledge.tests import importData
+ >>> importData(loopsRoot)
+
+ >>> from cyberapps.knowledge.tests import importData
+ >>> importData(loopsRoot)
+
+
+Job Positions
+=============
+
+Job positions, qualifications, interpersonal skills
+---------------------------------------------------
+
+ >>> from cyberapps.knowledge.browser.qualification import JobPositionsOverview
+ >>> from cyberapps.knowledge.browser.qualification import IPSkillsForm
+
+Person data
+-----------
+
+ >>> from cyberapps.knowledge.browser.person import JobPersonsOverview
+
+
+Competences Questionnaire
+=========================
+
+ >>> from cyberapps.knowledge.data import IPSkillsQuestionnaire
+
+
+Reporting
+=========
+
+ >>> from cyberapps.knowledge.browser.report import JobsListing, JobDescription
+
+
\ No newline at end of file
diff --git a/cyberapps/knowledge/__init__.py b/cyberapps/knowledge/__init__.py
new file mode 100644
index 0000000..972e910
--- /dev/null
+++ b/cyberapps/knowledge/__init__.py
@@ -0,0 +1,3 @@
+"""
+cyberapps.knowledge
+"""
diff --git a/cyberapps/knowledge/browser/__init__.py b/cyberapps/knowledge/browser/__init__.py
new file mode 100644
index 0000000..afe7e3b
--- /dev/null
+++ b/cyberapps/knowledge/browser/__init__.py
@@ -0,0 +1,3 @@
+"""
+cyberapps.knowledge.browser
+"""
diff --git a/cyberapps/knowledge/browser/chart.js b/cyberapps/knowledge/browser/chart.js
new file mode 100644
index 0000000..869a30e
--- /dev/null
+++ b/cyberapps/knowledge/browser/chart.js
@@ -0,0 +1,32 @@
+/* */
diff --git a/cyberapps/knowledge/browser/configure.zcml b/cyberapps/knowledge/browser/configure.zcml
new file mode 100644
index 0000000..facc4f6
--- /dev/null
+++ b/cyberapps/knowledge/browser/configure.zcml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cyberapps/knowledge/browser/knowledge.css b/cyberapps/knowledge/browser/knowledge.css
new file mode 100644
index 0000000..5687a37
--- /dev/null
+++ b/cyberapps/knowledge/browser/knowledge.css
@@ -0,0 +1,112 @@
+/*
+
+ Basic settings for knowledge management.
+
+*/
+
+.input input, div.dijitInputField {
+ font-size: 100%;
+ font-family: monospace;
+}
+
+table.grid td, th {
+ vertical-align: top;
+ border: 1px solid black;
+}
+
+tr.grid td, th {
+ vertical-align: top;
+ border: 1px solid black;
+}
+
+tr.grid td.rbcolumn {
+ border-left: none;
+ border-right: none;
+ text-align: center;
+}
+
+tr.grid td.rbcolumn input {
+ margin-top: 0;
+}
+
+tr.grid td.rbcolumnlast {
+ border-left: none;
+ text-align: center;
+}
+
+tr.grid td.rbcolumnlast input {
+ margin-top: 0;
+}
+
+tr.headline td, th {
+ font-size: 110%;
+}
+
+td.optional {
+ color: #666666;
+}
+
+table.jpdesc_admin div.label {
+ font-size: 140%;
+}
+
+table.jpdesc_admin div.description {
+ font-style: italic;
+}
+
+table.jpdesc_admin textarea {
+ border: none;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ background-color: #f8f8f8;
+}
+
+table.jpdesc_admin textarea[disabled] {
+ background-color: white;
+}
+
+table.jpdesc_workdesc tr.label td {
+ padding-top: 1.5em;
+}
+
+table.jpdesc_workdesc span.label {
+ font-size: 140%;
+}
+
+table.jpdesc_workdesc div.description {
+ font-style: italic;
+}
+
+table.jpdesc_workdesc td.input input {
+ border: none;
+ width: 100%;
+ padding: 0;
+ background-color: #f8f8f8;
+}
+
+table.jpdesc_qualif tr.label td {
+ padding-top: 1.5em;
+}
+
+table.jpdesc_qualif span.label {
+ font-weight: bold;
+ font-size: 120%;
+}
+
+table.jpdesc_qualif div.description {
+ font-style: italic;
+}
+
+table.jpdesc_qualif input {
+ border: none;
+ width: 100%;
+ padding: 0;
+ background-color: #f8f8f8;
+}
+
+@media print {
+ td.optional {
+ color: Black;
+ }
+}
diff --git a/cyberapps/knowledge/browser/person.py b/cyberapps/knowledge/browser/person.py
new file mode 100644
index 0000000..cb0af19
--- /dev/null
+++ b/cyberapps/knowledge/browser/person.py
@@ -0,0 +1,428 @@
+#
+# Copyright (c) 2015 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
+cyberapps.knowledge package.
+
+Person data (competences, preferences, qualifications) views.
+"""
+
+from zope.app.container.interfaces import INameChooser
+from zope import interface, component
+from zope.app.container.interfaces import INameChooser
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.i18n import translate
+from zope.cachedescriptors.property import Lazy
+from zope.traversing.api import getName
+
+from cybertools.organize.interfaces import IPerson
+from cybertools.stateful.interfaces import IStateful
+from loops.browser.concept import ConceptView
+from loops.common import adapted, baseObject
+from loops.concept import Concept
+from loops.knowledge.survey.interfaces import IQuestionnaire
+from loops.organize.party import getPersonForUser
+from loops.setup import addObject
+from loops import util
+
+from cyberapps.knowledge.browser.qualification import QualificationBaseView
+from cyberapps.knowledge.browser.qualification import template as baseTemplate
+from cyberapps.knowledge.interfaces import IJobPosition
+from cyberapps.knowledge.interfaces import IQualificationsRecorded, ISkillsRecorded
+from cyberapps.knowledge.interfaces import _
+
+
+template = ViewPageTemplateFile('person_macros.pt')
+
+
+class JobPersonsOverview(QualificationBaseView, ConceptView):
+
+ template = template
+ baseTemplate = baseTemplate
+ macroName = 'persons'
+
+ def update(self):
+ form = self.request.form
+ instUid = form.get('select_institution')
+ if instUid:
+ return self.setInstitution(instUid)
+
+ @Lazy
+ def persons(self):
+ self.setupController()
+ result = {}
+ if self.options('hide_master'):
+ result ['master'] = []
+ else:
+ result['master'] = [PersonView(p, self.request)
+ for p in self.institution.getChildren([self.masterPredicate])]
+ result['member'] = [PersonView(p, self.request)
+ for p in self.institution.getChildren([self.memberPredicate])]
+ result['other'] = [PersonView(p, self.request,)
+ for p in self.institution.getChildren([self.defaultPredicate])
+ if IPerson.providedBy(adapted(p))]
+ return result
+
+
+class PersonView(QualificationBaseView, ConceptView):
+
+ template = template
+ baseTemplate = baseTemplate
+
+ pageTitle = None
+
+ @Lazy
+ def title(self):
+ return self.getTitle()
+
+ def getTitle(self):
+ if self.pageTitle is None:
+ return self.context.title
+ lang = self.languageInfo.language
+ pageTitle = translate(_(self.pageTitle), target_language=lang)
+ return '%s: %s' % (pageTitle, self.context.title)
+
+ @Lazy
+ def breadcrumbsParent(self):
+ for p in self.context.conceptType.getParents([self.queryTargetPredicate]):
+ return self.nodeView.getViewForTarget(p)
+ for p in self.context.getParents([self.queryTargetPredicate]):
+ return self.nodeView.getViewForTarget(p)
+
+ @Lazy
+ def jobs(self):
+ result = []
+ for ch in self.institution.getChildren([self.defaultPredicate]):
+ job = adapted(ch)
+ if IJobPosition.providedBy(job):
+ result.append(job)
+ return result
+
+ def jobAssignments(self):
+ jobs = []
+ for p in self.context.getParents([self.defaultPredicate]):
+ job = adapted(p)
+ if IJobPosition.providedBy(job):
+ jobs.append(job)
+ if jobs:
+ state = 'active'
+ text = 'active'
+ else:
+ state = 'none'
+ text = 'to be done'
+ action = 'edit_jobassignments.html'
+ editUrl = '%s/%s' % (self.nodeView.getUrlForTarget(self.context), action)
+ return dict(text=text, editUrl=editUrl, jobs=jobs)
+
+ def qualifications(self):
+ qualifications = self.getQualifications(adapted(self.target))
+ if qualifications is None:
+ state = 'none'
+ text = 'to be done'
+ else:
+ stf = component.getAdapter(baseObject(qualifications), IStateful,
+ name='task_states')
+ state = stf.state
+ text = stf.getStateObject().title
+ action = 'edit_qualifications.html'
+ editUrl = '%s/%s' % (self.nodeView.getUrlForTarget(self.context), action)
+ return dict(text=text, editUrl=editUrl)
+
+ def skills(self):
+ skills = self.getSkills(adapted(self.target))
+ if not skills:
+ state = 'none'
+ text = 'to be done'
+ else:
+ stf = component.getAdapter(baseObject(skills), IStateful,
+ name='task_states')
+ state = stf.state
+ text = stf.getStateObject().title
+ action = 'edit_skills.html'
+ editUrl = '%s/%s' % (self.nodeView.getUrlForTarget(self.context), action)
+ return dict(text=text, editUrl=editUrl)
+
+ def ipskills(self):
+ ipskills = []
+ if not ipskills:
+ state = 'none'
+ text = 'to be done'
+ questionnaire = None
+ for ch in self.breadcrumbsParent.context.getChildren(
+ [self.defaultPredicate]):
+ qu = adapted(ch)
+ if IQuestionnaire.providedBy(qu):
+ questionnaire = ch
+ break
+ if questionnaire is None:
+ return dict(text='questionnaire missing', editUrl=None)
+ personUid = util.getUidForObject(self.context)
+ storage = self.loopsRoot['records']['survey_responses']
+ tracks = storage.getUserTracks(qu.uid, 0, personUid)
+ if tracks:
+ #text = state = 'draft'
+ text = state = tracks[0].data.get('state') or 'draft'
+ editUrl = '%s?person=%s' % (
+ self.nodeView.getUrlForTarget(questionnaire), personUid)
+ return dict(text=text, editUrl=editUrl)
+
+ def preferences(self):
+ preferences = []
+ if not preferences:
+ state = 'none'
+ text = 'to be done'
+ questionnaire = None
+ idx = 0
+ for ch in self.breadcrumbsParent.context.getChildren(
+ [self.defaultPredicate]):
+ qu = adapted(ch)
+ if IQuestionnaire.providedBy(qu):
+ if idx > 0:
+ questionnaire = ch
+ break
+ idx += 1
+ if questionnaire is None:
+ return dict(text='questionnaire missing', editUrl=None)
+ personUid = util.getUidForObject(self.context)
+ storage = self.loopsRoot['records']['survey_responses']
+ tracks = storage.getUserTracks(qu.uid, 0, personUid)
+ if tracks:
+ text = state = 'draft'
+ editUrl = '%s?person=%s' % (
+ self.nodeView.getUrlForTarget(questionnaire), personUid)
+ return dict(text=text, editUrl=editUrl)
+
+ def getQualifications(self, person):
+ for c in baseObject(person).getChildren():
+ obj = adapted(c)
+ if IQualificationsRecorded.providedBy(obj):
+ return obj
+
+ def getSkills(self, person):
+ for c in baseObject(person).getChildren():
+ obj = adapted(c)
+ if ISkillsRecorded.providedBy(obj):
+ return obj
+
+
+class ReferredListing(JobPersonsOverview):
+
+ macroName = 'referred_listing'
+
+ @Lazy
+ def persons(self):
+ self.setupController()
+ for ch in self.context.getChildren([self.defaultPredicate]):
+ if IQuestionnaire.providedBy(adapted(ch)):
+ baseUrl = self.nodeView.getUrlForTarget(ch)
+ break
+ else:
+ return [dict(title='Questionnaire missing')]
+ me = getPersonForUser(self.context, self.request)
+ result = [adapted(p) for p in self.institution.getChildren()
+ if p != me]
+ result = [dict(title=p.title,
+ url='%s?person=%s' % (baseUrl, p.uid))
+ for p in result if IPerson.providedBy(p)]
+ return result
+
+
+class JobAssignmentsForm(PersonView):
+ """ Form for assigning jobs to a person.
+ """
+
+ macroName = 'jobassignmentsform'
+
+ pageTitle = 'label_jobs_assigned'
+
+ def getData(self):
+ result = []
+ assignments = self.jobAssignments()
+ for job in self.jobs:
+ checked = job in assignments['jobs']
+ result.append(dict(title=job.title, uid=job.uid, checked=checked))
+ return result
+
+ def update(self):
+ form = self.request.form
+ if form.get('button_cancel'):
+ url = self.breadcrumbsParent.targetUrl
+ self.request.response.redirect(url)
+ return False
+ if not form.get('submit_save'):
+ return True
+ current = self.jobAssignments()['jobs']
+ newUids = form.get('assignments') or []
+ for job in self.jobs:
+ if job.uid in newUids:
+ if job not in current:
+ self.context.assignParent(baseObject(job))
+ else:
+ if job in current:
+ self.context.deassignParent(baseObject(job))
+ return True
+
+
+class QualificationsForm(PersonView):
+ """ Form for entering qualifications for a person.
+ """
+
+ macroName = 'qualificationsform'
+ pageTitle = 'label_qualifications'
+ textParentName = 'qualificationsrecorded'
+
+ def getTitle(self):
+ if not IPerson.providedBy(self.adapted):
+ dummy = self.breadcrumbsParent # evaluate before tweaking context
+ self.context = getPersonForUser(self.context, self.request)
+ self.adapted = adapted(self.context)
+ return super(QualificationsForm, self).getTitle()
+
+ def getData(self):
+ self.setupController()
+ self.registerDojoComboBox()
+ form = self.request.form
+ if form.get('button_cancel'):
+ url = self.breadcrumbsParent.targetUrl
+ self.request.response.redirect(url)
+ return []
+ data = {}
+ input = form.get('qualifications')
+ for item in (input or []):
+ data[item['key']] = item
+ if data:
+ self.update(data)
+ else:
+ qu = self.getQualifications(adapted(self.target))
+ if qu is not None:
+ data = qu.data
+ result = []
+ qualifications = self.conceptManager['qualifications']
+ for obj in qualifications.getChildren([self.defaultPredicate]):
+ uid = util.getUidForObject(obj)
+ dataRow = data.get(uid) or {}
+ item = dict(key=uid, label=obj.title,
+ desc=obj.description,
+ certVocabulary=adapted(obj).certVocabulary or [],
+ subitems=[], schema=[],
+ value=dataRow.get('value') or (3 * [u'']),
+ cert=dataRow.get('cert') or (3 * [u'']))
+ for subitem in obj.getChildren([self.defaultPredicate]):
+ item['subitems'].append(dict(
+ uid=util.getUidForObject(subitem),
+ title=subitem.title))
+ for row in adapted(obj).data.values():
+ key = row[0]
+ if len(row) < 6:
+ continue
+ value = dataRow.get('qu_' + key) or (3 * [u''])
+ item['schema'].append(dict(
+ key=key, label=row[1],
+ level=row[2], type=row[4],
+ vocabulary=row[5].split(';'),
+ value=value))
+ result.append(item)
+ return result
+
+ def update(self, data):
+ person = adapted(self.target)
+ qu = self.getQualifications(person)
+ if qu is None:
+ qu = self.createQualifications(person)
+ qu.data = data
+ return self.processStateTransition(qu)
+
+ def createQualifications(self, person):
+ concepts = self.conceptManager
+ name = 'qu.' + person.name
+ name = INameChooser(concepts).chooseName(name, None)
+ type = concepts['qualificationsrecorded']
+ obj = addObject(concepts, Concept, name, type=type,
+ title='Qualifications: ' + person.title)
+ baseObject(person).assignChild(obj)
+ return adapted(obj)
+
+
+class SkillsForm(QualificationsForm):
+ """ Form for entering skills for a person.
+ """
+
+ macroName = 'skillsform'
+ pageTitle = 'label_skills'
+ textParentName = 'skillsrecorded'
+
+ def getData(self):
+ self.setupController()
+ self.registerDojoComboBox()
+ form = self.request.form
+ if form.get('button_cancel'):
+ url = self.breadcrumbsParent.targetUrl
+ self.request.response.redirect(url)
+ return {}
+ data = {}
+ input = form.get('skills') or []
+ for key in input:
+ row = form.get('skills.' + key)
+ if row['value']:
+ if not data:
+ data = dict(value=[], exp=[], int=[])
+ data['value'].append(row['value'])
+ data['exp'].append(row['exp'])
+ data['int'].append(row['int'])
+ if data:
+ self.update(data)
+ else:
+ sk = self.getSkills(adapted(self.target))
+ if sk is not None:
+ data = sk.data
+ obj = self.conceptManager['skills']
+ item = dict(subitems=[],
+ value=data.get('value') or [],
+ exp=data.get('exp') or [],
+ int=data.get('int') or [])
+ fill(item['value'], u'', 15)
+ fill(item['exp'], u'0', 15)
+ fill(item['int'], u'0', 15)
+ for subitem in obj.getChildren([self.defaultPredicate]):
+ item['subitems'].append(dict(
+ uid=util.getUidForObject(subitem),
+ title=subitem.title))
+ return item
+
+ def update(self, data):
+ person = adapted(self.target)
+ qu = self.getSkills(person)
+ if qu is None:
+ qu = self.createSkills(person)
+ qu.data = data
+ return self.processStateTransition(qu)
+
+ def createSkills(self, person):
+ concepts = self.conceptManager
+ name = 'sk.' + person.name
+ name = INameChooser(concepts).chooseName(name, None)
+ type = concepts['skillsrecorded']
+ obj = addObject(concepts, Concept, name, type=type,
+ title='Skills: ' + person.title)
+ baseObject(person).assignChild(obj)
+ return adapted(obj)
+
+
+def fill(lst, v, length):
+ lst.extend((length - len(lst)) * [v])
diff --git a/cyberapps/knowledge/browser/person_macros.pt b/cyberapps/knowledge/browser/person_macros.pt
new file mode 100644
index 0000000..85bae9b
--- /dev/null
+++ b/cyberapps/knowledge/browser/person_macros.pt
@@ -0,0 +1,340 @@
+
+
+
+
+
+
+
+
+
+
+
+ label_person_fullname
+ label_jobs_assigned
+ label_qualifications
+ label_skills
+ label_ipskills
+ label_preferences
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Organisation/Team :
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cyberapps/knowledge/browser/qualification.py b/cyberapps/knowledge/browser/qualification.py
new file mode 100644
index 0000000..994a1ca
--- /dev/null
+++ b/cyberapps/knowledge/browser/qualification.py
@@ -0,0 +1,501 @@
+#
+# Copyright (c) 2015 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
+cyberapps.knowledge package.
+
+Base classes, job position and requirements views.
+"""
+
+from copy import deepcopy
+from zope import interface, component
+from zope.app.container.interfaces import INameChooser
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.cachedescriptors.property import Lazy
+from zope.traversing.api import getName
+
+from cybertools.browser.action import actions
+from cybertools.stateful.interfaces import IStateful
+from loops.browser.action import DialogAction
+from loops.browser.concept import ConceptView
+from loops.common import adapted, baseObject, generateNameFromTitle
+from loops.concept import Concept
+from loops.knowledge.browser import InstitutionMixin
+from loops.organize.party import getPersonForUser
+from loops.organize.personal import favorite
+from loops.organize.personal.interfaces import IFavorites
+from loops.security.common import checkPermission
+from loops.setup import addObject
+from loops import util
+from cyberapps.knowledge.interfaces import _
+
+
+template = ViewPageTemplateFile('qualification_macros.pt')
+
+
+actions.register('createJobPosition', 'portlet', DialogAction,
+ title=_(u'Create Job...'),
+ description=_(u'Create a new job / position.'),
+ viewName='create_concept.html',
+ dialogName='createPosition',
+ typeToken='.loops/concepts/jobposition',
+ fixedType=True,
+ innerForm='inner_concept_form.html',
+ permission='loops.AssignAsParent',
+)
+
+
+class QualificationBaseView(InstitutionMixin):
+
+ template = template
+ templateName = 'knowledge.qualification'
+
+ showInBreadcrumbs = True
+
+ textKeys = ['1'] # should be overridden by subclass
+ textParentName = None # to be specified by subclass
+
+ def setupController(self):
+ cm = self.controller.macros
+ cm.register('css',
+ identifier='cyberapps.knowledge.css',
+ resourceName='cyberapps.knowledge.css',
+ media='all', priority=90)
+
+ def getTexts(self):
+ result = {}
+ if not self.textParentName:
+ return result
+ textKeys = self.textKeys
+ parent = self.conceptManager[self.textParentName]
+ for idx, r in enumerate(parent.getResources()[:len(textKeys)]):
+ result[textKeys[idx]] = dict(
+ title=r.title, text=self.renderText(r.data, r.contentType))
+ return result
+
+ @Lazy
+ def jobPositionType(self):
+ return self.conceptManager['jobposition']
+
+ def registerDojoSlider(self):
+ self.registerDojo()
+ jsCall = ('dojo.require("dijit.form.HorizontalSlider");'
+ 'dojo.require("dijit.form.HorizontalRule");'
+ 'dojo.require("dijit.form.HorizontalRuleLabels");')
+ self.controller.macros.register('js-execute',
+ 'dojo.require.HorizontalSlider', jsCall=jsCall)
+
+ def registerDojoCharting(self):
+ self.registerDojo()
+ jsCall = ('dojo.require("dojox.charting.Chart");'
+ 'dojo.require("dojox.charting.plot2d.ClusteredBars");'
+ 'dojo.require("dojox.charting.themes.Claro");')
+ self.controller.macros.register('js-execute',
+ 'dojo.require.Charting', jsCall=jsCall)
+
+ def processStateTransition(self, obj):
+ stf = component.getAdapter(baseObject(obj), IStateful,
+ name='task_states')
+ state = stf.state
+ if self.request.form.get('submit_activate'):
+ if state == 'draft':
+ stf.doTransition('release')
+ url = self.breadcrumbsParent.targetUrl
+ self.request.response.redirect(url)
+ return False
+ elif state == 'active':
+ stf.doTransition('reopen')
+ return True
+
+ def createJob(self, title):
+ concepts = self.conceptManager
+ inst = self.institution
+ #name = 'jobposition.%s_%s' % (
+ # getName(inst), generateNameFromTitle(title))
+ name = 'jobposition.%s' % generateNameFromTitle(title)
+ name = INameChooser(concepts).chooseName(name, None)
+ obj = addObject(concepts, Concept, name, title=title,
+ type=self.jobPositionType)
+ obj.assignParent(inst)
+ return adapted(obj)
+
+
+class JobPositionsOverview(QualificationBaseView, ConceptView):
+
+ macroName = 'jobpositions'
+ textParentName = 'data_entry'
+
+ def update(self):
+ form = self.request.form
+ if form.get('create_jobposition'):
+ title = form.get('form_jptitle')
+ if not title:
+ # TODO: provide error message
+ return True
+ job = self.createJob(title)
+ return True
+ instUid = form.get('select_institution')
+ if instUid:
+ return self.setInstitution(instUid)
+
+ @Lazy
+ def positions(self):
+ result = []
+ self.setupController()
+ self.registerDojoComboBox()
+ inst = baseObject(self.institution)
+ if inst is not None:
+ for child in inst.getChildren([self.defaultPredicate]):
+ if child.conceptType == self.jobPositionType:
+ result.append(PositionView(child, self.request))
+ return result
+
+ @Lazy
+ def jobTitles(self):
+ table = self.conceptManager.get('job_names')
+ if table is None:
+ return []
+ return [v[0] for v in adapted(table).data.values()]
+
+
+class PositionView(QualificationBaseView, ConceptView):
+
+ parentName = None
+
+ @Lazy
+ def breadcrumbsParent(self):
+ parent = None
+ if self.parentName is not None:
+ parent = self.conceptManager.get(self.parentName)
+ if parent is None:
+ for p in self.context.conceptType.getParents([self.queryTargetPredicate]):
+ parent = p
+ return self.nodeView.getViewForTarget(parent)
+
+ @Lazy
+ def copyUrl(self):
+ return '%s/copy_jpprofiles' % (self.nodeView.getUrlForTarget(self.context))
+
+ @Lazy
+ def deleteUrl(self):
+ #for f in (self.jpDescription, self.ipskillsRequired,
+ # self.qualificationsRequired):
+ # if f['state'] != 'none':
+ # return None
+ return '%s/del_jobposition' % (self.nodeView.getUrlForTarget(self.context))
+
+ @Lazy
+ def jpDescription(self):
+ jpDesc = adapted(self.target).getJPDescription()
+ if jpDesc is None:
+ state = 'none'
+ text = 'to be done'
+ else:
+ stf = component.getAdapter(baseObject(jpDesc), IStateful,
+ name='task_states')
+ state = stf.state
+ text = stf.getStateObject().title
+ action = 'edit_jpdescription.html'
+ editUrl = '%s/%s' % (self.nodeView.getUrlForTarget(self.context), action)
+ return dict(text=text, editUrl=editUrl, state=state)
+
+ @Lazy
+ def ipskillsRequired(self):
+ ipsReq = adapted(self.target).getIPSkillsRequired()
+ if ipsReq is None:
+ state = 'none'
+ text = 'to be done'
+ else:
+ stf = component.getAdapter(baseObject(ipsReq), IStateful,
+ name='task_states')
+ state = stf.state
+ text = stf.getStateObject().title
+ action = 'edit_ipskillsreq.html'
+ editUrl = '%s/%s' % (self.nodeView.getUrlForTarget(self.context), action)
+ ipskillsUrl = None
+ return dict(text=text, editUrl=editUrl, ipskillsUrl=ipskillsUrl,
+ state=state)
+
+ @Lazy
+ def qualificationsRequired(self):
+ quReq = adapted(self.target).getQualificationsRequired()
+ if quReq is None:
+ state = 'none'
+ text = 'to be done'
+ else:
+ stf = component.getAdapter(baseObject(quReq), IStateful,
+ name='task_states')
+ state = stf.state
+ text = stf.getStateObject().title
+ action = 'edit_qualificationsreq.html'
+ editUrl = '%s/%s' % (self.nodeView.getUrlForTarget(self.context), action)
+ return dict(text=text, editUrl=editUrl, state=state)
+
+ @Lazy
+ def ipskills(self):
+ #action = 'edit_ipskills.html'
+ #editUrl = '%s/%s' % (self.nodeView.getUrlForTarget(self.context), action)
+ ipskillsUrl = None
+ return dict(text='to be done') #, editUrl=editUrl, ipskillsUrl=ipskillsUrl)
+
+
+class DeleteJobPosition(PositionView):
+
+ isToplevel = True
+ parentName = 'data_entry'
+
+ def __call__(self):
+ obj = self.adapted
+ jobdesc = obj.getJPDescription()
+ ipskills = obj.getIPSkillsRequired()
+ qualif = obj.getQualificationsRequired()
+ for subobj in (jobdesc, ipskills, qualif):
+ if subobj is not None:
+ name = getName(baseObject(subobj))
+ del self.conceptManager[name]
+ targetUrl = self.breadcrumbsParent.targetUrl
+ name = getName(self.context)
+ del self.conceptManager[name]
+ return self.request.response.redirect(targetUrl)
+
+
+class CopyJPProfiles(PositionView):
+
+ isToplevel = True
+ parentName = 'data_entry'
+
+ def __call__(self):
+ source = self.adapted
+ jobdesc = source.getJPDescription()
+ ipskills = source.getIPSkillsRequired()
+ qualif = source.getQualificationsRequired()
+ new = self.createJob(self.context.title)
+ if jobdesc is not None:
+ newJobdesc = new.createJPDescription()
+ newJobdesc.header = deepcopy(jobdesc.header)
+ newJobdesc.administrativeData = deepcopy(jobdesc.administrativeData)
+ newJobdesc.workDescription = deepcopy(jobdesc.workDescription)
+ if ipskills is not None:
+ newIpskills = new.createIPSkillsRequired()
+ newIpskills.requirements = deepcopy(ipskills.requirements)
+ if qualif is not None:
+ newQualif = new.createQualificationsRequired()
+ newQualif.requirements = deepcopy(qualif.requirements)
+ url = self.breadcrumbsParent.targetUrl
+ return self.request.response.redirect(url)
+
+
+class JPDescForm(PositionView):
+ """ Form for entering job description for a certain position.
+ """
+
+ macroName = 'jpdescform'
+ textKeys = ['administrative', 'workdesc', 'footer']
+ textParentName = 'jpdescription'
+ parentName = 'data_entry'
+
+ def getData(self):
+ self.setupController()
+ self.registerDojoComboBox()
+ result = dict(header={}, administrative=[], workdesc=[])
+ jp = adapted(self.target)
+ jpDesc = jp.getJPDescription()
+ form = self.request.form
+ if form.get('button_cancel'):
+ url = self.breadcrumbsParent.targetUrl
+ self.request.response.redirect(url)
+ return result
+ data = dict(header={}, administrative={}, workdesc={})
+ for k, v in (form.get('header') or {}).items():
+ data['header'][k] = v
+ for item in (form.get('administrative') or []):
+ data['administrative'][item['key']] = item
+ for item in (form.get('workdesc') or []):
+ data['workdesc'][item['key']] = item
+ if data['administrative']:
+ self.update(data)
+ elif jpDesc is not None:
+ if jpDesc.administrativeData:
+ data['administrative'] = jpDesc.administrativeData
+ if jpDesc.workDescription:
+ data['workdesc'] = jpDesc.workDescription
+ if jpDesc.header:
+ data['header'] = jpDesc.header
+ titleData = data['administrative'].get('title')
+ if not titleData or not titleData.get('text'):
+ data['administrative']['title'] = dict(key='title', text=jp.title)
+ result['header'] = data['header']
+ adminDT = adapted(self.conceptManager['jpdesc_administrative'])
+ for row in adminDT.data.values():
+ if len(row) < 4:
+ continue
+ key, label, desc, optional = row
+ dataRow = data['administrative'].get(key) or {}
+ result['administrative'].append(
+ dict(key=key, label=label, desc=desc, optional=bool(optional),
+ text=dataRow.get('text') or u'',
+ inactive=dataRow.get('inactive')))
+ workdescDT = adapted(self.conceptManager['jpdesc_workdesc'])
+ for row in workdescDT.data.values():
+ if len(row) < 4:
+ continue
+ key, label, desc, optional = row
+ dataRow = data['workdesc'].get(key) or {}
+ result['workdesc'].append(
+ dict(key=key, label=label, desc=desc, optional=bool(optional),
+ text=dataRow.get('text') or (5 * [u'']),
+ inactive=dataRow.get('inactive')))
+ return result
+
+ def update(self, data):
+ jp = adapted(self.target)
+ jobdesc = jp.getJPDescription()
+ if jobdesc is None:
+ jobdesc = jp.createJPDescription()
+ jobdesc.header = data['header']
+ jobdesc.administrativeData = data['administrative']
+ titleData = data['administrative'].get('title')
+ if titleData and titleData.get('text') != jp.title:
+ jp.title = titleData['text']
+ jobdesc.workDescription = data['workdesc']
+ return self.processStateTransition(jobdesc)
+
+
+class QualificationsForm(PositionView):
+ """ Form for entering qualifications required for a certain position.
+ """
+
+ macroName = 'qualificationsform'
+ textKeys = ['1', '2']
+ textParentName = 'qualificationsrequired'
+ parentName = 'data_entry'
+
+ def getData(self):
+ self.setupController()
+ self.registerDojoComboBox()
+ form = self.request.form
+ if form.get('button_cancel'):
+ url = self.breadcrumbsParent.targetUrl
+ self.request.response.redirect(url)
+ return []
+ data = {}
+ input = form.get('qualifications')
+ for item in (input or []):
+ data[item['key']] = item
+ if data:
+ self.update(data)
+ else:
+ qureq = adapted(self.target).getQualificationsRequired()
+ if qureq is not None:
+ data = qureq.requirements
+ result = []
+ qualifications = self.conceptManager['qualifications']
+ for obj in qualifications.getChildren([self.defaultPredicate]):
+ uid = util.getUidForObject(obj)
+ dataRow = data.get(uid) or {}
+ item = dict(key=uid, label=obj.title,
+ desc=obj.description,
+ subitems=[], schema=[],
+ value=dataRow.get('value') or (3 * [u'']),
+ req=dataRow.get('req') or (3 * [u'0']))
+ for subitem in obj.getChildren([self.defaultPredicate]):
+ item['subitems'].append(dict(
+ uid=util.getUidForObject(subitem),
+ title=subitem.title))
+ for row in adapted(obj).data.values():
+ if len(row) < 6:
+ continue
+ key = row[0]
+ value = dataRow.get('qu_' + key) or (3 * [u''])
+ item['schema'].append(dict(
+ key=key, label=row[1],
+ level=row[2], type=row[4],
+ vocabulary=row[5].split(';'),
+ value=value))
+ result.append(item)
+ return result
+
+ def update(self, data):
+ jp = adapted(self.target)
+ qureq = jp.getQualificationsRequired()
+ if qureq is None:
+ qureq = jp.createQualificationsRequired()
+ qureq.requirements = data
+ return self.processStateTransition(qureq)
+
+
+class IPSkillsForm(PositionView):
+ """ Form for entering interpersonal skills required for a certain position.
+ """
+
+ macroName = 'ipskillsform'
+ textKeys = ['1', '2']
+ textParentName = 'ipskillsrequired'
+ parentName = 'data_entry'
+
+ numberSelected = 0
+
+ def getData(self):
+ self.setupController()
+ form = self.request.form
+ if form.get('button_cancel'):
+ url = self.breadcrumbsParent.targetUrl
+ self.request.response.redirect(url)
+ return []
+ data = {}
+ input = form.get('ipskills')
+ for item in (input or []):
+ data[item['uid']] = item
+ self.registerDojoSlider()
+ if data:
+ self.update(data)
+ else:
+ skillsreq = adapted(self.target).getIPSkillsRequired()
+ if skillsreq is not None:
+ data = skillsreq.requirements
+ result = []
+ ipskills = self.conceptManager['ipskills']
+ for parent in ipskills.getChildren([self.defaultPredicate]):
+ #toplevelSkill = adapted(parent)
+ uid = util.getUidForObject(parent)
+ item = dict(uid=uid, label=parent.title,
+ description=parent.description, skills=[])
+ for child in parent.getChildren([self.defaultPredicate]):
+ #skill = adapted(child)
+ uid = util.getUidForObject(child)
+ row = data.get(uid) or {}
+ selected = row.get('selected')
+ if selected:
+ self.numberSelected += 1
+ item['skills'].append(
+ dict(uid=uid, label=child.title,
+ description=child.description,
+ selected=row.get('selected'),
+ expected=row.get('expected') or 0))
+ result.append(item)
+ return result
+
+ def update(self, data):
+ jp = adapted(self.target)
+ skillsreq = jp.getIPSkillsRequired()
+ if skillsreq is None:
+ skillsreq = jp.createIPSkillsRequired()
+ skillsreq.requirements = data
+ return self.processStateTransition(skillsreq)
+
diff --git a/cyberapps/knowledge/browser/qualification_macros.pt b/cyberapps/knowledge/browser/qualification_macros.pt
new file mode 100644
index 0000000..cf167da
--- /dev/null
+++ b/cyberapps/knowledge/browser/qualification_macros.pt
@@ -0,0 +1,483 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Number of currently selected skills:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cyberapps/knowledge/browser/report.py b/cyberapps/knowledge/browser/report.py
new file mode 100644
index 0000000..6c14a0c
--- /dev/null
+++ b/cyberapps/knowledge/browser/report.py
@@ -0,0 +1,312 @@
+#
+# Copyright (c) 2016 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 classes for viewing reporting data in cyberapps.knowledge.
+"""
+
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.cachedescriptors.property import Lazy
+from zope.i18n import translate
+from zope.i18nmessageid import MessageFactory
+
+from loops.common import adapted, baseObject
+from loops.knowledge.survey.interfaces import IQuestionGroup
+from loops.knowledge.survey.response import Responses
+from loops import util
+from cyberapps.knowledge.browser.qualification import \
+ JobPositionsOverview, PositionView, JPDescForm
+from cyberapps.knowledge.browser.qualification import template as baseTemplate
+from cyberapps.knowledge.interfaces import IQualificationsRecorded
+
+_ = MessageFactory('cyberapps.knowledge')
+
+
+template = ViewPageTemplateFile('report_macros.pt')
+
+
+class ReportBaseView(object):
+
+ template = template
+ templateName = 'knowledge.report'
+ baseTemplate = baseTemplate
+
+
+class JobsListing(ReportBaseView, JobPositionsOverview):
+
+ macroName = 'jobs'
+
+ def update(self):
+ instUid = self.request.form.get('select_institution')
+ if instUid:
+ return self.setInstitution(instUid)
+
+ def getItemUrl(self, item):
+ itemViewName = self.options('item_viewname')
+ baseUrl = self.nodeView.getUrlForTarget(item)
+ if itemViewName:
+ return '/'.join((baseUrl, itemViewName[0]))
+ return baseUrl
+
+
+class JobDescription(ReportBaseView, JPDescForm):
+
+ macroName = 'jobdescription'
+ parentName = None
+
+ def getData(self):
+ self.setupController()
+ self.registerDojoSlider()
+ result = dict(header={}, administrative=[], workdesc=[],
+ qualifications=[], ipskills=[])
+ data = dict(header={}, administrative={}, workdesc={},
+ qualifications={}, ipskills={})
+ # load data
+ jp = adapted(self.target)
+ jpDesc = jp.getJPDescription()
+ if jpDesc is not None:
+ if jpDesc.administrativeData:
+ data['administrative'] = jpDesc.administrativeData
+ if jpDesc.workDescription:
+ data['workdesc'] = jpDesc.workDescription
+ if jpDesc.header:
+ data['header'] = result['header'] = jpDesc.header
+ qureq = adapted(self.target).getQualificationsRequired()
+ if qureq is not None:
+ data['qualifications'] = qureq.requirements
+ skillsreq = adapted(self.target).getIPSkillsRequired()
+ if skillsreq is not None:
+ data['ipskills'] = skillsreq.requirements
+ # administrative data
+ adminDT = adapted(self.conceptManager['jpdesc_administrative'])
+ for row in adminDT.data.values():
+ key, label, desc, optional = row
+ dataRow = data['administrative'].get(key) or {}
+ if dataRow.get('inactive'):
+ continue
+ result['administrative'].append(
+ dict(key=key, label=label, desc=desc, optional=bool(optional),
+ text=dataRow.get('text') or u''))
+ # work description
+ workdescDT = adapted(self.conceptManager['jpdesc_workdesc'])
+ for row in workdescDT.data.values():
+ key, label, desc, optional = row
+ dataRow = data['workdesc'].get(key) or {}
+ if dataRow.get('inactive'):
+ continue
+ result['workdesc'].append(
+ dict(key=key, label=label, desc=desc, optional=bool(optional),
+ text=dataRow.get('text') or (5 * [u''])))
+ # qualifications
+ qualifications = self.conceptManager['qualifications']
+ for obj in qualifications.getChildren([self.defaultPredicate]):
+ uid = util.getUidForObject(obj)
+ dataRow = data['qualifications'].get(uid) or {}
+ item = dict(key=uid, label=obj.title,
+ desc=obj.description, schema=[],
+ value=dataRow.get('value') or (3 * [u'']),
+ req=dataRow.get('req') or (3 * [u'0']))
+ for row in adapted(obj).data.values():
+ if len(row) < 5:
+ continue
+ key = row[0]
+ value = dataRow.get('qu_' + key) or (3 * [u''])
+ item['schema'].append(dict(
+ key=key, label=row[1],
+ level=row[2], type=row[4],
+ value=value))
+ result['qualifications'].append(item)
+ # ipskills
+ ipskills = self.conceptManager['ipskills']
+ for parent in ipskills.getChildren([self.defaultPredicate]):
+ uid = util.getUidForObject(parent)
+ item = dict(uid=uid, label=parent.title,
+ description=parent.description, skills=[])
+ for child in parent.getChildren([self.defaultPredicate]):
+ uid = util.getUidForObject(child)
+ row = data['ipskills'].get(uid) or {}
+ if row.get('selected'):
+ item['skills'].append(
+ dict(uid=uid, label=child.title,
+ description=child.description,
+ expected=row.get('expected') or 0))
+ result['ipskills'].append(item)
+ return result
+
+
+class JobReport(ReportBaseView, PositionView):
+
+ macroName = 'job_report'
+ parentName = 'qkb'
+
+ qualificationData = None
+ questionnaires = None
+ quGroups = None
+ ipskillsInputData = None
+
+ def getData(self):
+ self.setupController()
+ self.registerDojoCharting()
+ lang = self.languageInfo.language
+ result = dict(qualifications=[], ipskills=[])
+ reqData = dict(qualifications={}, ipskills={})
+ persons = self.adapted.getPersons()
+ selectedPerson = self.request.form.get('person')
+ if selectedPerson:
+ p = adapted(util.getObjectForUid(selectedPerson))
+ if p in persons:
+ persons = [p]
+ # load requirement data
+ qureq = adapted(self.target).getQualificationsRequired()
+ if qureq is not None:
+ reqData['qualifications'] = qureq.requirements
+ skillsreq = adapted(self.target).getIPSkillsRequired()
+ if skillsreq is not None:
+ reqData['ipskills'] = skillsreq.requirements
+ # qualification data
+ qualifications = self.conceptManager['qualifications']
+ for obj in qualifications.getChildren([self.defaultPredicate]):
+ qualification = adapted(obj)
+ uid = qualification.uid
+ dataRow = reqData['qualifications'].get(uid) or {}
+ personData = self.getQualificationData(uid, persons)
+ item = dict(key=uid, label=qualification.title,
+ desc=qualification.description, schema=[],
+ value=dataRow.get('value') or (3 * [u'']),
+ req=dataRow.get('req') or (3 * [u'0']),
+ personData=personData)
+ for row in qualification.data.values():
+ if len(row) < 5:
+ continue
+ key = row[0]
+ value = dataRow.get('qu_' + key) or (3 * [u''])
+ item['schema'].append(dict(
+ key=key, label=row[1],
+ level=row[2], type=row[4],
+ value=value))
+ result['qualifications'].append(item)
+ # ipskills data
+ ipskills = self.conceptManager['ipskills']
+ for parent in ipskills.getChildren([self.defaultPredicate]):
+ uid = util.getUidForObject(parent)
+ item = dict(uid=uid, label=parent.title,
+ description=parent.description, skills=[])
+ for child in parent.getChildren([self.defaultPredicate]):
+ uid = util.getUidForObject(child)
+ row = reqData['ipskills'].get(uid) or {}
+ if row.get('selected'):
+ ipskillsInput = self.getIPSkillsInput(child, persons)
+ v = int(row.get('expected') or 0) + 1
+ vstr = '%s: %s' % (
+ translate(_('ipskills_required'), target_language=lang), v)
+ item['skills'].append(
+ dict(uid=uid, label=child.title,
+ description=child.description,
+ expected=v,
+ expStr=vstr,
+ ipskillsInput=ipskillsInput))
+ result['ipskills'].append(item)
+ return result
+
+ def getQualificationData(self, quUid, persons):
+ result = []
+ personUids = [p.uid for p in persons]
+ if self.qualificationData is None:
+ self.qualificationData = {}
+ for p in persons:
+ for c in baseObject(p).getChildren():
+ obj = adapted(c)
+ if IQualificationsRecorded.providedBy(obj):
+ self.qualificationData[p.uid] = obj.data
+ break
+ else:
+ self.qualificationData[p.uid] = {}
+ for p in persons:
+ data = self.qualificationData[p.uid].get(quUid) or {}
+ if data:
+ item = dict(name=p.title)
+ item.update(data)
+ result.append(item)
+ return result
+
+ def getIPSkillsInput(self, competence, persons):
+ result = []
+ lang = self.languageInfo.language
+ personUids = [p.uid for p in persons]
+ questionGroup = refQuestionGroup = None
+ for c in baseObject(competence).getChildren():
+ qug = adapted(c)
+ if IQuestionGroup.providedBy(qug):
+ questionnaire = self.getQuestionnaire(qug, 'standard')
+ if (questionnaire is not None and
+ qug in self.quGroups.get('standard')):
+ questionGroup = qug
+ else:
+ refQuestionnaire = self.getQuestionnaire(qug, 'person')
+ if (refQuestionnaire is not None and
+ qug in self.quGroups.get('person')):
+ refQuestionGroup = qug
+ #break
+ if questionGroup is None and refQuestionGroup is None:
+ return result
+ if self.ipskillsInputData is None:
+ self.ipskillsInputData = {}
+ for uid in personUids:
+ respManager = Responses(baseObject(questionnaire))
+ self.ipskillsInputData[uid] = respManager.load(uid)
+ if refQuestionGroup is not None:
+ refRespManager = Responses(baseObject(refQuestionnaire))
+ self.ipskillsInputData[uid].update(
+ refRespManager.loadRange(uid + '.*'))
+ for idx, uid in enumerate(personUids):
+ person = persons[idx]
+ data = self.ipskillsInputData.get(uid)
+ if data is not None:
+ value = data.get(questionGroup.uid)
+ refValues = refQuestionGroup and data.get(refQuestionGroup.uid)
+ if value is None and refValues is None:
+ continue
+ item = dict(name=person.title, value=None, avg=None, vstr=None,
+ refValues=dict(values=[], avg=None, vstr=None))
+ if value is not None:
+ v = int(round(value * 4 + 1))
+ item['value'] = v
+ item['vstr'] = '%s: %s' % (
+ translate(_('label_skillValues'), target_language=lang), v)
+ if refValues:
+ refValues = sorted([int(round(v * 4 + 1)) for v in refValues])
+ avg = int(round(sum(refValues) / len(refValues)))
+ vstr = '%s: %s' % (
+ translate(_('label_refValues'), target_language=lang),
+ ', '.join(str(v) for v in refValues))
+ item['refValues']=dict(values=refValues, avg=avg, vstr=vstr)
+ result.append(item)
+ return result
+
+ def getQuestionnaire(self, quGroup, quType):
+ if self.questionnaires is None:
+ self.questionnaires = {}
+ self.quGroups = {}
+ if quType in self.questionnaires:
+ return self.questionnaires[quType]
+ for qu in quGroup.getQuestionnaires():
+ if qu.questionnaireType == quType:
+ self.questionnaires[quType] = qu
+ self.quGroups[quType] = qu.getAllQuestionGroups()
+ break
+ return self.questionnaires.get(quType)
diff --git a/cyberapps/knowledge/browser/report_macros.pt b/cyberapps/knowledge/browser/report_macros.pt
new file mode 100644
index 0000000..db550e7
--- /dev/null
+++ b/cyberapps/knowledge/browser/report_macros.pt
@@ -0,0 +1,350 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Created at :
+
+
+
+ Responsible :
+
+
+
+ Next Review :
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ :
+
+
+
+
+ 1.
+
+
+
+
+
+
+
+
+ 3.
+
+
+ label_category
+ label_knowledge_level
+ label_requirement
+
+
+
+
+
+
+
+
+
+
+
+ label_requirement
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 4.
+
+
+
+
+
+ weniger wichtig
+
+ äußerst wichtig
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Restrict report to person :
+
+ All persons
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Required
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ blubb
+
+
+
+
+ Certificate :
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Required
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cyberapps/knowledge/configure.zcml b/cyberapps/knowledge/configure.zcml
new file mode 100644
index 0000000..697f44b
--- /dev/null
+++ b/cyberapps/knowledge/configure.zcml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cyberapps/knowledge/data.py b/cyberapps/knowledge/data.py
new file mode 100644
index 0000000..26b8555
--- /dev/null
+++ b/cyberapps/knowledge/data.py
@@ -0,0 +1,174 @@
+# cyberapps.knowlege.data
+
+""" Classes for Knowledge and Skills Management.
+"""
+
+from zope.container.interfaces import INameChooser
+from zope.component import adapts
+from zope.interface import implementer
+from zope.traversing.api import getName
+
+from cyberapps.knowledge.interfaces import IJobPosition, IQualification
+from cyberapps.knowledge.interfaces import IJPDescription, IIPSkillsRequired
+from cyberapps.knowledge.interfaces import IQualificationsRequired
+from cyberapps.knowledge.interfaces import IQualificationsRecorded
+from cyberapps.knowledge.interfaces import ISkillsRecorded
+from cyberapps.knowledge.interfaces import IIPSkillsQuestionnaire
+from cybertools.organize.interfaces import IPerson
+from loops.common import AdapterBase, adapted, baseObject
+from loops.concept import Concept
+from loops.interfaces import IConcept
+from loops.knowledge.survey.base import Questionnaire
+from loops.organize.party import getPersonForUser
+from loops.setup import addObject
+from loops.table import DataTable
+from loops.type import TypeInterfaceSourceList
+from loops import util
+
+
+TypeInterfaceSourceList.typeInterfaces += (
+ IJobPosition, IJPDescription, IIPSkillsRequired,
+ IQualificationsRequired, IQualification,
+ IQualificationsRecorded, ISkillsRecorded,
+ IIPSkillsQuestionnaire)
+
+
+@implementer(IJobPosition)
+class JobPosition(AdapterBase):
+
+ def getPersons(self):
+ result = [adapted(c) for c in self.context.getChildren()]
+ return [p for p in result if IPerson.providedBy(p)]
+
+ def getJPDescription(self):
+ for c in self.context.getChildren():
+ obj = adapted(c)
+ if IJPDescription.providedBy(obj):
+ return obj
+
+ def createJPDescription(self):
+ concepts = self.getLoopsRoot().getConceptManager()
+ name = 'jpdesc.' + self.name
+ name = INameChooser(concepts).chooseName(name, None)
+ type = concepts['jpdescription']
+ obj = addObject(concepts, Concept, name, type=type,
+ title='JP Description: ' + self.title)
+ self.context.assignChild(obj)
+ return adapted(obj)
+
+
+ def getIPSkillsRequired(self):
+ for c in self.context.getChildren():
+ obj = adapted(c)
+ if IIPSkillsRequired.providedBy(obj):
+ return obj
+
+ def createIPSkillsRequired(self):
+ concepts = self.getLoopsRoot().getConceptManager()
+ name = 'ipsreq.' + self.name
+ name = INameChooser(concepts).chooseName(name, None)
+ type = concepts['ipskillsrequired']
+ obj = addObject(concepts, Concept, name, type=type,
+ title='IP Skills Req: ' + self.title)
+ self.context.assignChild(obj)
+ return adapted(obj)
+
+ def getQualificationsRequired(self):
+ for c in self.context.getChildren():
+ obj = adapted(c)
+ if IQualificationsRequired.providedBy(obj):
+ return obj
+
+ def createQualificationsRequired(self):
+ concepts = self.getLoopsRoot().getConceptManager()
+ name = 'qureq.' + self.name
+ name = INameChooser(concepts).chooseName(name, None)
+ type = concepts['qualificationsrequired']
+ obj = addObject(concepts, Concept, name, type=type,
+ title='Qualifications Req: ' + self.title)
+ self.context.assignChild(obj)
+ return adapted(obj)
+
+
+@implementer(IJPDescription)
+class JPDescription(AdapterBase):
+
+ _contextAttributes = AdapterBase._contextAttributes + list(IJPDescription)
+
+
+@implementer(IIPSkillsRequired)
+class IPSkillsRequired(AdapterBase):
+
+ _contextAttributes = AdapterBase._contextAttributes + list(IIPSkillsRequired)
+
+
+@implementer(IQualificationsRequired)
+class QualificationsRequired(AdapterBase):
+
+ _contextAttributes = (AdapterBase._contextAttributes +
+ list(IQualificationsRequired))
+
+
+@implementer(IQualification)
+class Qualification(DataTable):
+
+ _contextAttributes = AdapterBase._contextAttributes + list(IQualification)
+
+
+@implementer(IQualificationsRecorded)
+class QualificationsRecorded(AdapterBase):
+
+ _contextAttributes = (AdapterBase._contextAttributes +
+ list(IQualificationsRecorded))
+
+
+@implementer(ISkillsRecorded)
+class SkillsRecorded(AdapterBase):
+
+ _contextAttributes = (AdapterBase._contextAttributes +
+ list(ISkillsRecorded))
+
+
+@implementer(IIPSkillsQuestionnaire)
+class IPSkillsQuestionnaire(Questionnaire):
+
+ def getQuestionGroups(self, personId=None):
+ if personId is None:
+ person = getPersonForUser(self.context)
+ else:
+ person = util.getObjectForUid(personId)
+ result = []
+ required = self.getRequiredIPSkills(person)
+ groups = super(IPSkillsQuestionnaire, self).getQuestionGroups()
+ if not required:
+ return groups
+ for group in groups:
+ skills = self.getIPSkillsForGroup(group)
+ if skills:
+ for skill in skills:
+ if skill in required:
+ result.append(group)
+ break
+ else:
+ result.append(group)
+ return result
+
+ def getIPSkillsForGroup(self, group):
+ result = []
+ for p in baseObject(group).getParents():
+ if getName(p.conceptType) == 'ipskill':
+ result.append(adapted(p))
+ return result
+
+ def getRequiredIPSkills(self, person):
+ result = []
+ for p in person.getParents():
+ job = adapted(p)
+ if IJobPosition.providedBy(job):
+ ipskills = job.getIPSkillsRequired()
+ if ipskills is not None:
+ requirements = ipskills.requirements
+ for item in requirements.values():
+ if item.get('selected'):
+ result.append(util.getObjectForUid(item['uid']))
+ return result
diff --git a/cyberapps/knowledge/interfaces.py b/cyberapps/knowledge/interfaces.py
new file mode 100644
index 0000000..0761fd2
--- /dev/null
+++ b/cyberapps/knowledge/interfaces.py
@@ -0,0 +1,83 @@
+#
+# Copyright (c) 2015 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 and skills management specials.
+"""
+
+from zope.i18nmessageid import MessageFactory
+from zope.interface import Interface, Attribute
+from zope import schema
+
+from cybertools.composer.schema.grid.interfaces import KeyTable
+from loops.interfaces import ILoopsAdapter
+from loops.knowledge.survey.interfaces import IQuestionnaire
+from loops.table import IDataTable
+from loops.util import _
+
+_ = MessageFactory('cyberapps.knowledge')
+
+
+class IJobPosition(ILoopsAdapter):
+
+ pass
+
+
+class IJPDescription(ILoopsAdapter):
+
+ header = Attribute('Header data.')
+ administrativeData = Attribute('Administrative job data.')
+ workDescription = Attribute('Work description.')
+
+
+class IIPSkillsRequired(ILoopsAdapter):
+
+ requirements = Attribute('Required interpersonal skills.')
+
+
+class IQualificationsRequired(ILoopsAdapter):
+
+ requirements = Attribute('Required qualifications.')
+
+
+class IQualification(IDataTable):
+
+ data = KeyTable(title=_(u'Qualification Schema'),
+ description=_(u'Data fields for specifying the qualification.'),
+ required=False)
+
+ certVocabulary = schema.List(title=_(u'Certification Vocabulary'),
+ description=_(u'List of proposed certificates.'),
+ required=False)
+
+
+class IQualificationsRecorded(ILoopsAdapter):
+
+ data = Attribute('Qualifications recorded for person.')
+
+
+class ISkillsRecorded(ILoopsAdapter):
+
+ data = Attribute('Skills recorded for person.')
+
+
+
+class IIPSkillsQuestionnaire(IQuestionnaire):
+ """ Allow specialized questionnaire implementation that limits
+ question groups according to competences required for person.
+ """
diff --git a/cyberapps/knowledge/knowledge_de.dmp b/cyberapps/knowledge/knowledge_de.dmp
new file mode 100644
index 0000000..ee13fe9
--- /dev/null
+++ b/cyberapps/knowledge/knowledge_de.dmp
@@ -0,0 +1,56 @@
+# this depends on import of loops/knowledge/data/knowledge_de.dmp
+
+type(u'datatable', u'Datentabelle', viewName=u'',
+ typeInterface=u'loops.table.IDataTable',
+ options=u'action.portlet:edit_concept')
+type(u'ipskill', u'Kompetenz', viewName=u'',
+ options=u'action.portlet:edit_concept')
+type(u'ipskillsrequired', u'Soll-Profil Kompetenzen', viewName=u'',
+ typeInterface=u'cyberapps.knowledge.interfaces.IIPSkillsRequired',
+ options=u'action.portlet:edit_concept\n'
+ u'organize.stateful:task_states\nportlet_states:task_states')
+type(u'qualificationsrequired', u'Soll-Profil Qualifikationen', viewName=u'',
+ typeInterface=u'cyberapps.knowledge.interfaces.IQualificationsRequired',
+ options=u'action.portlet:edit_concept\n'
+ u'organize.stateful:task_states\nportlet_states:task_states')
+type(u'jobposition', u'Stelle', viewName=u'',
+ typeInterface=u'cyberapps.knowledge.interfaces.IJobPosition',
+ options=u'action.portlet:edit_concept')
+type(u'jpdescription', u'Stellenbeschreibung', viewName=u'',
+ typeInterface=u'cyberapps.knowledge.interfaces.IJPDescription',
+ options=u'action.portlet:edit_concept\n'
+ u'organize.stateful:task_states\nportlet_states:task_states')
+type(u'qualification', u'Qualifikation', viewName=u'',
+ options=u'action.portlet:edit_concept')
+type(u'qualificationsrecorded', u'Ist-Profil Qualifikationen', viewName=u'',
+ typeInterface=u'cyberapps.knowledge.interfaces.IQualificationsRecorded',
+ options=u'action.portlet:edit_concept\n'
+ u'organize.stateful:task_states\nportlet_states:task_states')
+type(u'skillsrecorded', u'Ist-Profil F\xe4higkeiten', viewName=u'',
+ typeInterface=u'cyberapps.knowledge.interfaces.ISkillsRecorded',
+ options=u'action.portlet:edit_concept\n'
+ u'organize.stateful:task_states\nportlet_states:task_states')
+
+# data tables
+#concept(u'jpdesc_administrative', u'Stellenbeschreibung administrativ', u'datatable',
+# columns=[u'number', u'key', u'label', u'description', u'optional'])
+#concept(u'jpdesc_workdesc', u'Stellenbeschreibung Tätigkeit',
+# u'datatable',
+# columns=[u'number', u'key', u'label', u'description', u'optional'])
+#concept(u'jpdesc_qualifications', u'Qualifikationen',
+# u'datatable',
+# columns=[u'number', u'key', u'label', u'description', u'optional'])
+#concept(u'job_names', u'Stellenbezeichnungen', u'datatable')
+
+# structure
+child(u'general', u'jpdescription', u'standard')
+child(u'general', u'ipskill', u'standard')
+child(u'general', u'ipskillsrequired', u'standard')
+child(u'general', u'qualificationsrequired', u'standard')
+child(u'general', u'jobposition', u'standard')
+child(u'general', u'qualificationsrecorded', u'standard')
+child(u'general', u'skillsrecorded', u'standard')
+
+# records
+#records(u'qualification',
+# u'cyberapps.knowledge.qualification.data.QualificationRecord')
diff --git a/cyberapps/knowledge/locales/cyberapps.knowledge.pot b/cyberapps/knowledge/locales/cyberapps.knowledge.pot
new file mode 100644
index 0000000..15d114c
--- /dev/null
+++ b/cyberapps/knowledge/locales/cyberapps.knowledge.pot
@@ -0,0 +1,24 @@
+msgid ""
+msgstr ""
+
+"Project-Id-Version: 0.0.1\n"
+"POT-Creation-Date: 2014-08-08 12:00 CET\n"
+"PO-Revision-Date: 2014-08-08 12:00 CET\n"
+"Last-Translator: Helmut Merz \n"
+"Language-Team: cyberapps.knowledge developers \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: kwrite\n"
+
+msgid "Jobs / Positions"
+msgstr ""
+
+msgid "Create Job..."
+msgstr ""
+
+msgid "Create a new job / position"
+msgstr ""
+
+msgid "label_jobposition_title"
+msgstr ""
diff --git a/cyberapps/knowledge/locales/de/LC_MESSAGES/cyberapps.knowledge.mo b/cyberapps/knowledge/locales/de/LC_MESSAGES/cyberapps.knowledge.mo
new file mode 100644
index 0000000..718b1b7
Binary files /dev/null and b/cyberapps/knowledge/locales/de/LC_MESSAGES/cyberapps.knowledge.mo differ
diff --git a/cyberapps/knowledge/locales/de/LC_MESSAGES/cyberapps.knowledge.po b/cyberapps/knowledge/locales/de/LC_MESSAGES/cyberapps.knowledge.po
new file mode 100644
index 0000000..8f00705
--- /dev/null
+++ b/cyberapps/knowledge/locales/de/LC_MESSAGES/cyberapps.knowledge.po
@@ -0,0 +1,241 @@
+msgid ""
+msgstr ""
+
+"Project-Id-Version: 0.0.1\n"
+"POT-Creation-Date: 2014-08-08 12:00 CET\n"
+"PO-Revision-Date: 2016-10-10 12:00 CET\n"
+"Last-Translator: Helmut Merz \n"
+"Language-Team: cyberapps.knowledge developers \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: kwrite\n"
+
+msgid "Jobs / Positions"
+msgstr "Stellen"
+
+msgid "Create Job"
+msgstr "Stelle anlegen"
+
+msgid "Create a New Job / Position"
+msgstr "Eine neue Stelle anlegen"
+
+msgid "Back"
+msgstr "Zurück"
+
+msgid "Back to Resource Management Page"
+msgstr "Zurück zur Ressourcenmanagement-Übersichtsseite"
+
+# job positions
+
+msgid "label_jobposition_title"
+msgstr "Stellenbezeichnung"
+
+msgid "label_jpdescription"
+msgstr "Stellenbeschreibung"
+
+msgid "Created at"
+msgstr "Erstellt am"
+
+msgid "Responsible"
+msgstr "Verantwortlich"
+
+msgid "Next Review"
+msgstr "Nächste Überprüfung"
+
+msgid "option_review_0"
+msgstr "in 6 Monaten"
+
+msgid "option_review_1"
+msgstr "in 9 Monaten"
+
+msgid "option_review_2"
+msgstr "in 12 Monaten"
+
+msgid "option_review_3"
+msgstr "in 18 Monaten"
+
+msgid "option_review_4"
+msgstr "in 24 Monaten"
+
+# requirements
+
+msgid "label_qualifications_required"
+msgstr "Sollprofil Qualifikationen"
+
+msgid "label_ipskills_required"
+msgstr "Sollprofil Kompetenzen"
+
+msgid "label_persondata"
+msgstr "Ist-Profile"
+
+msgid "label_ipskills_number_selected"
+msgstr "Anzahl der ausgewählten Kompetenzen:"
+
+# persons
+
+msgid "label_managers"
+msgstr "Leitung"
+
+msgid "label_employees"
+msgstr "Mitarbeiter/innen"
+
+msgid "label_others"
+msgstr "Weitere Personen"
+
+msgid "label_person_fullname"
+msgstr "Name"
+
+msgid "label_jobs_assigned"
+msgstr "Stelle(n)"
+
+msgid "label_qualifications"
+msgstr "Qualifikationen"
+
+msgid "label_skills"
+msgstr "Fähigkeiten"
+
+msgid "label_ipskills"
+msgstr "Kompetenzen"
+
+msgid "label_professional_experience"
+msgstr "Berufserfahrung"
+
+msgid "label_certificate"
+msgstr "Nachweis"
+
+msgid "label_experience"
+msgstr "Erfahrung"
+
+msgid "label_interest"
+msgstr "Interesse"
+
+msgid "label_preferences"
+msgstr "Präferenzen"
+
+msgid "option_cert_0"
+msgstr "Zertifikat"
+
+msgid "option_cert_1"
+msgstr "Zeugnis"
+
+msgid "option_cert_2"
+msgstr "Teilnahmebescheinigung"
+
+msgid "option_cert_3"
+msgstr "Immatrikulationsbescheinigung"
+
+msgid "option_cert_4"
+msgstr "Ausbildungsvertrag"
+
+msgid "option_exp_0"
+msgstr "1 - Damit habe ich ein wenig Erfahrung"
+
+msgid "option_exp_1"
+msgstr "2"
+
+msgid "option_exp_2"
+msgstr "3"
+
+msgid "option_exp_3"
+msgstr "4"
+
+msgid "option_exp_4"
+msgstr "5 - Damit habe ich sehr viel Erfahrung"
+
+msgid "option_int_0"
+msgstr "1 - Interessiert mich ein wenig"
+
+msgid "option_int_1"
+msgstr "2"
+
+msgid "option_int_2"
+msgstr "3"
+
+msgid "option_int_3"
+msgstr "4"
+
+msgid "option_int_4"
+msgstr "5 - Interessiert mich sehr"
+
+# forms / fields
+
+msgid "label_category"
+msgstr "Kategorie"
+
+msgid "label_knowledge_level"
+msgstr "Ausprägungsgrad"
+
+msgid "label_requirement"
+msgstr "Erforderlich/Wünschenswert"
+
+msgid "option_req_0"
+msgstr "-"
+
+msgid "option_req_1"
+msgstr "wünschenswert"
+
+msgid "option_req_2"
+msgstr "erforderlich"
+
+# state / buttons
+
+msgid "to be done"
+msgstr "Ausstehend"
+
+msgid "draft"
+msgstr "In Arbeit"
+
+msgid "active"
+msgstr "Fertig"
+
+msgid "edit job description"
+msgstr "Stellenbeschreibung bearbeiten"
+
+msgid "edit profile"
+msgstr "Profil bearbeiten"
+
+msgid "button_save"
+msgstr "Zwischenspeichern"
+
+msgid "button_activate"
+msgstr "Freigeben"
+
+msgid "Save"
+msgstr "Speichern"
+
+msgid "Cancel"
+msgstr "Abbrechen"
+
+msgid "delete job position"
+msgstr "Stelle löschen"
+
+msgid "onclick_delete_jobposition"
+msgstr "return confirm('Wollen Sie die Stelle mit allen zugehörigen Daten wirklich löschen?')"
+
+msgid "copy job position"
+msgstr "Stelle kopieren"
+
+msgid "onclick_copy_jobposition"
+msgstr "return confirm('Wollen Sie die Stelle mit allen zugehörigen Daten kopieren?')"
+
+# reports
+
+msgid "select_person"
+msgstr "Person für Einzelauswertung auswählen"
+
+msgid "all_persons"
+msgstr "Auswertung für alle anzeigen"
+
+msgid "qualifications_required"
+msgstr "Anforderungen"
+
+msgid "ipskills_required"
+msgstr "Soll-Wert"
+
+msgid "label_skillValues"
+msgstr "Selbsteinschätzung"
+
+msgid "label_refValues"
+msgstr "Fremdeinschätzung"
+
diff --git a/cyberapps/knowledge/locales/en/LC_MESSAGES/cyberapps.knowledge.mo b/cyberapps/knowledge/locales/en/LC_MESSAGES/cyberapps.knowledge.mo
new file mode 100644
index 0000000..1627fe2
Binary files /dev/null and b/cyberapps/knowledge/locales/en/LC_MESSAGES/cyberapps.knowledge.mo differ
diff --git a/cyberapps/knowledge/locales/en/LC_MESSAGES/cyberapps.knowledge.po b/cyberapps/knowledge/locales/en/LC_MESSAGES/cyberapps.knowledge.po
new file mode 100644
index 0000000..5724fd5
--- /dev/null
+++ b/cyberapps/knowledge/locales/en/LC_MESSAGES/cyberapps.knowledge.po
@@ -0,0 +1,15 @@
+msgid ""
+msgstr ""
+
+"Project-Id-Version: 0.0.1\n"
+"POT-Creation-Date: 2014-08-08 12:00 CET\n"
+"PO-Revision-Date: 2014-08-08 12:00 CET\n"
+"Last-Translator: Helmut Merz \n"
+"Language-Team: cyberapps.knowledge developers \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: kwrite\n"
+
+msgid "label_jobposition_title"
+msgstr "Position"
diff --git a/cyberapps/knowledge/tests.py b/cyberapps/knowledge/tests.py
new file mode 100755
index 0000000..9379215
--- /dev/null
+++ b/cyberapps/knowledge/tests.py
@@ -0,0 +1,32 @@
+# cyberapps.knowledge.tests
+
+import os
+import unittest, doctest
+from zope.interface.verify import verifyClass
+
+from loops.setup import importData as baseImportData
+
+
+importPath = os.path.dirname(__file__)
+
+
+def importData(loopsRoot):
+ baseImportData(loopsRoot, importPath, 'knowledge_de.dmp')
+
+
+class Test(unittest.TestCase):
+ "Basic tests for the cyberapps.knowledge package."
+
+ def testSomething(self):
+ pass
+
+
+def test_suite():
+ flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
+ return unittest.TestSuite((
+ unittest.makeSuite(Test),
+ doctest.DocFileSuite('README.txt', optionflags=flags),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
diff --git a/pyproject.toml b/pyproject.toml
index 84700a4..a44a4d7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -18,9 +18,9 @@ dependencies = [
[project.optional-dependencies]
-jwt = ["python_jwt", "jwcrypto"]
+jwt = ["python-jwt", "jwcrypto"]
test = ["zope.testrunner"]
[tool.setuptools]
-packages = ["cco"]
+packages = ["cco", "cyberapps"]