Merge branch 'master' of ssh://git.cy55.de/home/git/cybertools
This commit is contained in:
commit
8824044726
23 changed files with 250 additions and 74 deletions
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2014 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
|
||||
|
@ -37,9 +37,9 @@ class Controller(BaseController):
|
|||
macros = self.macros
|
||||
presentationMode = self.request.get('liquid.viewmode') == 'presentation'
|
||||
params = [('blue/blue8.css', 'all', 20, False),
|
||||
('print.css', 'print', 25, False),
|
||||
('blue/ie.css', 'all', 30, True),
|
||||
('custom.css', 'all', 100, False)]
|
||||
('custom.css', 'all', 100, False),
|
||||
('print.css', 'print', 200, False),]
|
||||
#if presentationMode:
|
||||
# params.append(('presentation.css', 'all', 30, False))
|
||||
for id, media, prio, ie in params:
|
||||
|
|
BIN
browser/icons/page_copy.png
Normal file
BIN
browser/icons/page_copy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 663 B |
BIN
browser/icons/page_delete.png
Normal file
BIN
browser/icons/page_delete.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 740 B |
|
@ -1,5 +1,5 @@
|
|||
<metal:page define-macro="page"
|
||||
tal:condition="view/update"><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
tal:condition="view/update"><!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
|
||||
tal:define="body view/body;
|
||||
layout view/context/template">
|
||||
|
|
|
@ -83,6 +83,7 @@ class Report(Template):
|
|||
queryCriteria = None
|
||||
outputFields = ()
|
||||
sortCriteria = ()
|
||||
sortDescending = False
|
||||
limits = None
|
||||
|
||||
|
||||
|
|
|
@ -81,12 +81,14 @@ class GroupHeaderRow(BaseRow):
|
|||
class ResultSet(object):
|
||||
|
||||
def __init__(self, context, data, rowFactory=Row,
|
||||
sortCriteria=None, queryCriteria=BaseQueryCriteria(),
|
||||
sortCriteria=None, sortDescending=False,
|
||||
queryCriteria=BaseQueryCriteria(),
|
||||
limits=None):
|
||||
self.context = context # the report or report instance
|
||||
self.data = data
|
||||
self.rowFactory = rowFactory
|
||||
self.sortCriteria = sortCriteria
|
||||
self.sortDescending = sortDescending
|
||||
self.queryCriteria = queryCriteria
|
||||
self.limits = limits
|
||||
self.totals = BaseRow(None, self)
|
||||
|
@ -95,7 +97,9 @@ class ResultSet(object):
|
|||
result = [self.rowFactory(item, self) for item in self.data]
|
||||
result = [row for row in result if self.queryCriteria.check(row)]
|
||||
if self.sortCriteria:
|
||||
result.sort(key=lambda x: [f.getSortValue(x) for f in self.sortCriteria])
|
||||
result.sort(key=lambda x:
|
||||
[f.getSortValue(x) for f in self.sortCriteria],
|
||||
reverse=self.sortDescending)
|
||||
if self.limits:
|
||||
start, stop = self.limits
|
||||
result = result[start:stop]
|
||||
|
|
|
@ -90,7 +90,7 @@
|
|||
tal:define="width field/width|nothing"
|
||||
tal:attributes="name name; id name;
|
||||
style python:
|
||||
('width: %s;;' % (width and str(width)+'px' or '570px')) +
|
||||
('width: %s;;' % (width and str(width)+'px' or '555px')) +
|
||||
'height: 1.5em;;';
|
||||
value data/?name|string:;
|
||||
xxrequired field/required_js;" />
|
||||
|
@ -144,7 +144,11 @@
|
|||
|
||||
<metal:upload define-macro="input_fileupload">
|
||||
<input type="file" name="field"
|
||||
tal:attributes="name name;" />
|
||||
tal:attributes="name name;"
|
||||
onchange="if (this.form.title.value == '') {
|
||||
var value = this.value.split('\\');
|
||||
this.form.title.value = value[value.length-1];
|
||||
}" />
|
||||
</metal:upload>
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2014 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
|
||||
|
@ -41,6 +41,14 @@ from cybertools.composer.schema.schema import formErrors
|
|||
from cybertools.util.format import toStr, toUnicode
|
||||
|
||||
|
||||
class FieldGroup(object):
|
||||
|
||||
def __init__(self, name, label, sublabels=[]):
|
||||
self.name = name
|
||||
self.label = label
|
||||
self.sublabels = sublabels
|
||||
|
||||
|
||||
class Field(Component):
|
||||
|
||||
implements(IField)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2014 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
|
||||
|
@ -42,7 +42,33 @@ class GridFieldInstance(ListFieldInstance):
|
|||
|
||||
@Lazy
|
||||
def columnTypes(self):
|
||||
return [createField(t) for t in self.context.column_types]
|
||||
fields = [createField(t) for t in self.context.column_types]
|
||||
for f in fields:
|
||||
f.linkedFields = [createField(sf)
|
||||
for sf in getattr(f.baseField, 'linkedFields', [])]
|
||||
return fields
|
||||
|
||||
#@Lazy
|
||||
def columnTypesForLayout(self):
|
||||
result = []
|
||||
groups = {}
|
||||
for idx, f in enumerate(self.columnTypes):
|
||||
group = getattr(f.baseField, 'group', None)
|
||||
if group is None:
|
||||
result.append(dict(name=f.name,
|
||||
label=(f.description or f.title),
|
||||
fields=[f], indexes=[idx], group=None))
|
||||
else:
|
||||
g = groups.get(group.name)
|
||||
if g is None:
|
||||
g = dict(name=group.name, label=group.label,
|
||||
fields=[f], indexes=[idx], group=group)
|
||||
groups[group.name] = g
|
||||
result.append(g)
|
||||
else:
|
||||
g['fields'].append(f)
|
||||
g['indexes'].append(idx)
|
||||
return result
|
||||
|
||||
@Lazy
|
||||
def columnFieldInstances(self):
|
||||
|
@ -122,6 +148,8 @@ class GridFieldInstance(ListFieldInstance):
|
|||
def unmarshallRow(self, row, idx=None):
|
||||
item = {}
|
||||
cardinality = getattr(self.context, 'cardinality', None)
|
||||
ignoreInCheckOnEmpty = list(
|
||||
getattr(self.context, 'ignoreInCheckOnEmpty', []))
|
||||
for fi in self.columnFieldInstances:
|
||||
if idx is not None:
|
||||
fi.index = idx
|
||||
|
@ -133,10 +161,9 @@ class GridFieldInstance(ListFieldInstance):
|
|||
else:
|
||||
if fi.default is not None:
|
||||
if value == fi.default:
|
||||
continue
|
||||
ignoreInCheckOnEmpty.append(fi.name)
|
||||
if value:
|
||||
item[fi.name] = value
|
||||
ignoreInCheckOnEmpty = getattr(self.context, 'ignoreInCheckOnEmpty', [])
|
||||
for k, v in item.items():
|
||||
if k not in ignoreInCheckOnEmpty: #and v != '__no_change__':
|
||||
return item
|
||||
|
@ -215,7 +242,8 @@ class KeyTableFieldInstance(RecordsFieldInstance):
|
|||
for k, v in value.items():
|
||||
row = [k]
|
||||
for idx, fi in enumerate(self.columnFieldInstances[1:]):
|
||||
row.append(fi.display(v[idx]))
|
||||
if idx < len(v):
|
||||
row.append(fi.display(v[idx]))
|
||||
rows.append(row)
|
||||
return dict(headers=headers, rows=rows)
|
||||
|
||||
|
@ -226,7 +254,8 @@ class KeyTableFieldInstance(RecordsFieldInstance):
|
|||
for k, v in value.items():
|
||||
item = {self.keyName: k}
|
||||
for idx, name in enumerate(self.dataNames):
|
||||
item[name] = v[idx]
|
||||
if idx < len(v):
|
||||
item[name] = v[idx]
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
tal:attributes="class string:${column/baseField/cssClass|string:}">
|
||||
<input type="text" style="width: auto"
|
||||
tal:define="cname column/name"
|
||||
tal:attributes="value row/?cname;
|
||||
tal:attributes="value row/?cname|nothing;
|
||||
name string:$name.$cname:records;
|
||||
style string:width: ${column/baseField/width|string:auto};" /></td>
|
||||
</tr>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2010 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2014 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
|
||||
|
@ -19,8 +19,6 @@
|
|||
"""
|
||||
Basic classes for schemas, i.e. sets of fields that may be used for creating
|
||||
editing forms or display views for objects.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.interface import implements
|
||||
|
|
|
@ -40,6 +40,10 @@ It's possible to leave some of the questions unanswered.
|
|||
>>> resp02 = Response(quest, 'john')
|
||||
>>> resp02.values = {qu01: 2, qu03: 4}
|
||||
|
||||
|
||||
Evaluation
|
||||
==========
|
||||
|
||||
Now let's calculate the result for resp01.
|
||||
|
||||
>>> res = resp01.getResult()
|
||||
|
@ -55,8 +59,8 @@ Now let's calculate the result for resp01.
|
|||
fi03 4.0
|
||||
fi01 2.4
|
||||
|
||||
Grouped Feedback Items
|
||||
======================
|
||||
Grouped feedback items
|
||||
----------------------
|
||||
|
||||
>>> from cybertools.knowledge.survey.questionnaire import QuestionGroup
|
||||
>>> qugroup = QuestionGroup(quest)
|
||||
|
@ -65,12 +69,25 @@ Grouped Feedback Items
|
|||
>>> qugroup.feedbackItems = [fi01, fi02, fi03]
|
||||
|
||||
>>> res = resp01.getGroupedResult()
|
||||
>>> for qugroup, fi, score in res:
|
||||
... print fi.text, round(score, 2)
|
||||
fi02 0.58
|
||||
>>> for r in res:
|
||||
... print r['feedback'].text, round(r['score'], 2), r['rank']
|
||||
fi02 0.58 1
|
||||
|
||||
>>> res = resp02.getGroupedResult()
|
||||
>>> for qugroup, fi, score in res:
|
||||
... print fi.text, round(score, 2)
|
||||
fi03 0.75
|
||||
>>> for r in res:
|
||||
... print r['feedback'].text, round(r['score'], 2), r['rank']
|
||||
fi03 0.75 1
|
||||
|
||||
Team evaluation
|
||||
---------------
|
||||
|
||||
>>> resp03 = Response(quest, 'mary')
|
||||
>>> resp03.values = {qu01: 1, qu02: 2, qu03: 4}
|
||||
|
||||
>>> resp01.values[qugroup] = resp01.getGroupedResult()[0]['score']
|
||||
>>> resp03.values[qugroup] = resp03.getGroupedResult()[0]['score']
|
||||
|
||||
>>> teamData = resp01.getTeamResult([qugroup], [resp01, resp03])
|
||||
>>> teamData
|
||||
[{'average': 0.6666...}]
|
||||
|
||||
|
|
|
@ -80,7 +80,8 @@ class IResponse(Interface):
|
|||
questionnaire = Attribute('The questionnaire this response belongs to.')
|
||||
party = Attribute('Some identification of the party that responded '
|
||||
'to this questionnaire.')
|
||||
values = Attribute('A mapping associating response values with questions.')
|
||||
values = Attribute('A mapping associating numeric response values with questions.')
|
||||
texts = Attribute('A mapping associating text response values with questions.')
|
||||
|
||||
def getResult():
|
||||
""" Calculate the result for this response.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||
# 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
|
||||
|
@ -58,12 +58,8 @@ class Question(object):
|
|||
self.feedbackItems = {}
|
||||
self.text = text
|
||||
self.revertAnswerOptions = False
|
||||
|
||||
def getAnswerRange(self):
|
||||
return self._answerRange or self.questionnaire.defaultAnswerRange
|
||||
def setAnswerRange(self, value):
|
||||
self._answerRange = value
|
||||
answerRange = property(getAnswerRange, setAnswerRange)
|
||||
self.questionType = 'value_selection'
|
||||
self.answerRange = None
|
||||
|
||||
|
||||
class FeedbackItem(object):
|
||||
|
@ -82,13 +78,18 @@ class Response(object):
|
|||
self.questionnaire = questionnaire
|
||||
self.party = party
|
||||
self.values = {}
|
||||
self.texts = {}
|
||||
|
||||
def getResult(self):
|
||||
result = {}
|
||||
for question, value in self.values.items():
|
||||
if question.questionType != 'value_selection':
|
||||
continue
|
||||
for fi, rf in question.feedbackItems.items():
|
||||
if question.revertAnswerOptions:
|
||||
value = question.answerRange - value - 1
|
||||
answerRange = (question.answerRange or
|
||||
self.questionnaire.defaultAnswerRange)
|
||||
value = answerRange - value - 1
|
||||
result[fi] = result.get(fi, 0.0) + rf * value
|
||||
return sorted(result.items(), key=lambda x: -x[1])
|
||||
|
||||
|
@ -97,15 +98,46 @@ class Response(object):
|
|||
for qugroup in self.questionnaire.questionGroups:
|
||||
score = scoreMax = 0.0
|
||||
for qu in qugroup.questions:
|
||||
value = self.values.get(qu)
|
||||
if value is None:
|
||||
if qu.questionType not in (None, 'value_selection'):
|
||||
continue
|
||||
value = self.values.get(qu)
|
||||
if value is None or isinstance(value, basestring):
|
||||
continue
|
||||
answerRange = (qu.answerRange or
|
||||
self.questionnaire.defaultAnswerRange)
|
||||
if qu.revertAnswerOptions:
|
||||
value = qu.answerRange - value - 1
|
||||
value = answerRange - value - 1
|
||||
score += value
|
||||
scoreMax += qu.answerRange - 1
|
||||
scoreMax += answerRange - 1
|
||||
if scoreMax > 0.0:
|
||||
relScore = score / scoreMax
|
||||
wScore = relScore * len(qugroup.feedbackItems) - 0.00001
|
||||
result.append((qugroup, qugroup.feedbackItems[int(wScore)], relScore))
|
||||
if qugroup.feedbackItems:
|
||||
feedback = qugroup.feedbackItems[int(wScore)]
|
||||
else:
|
||||
feedback = FeedbackItem()
|
||||
result.append(dict(
|
||||
group=qugroup,
|
||||
feedback=feedback,
|
||||
score=relScore))
|
||||
ranks = getRanks([r['score'] for r in result])
|
||||
for idx, r in enumerate(result):
|
||||
r['rank'] = ranks[idx]
|
||||
return result
|
||||
|
||||
def getTeamResult(self, groups, teamData):
|
||||
result = []
|
||||
for idx, group in enumerate(groups):
|
||||
values = [data.values.get(group) for data in teamData]
|
||||
values = [v for v in values if v is not None]
|
||||
#avg = sum(values) / len(teamData)
|
||||
avg = sum(values) / len(values)
|
||||
result.append(dict(group=group, average=avg))
|
||||
ranks = getRanks([r['average'] for r in result])
|
||||
for idx, r in enumerate(result):
|
||||
r['rank'] = ranks[idx]
|
||||
return result
|
||||
|
||||
def getRanks(values):
|
||||
ordered = list(reversed(sorted(values)))
|
||||
return [ordered.index(v) + 1 for v in values]
|
||||
|
|
|
@ -85,6 +85,7 @@ class IPerson(Interface):
|
|||
description=_(u'The date of birth - should be a '
|
||||
'datetime.date object.'),
|
||||
required=False,)
|
||||
birthDate.hideTime = True
|
||||
|
||||
age = schema.Int(
|
||||
title=_(u'Age'),
|
||||
|
|
Binary file not shown.
|
@ -3,7 +3,7 @@ msgstr ""
|
|||
|
||||
"Project-Id-Version: $Id$\n"
|
||||
"POT-Creation-Date: 2008-12-01 12:00 CET\n"
|
||||
"PO-Revision-Date: 2013-05-17 12:00 CET\n"
|
||||
"PO-Revision-Date: 2014-05-12 12:00 CET\n"
|
||||
"Last-Translator: Helmut Merz <helmutm@cy55.de>\n"
|
||||
"Language-Team: loops developers <helmutm@cy55.de>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
@ -65,3 +65,33 @@ msgstr "Beginn"
|
|||
msgid "End date"
|
||||
msgstr "Ende"
|
||||
|
||||
msgid "Street, number"
|
||||
msgstr "Straße, Hausnummer"
|
||||
|
||||
msgid "Street and number"
|
||||
msgstr "Straße und Hausnummer"
|
||||
|
||||
msgid "ZIP code"
|
||||
msgstr "Postleitzahl"
|
||||
|
||||
msgid "ZIP code, postal code"
|
||||
msgstr "Postleitzahl"
|
||||
|
||||
msgid "City"
|
||||
msgstr "Ort"
|
||||
|
||||
msgid "Name of the city"
|
||||
msgstr "Name der Stadt/des Orts"
|
||||
|
||||
msgid "Country code"
|
||||
msgstr "Ländercode"
|
||||
|
||||
msgid "International two-letter country code"
|
||||
msgstr "Aus zwei Buchstaben bestehender Ländercode"
|
||||
|
||||
msgid "Additional lines"
|
||||
msgstr "Zusätzliche Adresszeilen"
|
||||
|
||||
msgid "Additional address lines"
|
||||
msgstr "Zusätzliche Adresszeilen"
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2014 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
|
||||
|
@ -41,8 +41,8 @@ _not_found = object()
|
|||
def workItemStates():
|
||||
return StatesDefinition('workItemStates',
|
||||
State('new', 'new',
|
||||
('plan', 'accept', 'start', 'work', 'finish', 'delegate',
|
||||
'cancel', 'reopen'),
|
||||
('plan', 'accept', 'start', 'work', 'finish', 'delegate',
|
||||
'cancel', 'reopen'), # 'move', # ?
|
||||
color='red'),
|
||||
State('planned', 'planned',
|
||||
('plan', 'accept', 'start', 'work', 'finish', 'delegate',
|
||||
|
@ -59,7 +59,7 @@ def workItemStates():
|
|||
'move', 'cancel', 'modify'), color='lightgreen'),
|
||||
State('finished', 'finished',
|
||||
('plan', 'accept', 'start', 'work', 'finish',
|
||||
'move', 'modify', 'close'),
|
||||
'move', 'modify', 'close', 'cancel'),
|
||||
color='green'),
|
||||
State('cancelled', 'cancelled',
|
||||
('plan', 'accept', 'start', 'work', 'move', 'modify', 'close'),
|
||||
|
@ -79,8 +79,12 @@ def workItemStates():
|
|||
State('replaced', 'replaced', (), color='grey'),
|
||||
State('planned_x', 'planned', (), color='red'),
|
||||
State('accepted_x', 'accepted', (), color='yellow'),
|
||||
State('done_x', 'done', (), color='lightgreen'),
|
||||
State('finished_x', 'finished', (), color='green'),
|
||||
State('done_x', 'done',
|
||||
('modify', 'move', 'cancel'), color='lightgreen'),
|
||||
State('finished_x', 'finished',
|
||||
('modify','move', 'cancel'), color='green'),
|
||||
#State('done_y', 'done', (), color='grey'),
|
||||
#State('finished_y', 'finished', (), color='grey'),
|
||||
# transitions:
|
||||
Transition('plan', 'plan', 'planned'),
|
||||
Transition('accept', 'accept', 'accepted'),
|
||||
|
@ -96,7 +100,8 @@ def workItemStates():
|
|||
initialState='new')
|
||||
|
||||
|
||||
fieldNames = ['title', 'description', 'deadline', 'start', 'end',
|
||||
fieldNames = ['title', 'description', 'deadline', 'priority', 'activity',
|
||||
'start', 'end',
|
||||
'duration', 'effort',
|
||||
'comment', 'party'] # for use in editingRules
|
||||
|
||||
|
@ -138,7 +143,8 @@ class WorkItemType(object):
|
|||
self.title = title
|
||||
self.description = description
|
||||
self.actions = actions or list(editingRules)
|
||||
self.fields = fields or ('deadline', 'start-end', 'duration-effort')
|
||||
self.fields = fields or ('deadline', 'priority', 'activity',
|
||||
'start-end', 'duration-effort')
|
||||
self.indicator = indicator
|
||||
self.delegatedState = delegatedState
|
||||
self.prefillDate = prefillDate
|
||||
|
@ -156,9 +162,10 @@ workItemTypes = Jeep((
|
|||
fields =('deadline',),
|
||||
indicator='work_deadline'),
|
||||
WorkItemType('checkup', u'Check-up',
|
||||
actions=('plan', 'accept', 'finish', 'cancel',
|
||||
actions=('plan', 'accept', 'start', 'finish', 'cancel',
|
||||
'modify', 'delegate', 'close', 'reopen'),
|
||||
fields =('deadline', 'start-end',),
|
||||
#fields =('deadline', 'start-end',),
|
||||
fields =('deadline', 'daterange',),
|
||||
indicator='work_checkup',
|
||||
delegatedState='closed', prefillDate=False),
|
||||
))
|
||||
|
@ -177,7 +184,7 @@ class WorkItem(Stateful, Track):
|
|||
statesDefinition = 'organize.workItemStates'
|
||||
|
||||
initAttributes = set(['workItemType', 'party', 'title', 'description',
|
||||
'deadline', 'start', 'end',
|
||||
'deadline', 'priority', 'activity', 'start', 'end',
|
||||
'duration', 'effort'])
|
||||
|
||||
def __init__(self, taskId, runId, userName, data):
|
||||
|
@ -225,13 +232,16 @@ class WorkItem(Stateful, Track):
|
|||
return list(getParent(self).query(runId=self.runId))
|
||||
|
||||
def doAction(self, action, userName, **kw):
|
||||
if self != self.currentWorkItems[-1]:
|
||||
raise ValueError("Actions are only allowed on the last item of a run.")
|
||||
#if self != self.currentWorkItems[-1]:
|
||||
# raise ValueError("Actions are only allowed on the last item of a run.")
|
||||
if action not in [t.name for t in self.getAvailableTransitions()]:
|
||||
raise ValueError("Action '%s' not allowed in state '%s'" %
|
||||
(action, self.state))
|
||||
if action in self.specialActions:
|
||||
return self.specialActions[action](self, userName, **kw)
|
||||
return self.doStandardAction(action, userName, **kw)
|
||||
|
||||
def doStandardAction(self, action, userName, **kw):
|
||||
if self.state == 'new':
|
||||
self.setData(**kw)
|
||||
self.doTransition(action)
|
||||
|
@ -244,6 +254,9 @@ class WorkItem(Stateful, Track):
|
|||
elif self.state in ('planned', 'accepted', 'done'):
|
||||
self.state = self.state + '_x'
|
||||
self.reindex('state')
|
||||
elif self.state in ('finished',) and action == 'cancel':
|
||||
self.state = self.state + '_x'
|
||||
self.reindex('state')
|
||||
new.doTransition(action)
|
||||
new.reindex()
|
||||
return new
|
||||
|
@ -279,25 +292,57 @@ class WorkItem(Stateful, Track):
|
|||
delegated.data['target'] = new.name
|
||||
return new
|
||||
|
||||
def doStart(self, userName, **kw):
|
||||
action = 'start'
|
||||
# stop any running work item of user:
|
||||
# TODO: check: party query OK?
|
||||
if (userName == self.userName and
|
||||
self.workItemType in (None, 'work') and
|
||||
self.state != 'running'):
|
||||
running = getParent(self).query(
|
||||
party=userName, state='running')
|
||||
for wi in running:
|
||||
if wi.workItemType in 'work':
|
||||
wi.doAction('work', userName,
|
||||
end=(kw.get('start') or getTimeStamp()))
|
||||
# standard creation of new work item:
|
||||
if not kw.get('start'):
|
||||
kw['start'] = getTimeStamp()
|
||||
kw['end'] = None
|
||||
kw['duration'] = kw['effort'] = 0
|
||||
return self.doStandardAction(action, userName, **kw)
|
||||
|
||||
def move(self, userName, **kw):
|
||||
xkw = dict(kw)
|
||||
for k in ('deadline', 'start', 'end'):
|
||||
xkw.pop(k, None) # do not change on source item
|
||||
moved = self.createNew('move', userName, **xkw)
|
||||
moved.userName = self.userName
|
||||
moved.state = 'moved'
|
||||
moved.reindex()
|
||||
if self.state == 'new': # should this be possible?
|
||||
moved = self
|
||||
self.setData(kw)
|
||||
if self.state in ('done', 'finished', 'running'):
|
||||
moved = self # is this OK? or better new state ..._y?
|
||||
else:
|
||||
moved = self.createNew('move', userName, **xkw)
|
||||
moved.userName = self.userName
|
||||
task = kw.pop('task', None)
|
||||
new = moved.createNew(None, userName, taskId=task, runId=0, **kw)
|
||||
new.userName = self.userName
|
||||
new.data['source'] = moved.name
|
||||
new.state = self.state
|
||||
if self.state == 'new':
|
||||
new.state = 'planned'
|
||||
else:
|
||||
new.state = self.state
|
||||
new.reindex()
|
||||
moved.data['target'] = new.name
|
||||
if self.state in ('planned', 'accepted', 'delegated', 'moved',
|
||||
'done', 'finished'):
|
||||
moved.state = 'moved'
|
||||
moved.reindex()
|
||||
if self.state in ('planned', 'accepted', 'delegated', 'moved'):
|
||||
#'done', 'finished'):
|
||||
self.state = self.state + '_x'
|
||||
self.reindex('state')
|
||||
#elif self.state in ('done', 'finished'):
|
||||
# self.state = self.state + '_y'
|
||||
# self.reindex('state')
|
||||
return new
|
||||
|
||||
def close(self, userName, **kw):
|
||||
|
@ -315,7 +360,8 @@ class WorkItem(Stateful, Track):
|
|||
item.reindex('state')
|
||||
return new
|
||||
|
||||
specialActions = dict(modify=modify, delegate=delegate, move=move,
|
||||
specialActions = dict(modify=modify, delegate=delegate,
|
||||
start=doStart, move=move,
|
||||
close=close)
|
||||
|
||||
def setData(self, ignoreParty=False, **kw):
|
||||
|
@ -328,7 +374,7 @@ class WorkItem(Stateful, Track):
|
|||
self.reindex('userName')
|
||||
start = kw.get('start') or kw.get('deadline') # TODO: check OK?
|
||||
if start is not None:
|
||||
self.timeStamp = start
|
||||
self.timeStamp = start # TODO: better use end
|
||||
self.reindex('timeStamp')
|
||||
data = self.data
|
||||
for k, v in kw.items():
|
||||
|
|
|
@ -100,7 +100,7 @@ but may also be specified explicitly.
|
|||
>>> wi03 = wi02.doAction('start', 'jim', start=1229958000)
|
||||
>>> wi03
|
||||
<WorkItem ['001', 1, 'jim', '2008-12-22 16:00', 'running']:
|
||||
{'duration': 700, 'start': 1229958000, 'created': ..., 'creator': 'jim'}>
|
||||
{'duration': 0, 'start': 1229958000, 'created': ..., 'creator': 'jim'}>
|
||||
|
||||
Stopping and finishing work
|
||||
---------------------------
|
||||
|
@ -113,7 +113,7 @@ as "running" will be replaced by a new one.
|
|||
|
||||
>>> wi03
|
||||
<WorkItem ['001', 1, 'jim', '2008-12-22 16:00', 'replaced']:
|
||||
{'duration': 700, 'start': 1229958000, 'created': ..., 'creator': 'jim'}>
|
||||
{'duration': 0, 'start': 1229958000, 'created': ..., 'creator': 'jim'}>
|
||||
>>> wi04
|
||||
<WorkItem ['001', 1, 'jim', '2008-12-22 16:00', 'done']:
|
||||
{'start': 1229958000, 'created': ..., 'end': 1229958300, 'creator': 'jim'}>
|
||||
|
|
|
@ -52,7 +52,7 @@ class Stateful(object):
|
|||
|
||||
def getStateObject(self):
|
||||
states = self.getStatesDefinition().states
|
||||
if self.state not in states:
|
||||
if self.getState() not in states:
|
||||
self.state = self.getStatesDefinition().initialState
|
||||
return states[self.state]
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2014 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
|
||||
|
@ -18,8 +18,6 @@
|
|||
|
||||
"""
|
||||
ZODB-/BTree-based implementation of user interaction tracking.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
import time
|
||||
|
@ -142,11 +140,12 @@ class TrackingStorage(BTreeContainer):
|
|||
|
||||
def setupIndexes(self):
|
||||
changed = False
|
||||
for idx in self.indexAttributes:
|
||||
for idx in self.trackFactory.index_attributes:
|
||||
if idx not in self.indexes:
|
||||
self.indexes[idx] = FieldIndex()
|
||||
changed = True
|
||||
if changed:
|
||||
self.indexAttributes = self.trackFactory.index_attributes
|
||||
self.reindexTracks()
|
||||
|
||||
def idFromNum(self, num):
|
||||
|
@ -237,6 +236,7 @@ class TrackingStorage(BTreeContainer):
|
|||
self.unindexTrack(trackNum, track)
|
||||
|
||||
def indexTrack(self, trackNum, track, idx=None):
|
||||
#self.setupIndexes()
|
||||
if not trackNum:
|
||||
trackNum = int(track.__name__)
|
||||
data = track.indexdata
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||
# Copyright (c) 2014 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
|
||||
|
@ -18,8 +18,6 @@
|
|||
|
||||
"""
|
||||
Interface definitions for tracking of user interactions.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
from zope.interface import Interface, Attribute
|
||||
|
@ -50,6 +48,12 @@ class ITrack(Interface):
|
|||
# """ Return the internal name (ID) of the track.
|
||||
# """
|
||||
|
||||
def update(newData, overwrite=False):
|
||||
""" Update the track with new data, by default creating a new
|
||||
track. Overwrite the existing track if the corresponding
|
||||
flag is set.
|
||||
"""
|
||||
|
||||
|
||||
class ITrackingStorage(Interface):
|
||||
""" A utility for storing user tracks.
|
||||
|
|
|
@ -58,6 +58,7 @@ class ExternalEditorView(object):
|
|||
r.append('cookie:' + cookie)
|
||||
r.append('')
|
||||
r.append(fromUnicode(data))
|
||||
r = [str(item) for item in r]
|
||||
result = '\n'.join(r)
|
||||
self.setHeaders(len(result))
|
||||
return result
|
||||
|
|
Loading…
Add table
Reference in a new issue