501 lines
18 KiB
Python
501 lines
18 KiB
Python
#
|
|
# 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)
|
|
|