work in progress: reporting; example 'work statement' report is operational
This commit is contained in:
parent
55fbb7eefc
commit
0a1d2ec262
5 changed files with 150 additions and 102 deletions
|
@ -15,7 +15,25 @@
|
||||||
|
|
||||||
|
|
||||||
<div metal:define-macro="results">
|
<div metal:define-macro="results">
|
||||||
Default Results Listing
|
<h2 i18n:translate="">Work Items</h2>
|
||||||
|
<table tal:define="results view/results">
|
||||||
|
<tr>
|
||||||
|
<th tal:repeat="col view/displayedColumns"
|
||||||
|
tal:content="col/title"
|
||||||
|
i18n:translate="" />
|
||||||
|
</tr>
|
||||||
|
<tr tal:repeat="row results">
|
||||||
|
<tal:column repeat="col view/displayedColumns">
|
||||||
|
<metal:column use-macro="python:view.getColumnRenderer(col.renderer)" />
|
||||||
|
</tal:column>
|
||||||
|
</tr>
|
||||||
|
<tr tal:define="row nocall:results/totals"
|
||||||
|
tal:condition="nocall:row">
|
||||||
|
<tal:column repeat="col view/displayedColumns">
|
||||||
|
<metal:column use-macro="python:view.getColumnRenderer(col.renderer)" />
|
||||||
|
</tal:column>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,11 +42,18 @@
|
||||||
</metal:standard>
|
</metal:standard>
|
||||||
|
|
||||||
|
|
||||||
<metal:standard define-macro="target">
|
<metal:right define-macro="right">
|
||||||
|
<td style="text-align: right"
|
||||||
|
tal:content="python:col.getDisplayValue(row)" />
|
||||||
|
</metal:right>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:target define-macro="target">
|
||||||
<td tal:define="value python:col.getDisplayValue(row)">
|
<td tal:define="value python:col.getDisplayValue(row)">
|
||||||
<a tal:attributes="href value/url"
|
<a tal:omit-tag="not:value/url"
|
||||||
|
tal:attributes="href value/url"
|
||||||
tal:content="value/title" /></td>
|
tal:content="value/title" /></td>
|
||||||
</metal:standard>
|
</metal:target>
|
||||||
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
43
expert/field.py
Normal file
43
expert/field.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2011 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
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Field definitions for reports.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from cybertools.composer.report.field import Field
|
||||||
|
from loops import util
|
||||||
|
|
||||||
|
|
||||||
|
class TargetField(Field):
|
||||||
|
|
||||||
|
renderer = 'target'
|
||||||
|
|
||||||
|
def getValue(self, row):
|
||||||
|
value = self.getRawValue(row)
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return util.getObjectForUid(value)
|
||||||
|
|
||||||
|
def getDisplayValue(self, row):
|
||||||
|
value = self.getValue(row)
|
||||||
|
if value is None:
|
||||||
|
return dict(title=u'', url=u'')
|
||||||
|
view = row.parent.context.view
|
||||||
|
return dict(title=value.title, url=view.getUrlForTarget(value))
|
||||||
|
|
|
@ -220,16 +220,20 @@ in a results view.
|
||||||
>>> from loops.expert.browser.report import ReportView, ResultsView
|
>>> from loops.expert.browser.report import ReportView, ResultsView
|
||||||
>>> resultsView = ResultsView(reportNode, TestRequest())
|
>>> resultsView = ResultsView(reportNode, TestRequest())
|
||||||
|
|
||||||
>>> results = resultsView.results().getResult()
|
>>> results = resultsView.results()#.getResult()
|
||||||
>>> len(results)
|
>>> len(list(results))
|
||||||
1
|
1
|
||||||
>>> for row in results:
|
>>> for row in results:
|
||||||
... for col in resultsView.displayedColumns:
|
... for col in resultsView.displayedColumns:
|
||||||
... print col.getDisplayValue(row),
|
... print col.getDisplayValue(row),
|
||||||
... print
|
... print
|
||||||
08/12/28 1230487200 1230491700
|
08/12/28 19:00 20:15
|
||||||
{'url': '.../home/report/.36', 'title': u'loops Development'}
|
{'url': '.../home/report/.36', 'title': u'loops Development'}
|
||||||
{'url': '.../home/report/.33', 'title': u'john'} 4500 900 finished
|
{'url': '.../home/report/.33', 'title': u'john'} 01:15 00:15 finished
|
||||||
|
|
||||||
|
>>> results.totals.data
|
||||||
|
{'effort': 900}
|
||||||
|
|
||||||
|
|
||||||
Fin de partie
|
Fin de partie
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -33,41 +33,57 @@ from cybertools.util.date import timeStamp2Date
|
||||||
from cybertools.util.format import formatDate
|
from cybertools.util.format import formatDate
|
||||||
from cybertools.util.jeep import Jeep
|
from cybertools.util.jeep import Jeep
|
||||||
from loops.common import adapted, baseObject
|
from loops.common import adapted, baseObject
|
||||||
|
from loops.expert.field import TargetField
|
||||||
from loops.expert.report import ReportInstance
|
from loops.expert.report import ReportInstance
|
||||||
from loops import util
|
from loops import util
|
||||||
|
|
||||||
results_template = ViewPageTemplateFile('results.pt')
|
|
||||||
|
|
||||||
|
class DateField(Field):
|
||||||
|
|
||||||
class TargetField(Field):
|
part = 'date'
|
||||||
|
format = 'short'
|
||||||
renderer = 'target'
|
renderer = 'right'
|
||||||
|
|
||||||
def getValue(self, row):
|
def getValue(self, row):
|
||||||
value = self.getRawValue(row)
|
value = self.getRawValue(row)
|
||||||
return util.getObjectForUid(value)
|
|
||||||
|
|
||||||
def getDisplayValue(self, row):
|
|
||||||
value = self.getValue(row)
|
|
||||||
if value is None:
|
if value is None:
|
||||||
return dict(title=self.getRawValue(row), url=u'')
|
return None
|
||||||
view = row.parent.context.view
|
return timeStamp2Date(value)
|
||||||
return dict(title=value.title, url=view.getUrlForTarget(value))
|
|
||||||
|
|
||||||
|
|
||||||
class DayField(Field):
|
|
||||||
|
|
||||||
def getValue(self, row):
|
|
||||||
return timeStamp2Date(self.getRawValue(row))
|
|
||||||
|
|
||||||
def getDisplayValue(self, row):
|
def getDisplayValue(self, row):
|
||||||
value = self.getValue(row)
|
value = self.getValue(row)
|
||||||
if value:
|
if value:
|
||||||
view = row.parent.context.view
|
view = row.parent.context.view
|
||||||
return formatDate(value, 'date', 'short', view.languageInfo.language)
|
return formatDate(value, self.part, self.format,
|
||||||
|
view.languageInfo.language)
|
||||||
return u''
|
return u''
|
||||||
|
|
||||||
|
|
||||||
|
class TimeField(DateField):
|
||||||
|
|
||||||
|
part = 'time'
|
||||||
|
|
||||||
|
|
||||||
|
class DurationField(Field):
|
||||||
|
|
||||||
|
renderer = 'right'
|
||||||
|
|
||||||
|
def getValue(self, row):
|
||||||
|
value = self.getRawValue(row)
|
||||||
|
if value and 'totals' in self.executionSteps:
|
||||||
|
data = row.parent.totals.data
|
||||||
|
data[self.name] = data.get(self.name, 0) + value
|
||||||
|
if value:
|
||||||
|
value /= 3600.0
|
||||||
|
return value
|
||||||
|
|
||||||
|
def getDisplayValue(self, row):
|
||||||
|
value = self.getValue(row)
|
||||||
|
if not value:
|
||||||
|
return u''
|
||||||
|
return u'%02i:%02i' % divmod(value * 60, 60)
|
||||||
|
|
||||||
|
|
||||||
tasks = Field('tasks', u'Tasks',
|
tasks = Field('tasks', u'Tasks',
|
||||||
description=u'The tasks from which work items should be selected.',
|
description=u'The tasks from which work items should be selected.',
|
||||||
executionSteps=['query'])
|
executionSteps=['query'])
|
||||||
|
@ -77,13 +93,13 @@ dayFrom = Field('dayFrom', u'Start Day',
|
||||||
dayTo = Field('dayTo', u'End Day',
|
dayTo = Field('dayTo', u'End Day',
|
||||||
description=u'The last day until which to select work.',
|
description=u'The last day until which to select work.',
|
||||||
executionSteps=['query'])
|
executionSteps=['query'])
|
||||||
day = DayField('day', u'Day',
|
day = DateField('day', u'Day',
|
||||||
description=u'The day the work was done.',
|
description=u'The day the work was done.',
|
||||||
executionSteps=['sort', 'output'])
|
executionSteps=['sort', 'output'])
|
||||||
timeStart = Field('start', u'Start Time',
|
timeStart = TimeField('start', u'Start',
|
||||||
description=u'The time the unit of work was started.',
|
description=u'The time the unit of work was started.',
|
||||||
executionSteps=['sort', 'output'])
|
executionSteps=['sort', 'output'])
|
||||||
timeEnd = Field('end', u'End Time',
|
timeEnd = TimeField('end', u'End',
|
||||||
description=u'The time the unit of work was finished.',
|
description=u'The time the unit of work was finished.',
|
||||||
executionSteps=['output'])
|
executionSteps=['output'])
|
||||||
task = TargetField('taskId', u'Task',
|
task = TargetField('taskId', u'Task',
|
||||||
|
@ -91,19 +107,19 @@ task = TargetField('taskId', u'Task',
|
||||||
executionSteps=['output'])
|
executionSteps=['output'])
|
||||||
party = TargetField('userName', u'Party',
|
party = TargetField('userName', u'Party',
|
||||||
description=u'The party (usually a person) who did the work.',
|
description=u'The party (usually a person) who did the work.',
|
||||||
executionSteps=['sort', 'output'])
|
executionSteps=['query', 'sort', 'output'])
|
||||||
title = Field('title', u'Title',
|
title = Field('title', u'Title',
|
||||||
description=u'The short description of the work.',
|
description=u'The short description of the work.',
|
||||||
executionSteps=['output'])
|
executionSteps=['output'])
|
||||||
description = Field('description', u'Description',
|
description = Field('description', u'Description',
|
||||||
description=u'The long description of the work.',
|
description=u'The long description of the work.',
|
||||||
executionSteps=['x_output'])
|
executionSteps=['x_output'])
|
||||||
duration = Field('duration', u'Duration',
|
duration = DurationField('duration', u'Duration',
|
||||||
description=u'The duration of the work.',
|
description=u'The duration of the work.',
|
||||||
executionSteps=['output'])
|
executionSteps=['output'])
|
||||||
effort = Field('effort', u'Effort',
|
effort = DurationField('effort', u'Effort',
|
||||||
description=u'The effort of the work.',
|
description=u'The effort of the work.',
|
||||||
executionSteps=['output', 'sum'])
|
executionSteps=['output', 'totals'])
|
||||||
state = Field('state', u'State',
|
state = Field('state', u'State',
|
||||||
description=u'The state of the work.',
|
description=u'The state of the work.',
|
||||||
executionSteps=['query', 'output'])
|
executionSteps=['query', 'output'])
|
||||||
|
@ -122,7 +138,19 @@ class WorkRow(BaseRow):
|
||||||
def getDay(self, attr):
|
def getDay(self, attr):
|
||||||
return self.context.timeStamp
|
return self.context.timeStamp
|
||||||
|
|
||||||
attributeHandlers = dict(day=getDay)
|
def getDuration(self, attr):
|
||||||
|
value = self.context.data.get('duration')
|
||||||
|
if value is None:
|
||||||
|
value = self.getRawValue('end') - self.getRawValue('start')
|
||||||
|
return value
|
||||||
|
|
||||||
|
def getEffort(self, attr):
|
||||||
|
value = self.context.data.get('effort')
|
||||||
|
if value is None:
|
||||||
|
value = self.getDuration(attr)
|
||||||
|
return value
|
||||||
|
|
||||||
|
attributeHandlers = dict(day=getDay, duration=getDuration, effort=getEffort)
|
||||||
|
|
||||||
|
|
||||||
class WorkReportInstance(ReportInstance):
|
class WorkReportInstance(ReportInstance):
|
||||||
|
@ -131,37 +159,46 @@ class WorkReportInstance(ReportInstance):
|
||||||
label = u'Work Report'
|
label = u'Work Report'
|
||||||
|
|
||||||
rowFactory = WorkRow
|
rowFactory = WorkRow
|
||||||
|
|
||||||
fields = Jeep((dayFrom, dayTo, tasks,
|
fields = Jeep((dayFrom, dayTo, tasks,
|
||||||
day, timeStart, timeEnd, task, party, title, description,
|
day, timeStart, timeEnd, task, party, title, description,
|
||||||
duration, effort, state))
|
duration, effort, state))
|
||||||
|
|
||||||
defaultOutputFields = fields
|
defaultOutputFields = fields
|
||||||
|
defaultSortCriteria = (day, timeStart,)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def queryCriteria(self):
|
def queryCriteria(self):
|
||||||
|
form = self.view.request.form
|
||||||
crit = self.context.queryCriteria
|
crit = self.context.queryCriteria
|
||||||
if crit is None:
|
if crit is None:
|
||||||
f = self.fields['tasks']
|
f = self.fields['tasks']
|
||||||
tasks = baseObject(self.context).getChildren([self.hasReportPredicate])
|
tasks = baseObject(self.context).getChildren([self.hasReportPredicate])
|
||||||
|
tasks = [util.getUidForObject(task) for task in tasks]
|
||||||
crit = [LeafQueryCriteria(f.name, f.operator, tasks, f)]
|
crit = [LeafQueryCriteria(f.name, f.operator, tasks, f)]
|
||||||
|
for f in self.getAllQueryFields():
|
||||||
|
if f.name in form:
|
||||||
|
crit.append(LeafQueryCriteria(f.name, f.operator, form[f.name], f))
|
||||||
return CompoundQueryCriteria(crit)
|
return CompoundQueryCriteria(crit)
|
||||||
|
|
||||||
def getResultsRenderer(self, name, defaultMacros):
|
|
||||||
return results_template.macros[name]
|
|
||||||
|
|
||||||
def selectObjects(self, parts):
|
def selectObjects(self, parts):
|
||||||
result = []
|
result = []
|
||||||
tasks = parts.pop('tasks').comparisonValue
|
tasks = [util.getObjectForUid(t) for t in parts.pop('tasks').comparisonValue]
|
||||||
for t in list(tasks):
|
for t in list(tasks):
|
||||||
tasks.extend(self.getAllSubtasks(t))
|
tasks.extend(self.getAllSubtasks(t))
|
||||||
for t in tasks:
|
for t in tasks:
|
||||||
result.extend(self.selectWorkItems(t, parts))
|
result.extend(self.selectWorkItems(t, parts))
|
||||||
# TODO: remove parts already used for selection from parts list
|
# remove parts already used for selection from parts list:
|
||||||
|
parts.pop('userName', None)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def selectWorkItems(self, task, parts):
|
def selectWorkItems(self, task, parts):
|
||||||
wi = self.workItems
|
|
||||||
states = ['done', 'done_x', 'finished']
|
states = ['done', 'done_x', 'finished']
|
||||||
return wi.query(task=util.getUidForObject(task), state=states)
|
kw = dict(task=util.getUidForObject(task), state=states)
|
||||||
|
if 'userName' in parts:
|
||||||
|
kw['userName'] = parts['userName'].comparisonValue
|
||||||
|
wi = self.workItems
|
||||||
|
return wi.query(**kw)
|
||||||
|
|
||||||
def getAllSubtasks(self, concept):
|
def getAllSubtasks(self, concept):
|
||||||
result = []
|
result = []
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
<html i18n:domain="loops">
|
|
||||||
|
|
||||||
|
|
||||||
<div metal:define-macro="results">
|
|
||||||
<h2 i18n:translate="">Work Items</h2>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th tal:repeat="col view/displayedColumns"
|
|
||||||
tal:content="col/title"
|
|
||||||
i18n:translate="" />
|
|
||||||
</tr>
|
|
||||||
<tr tal:repeat="row view/results">
|
|
||||||
<tal:column repeat="col view/displayedColumns">
|
|
||||||
<metal:column use-macro="python:view.getColumnRenderer(col.renderer)" />
|
|
||||||
</tal:column>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div metal:define-macro="xx_results">
|
|
||||||
Work Items Listing
|
|
||||||
<table class="listing">
|
|
||||||
<tr>
|
|
||||||
<tal:colheader repeat="column work/allColumns">
|
|
||||||
<th tal:condition="python: column in work.columns"
|
|
||||||
tal:content="column"
|
|
||||||
i18n:translate="">Task</th>
|
|
||||||
</tal:colheader>
|
|
||||||
</tr>
|
|
||||||
<tal:workitem tal:repeat="row work/listWorkItems">
|
|
||||||
<tr tal:condition="row/monthChanged">
|
|
||||||
<td class="headline"
|
|
||||||
tal:attributes="colspan python: len(work.columns)"
|
|
||||||
tal:content="row/month">2009-01</td></tr>
|
|
||||||
<tr tal:attributes="class python:
|
|
||||||
(repeat['row'].odd() and 'even' or 'odd')">
|
|
||||||
<td class="nowrap center"
|
|
||||||
tal:define="today python: row.isToday and ' today' or ''"
|
|
||||||
tal:attributes="title row/weekDay;
|
|
||||||
class string:nowrap center$today"
|
|
||||||
i18n:attributes="title"
|
|
||||||
tal:content="row/day">2007-03-30</td>
|
|
||||||
<td class="nowrap center" tal:content="row/start">17:30</td>
|
|
||||||
<td class="nowrap center" tal:content="row/end">20:00</td>
|
|
||||||
<td class="nowrap center" tal:content="row/duration">2:30</td>
|
|
||||||
<td tal:condition="python: 'Task' in work.columns">
|
|
||||||
<a tal:attributes="href row/objectData/url"
|
|
||||||
tal:content="row/objectData/title">Task</a></td>
|
|
||||||
<td tal:condition="python: 'User' in work.columns">
|
|
||||||
<a tal:attributes="href row/user/url"
|
|
||||||
tal:content="row/user/title">John</a></td>
|
|
||||||
<td tal:content="row/track/title"
|
|
||||||
tal:attributes="title row/descriptionPlain">Title</td>
|
|
||||||
</tr>
|
|
||||||
</tal:workitem>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</html>
|
|
Loading…
Add table
Reference in a new issue