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">
|
||||
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>
|
||||
|
||||
|
||||
|
@ -24,11 +42,18 @@
|
|||
</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)">
|
||||
<a tal:attributes="href value/url"
|
||||
<a tal:omit-tag="not:value/url"
|
||||
tal:attributes="href value/url"
|
||||
tal:content="value/title" /></td>
|
||||
</metal:standard>
|
||||
</metal:target>
|
||||
|
||||
|
||||
</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
|
||||
>>> resultsView = ResultsView(reportNode, TestRequest())
|
||||
|
||||
>>> results = resultsView.results().getResult()
|
||||
>>> len(results)
|
||||
>>> results = resultsView.results()#.getResult()
|
||||
>>> len(list(results))
|
||||
1
|
||||
>>> for row in results:
|
||||
... for col in resultsView.displayedColumns:
|
||||
... print col.getDisplayValue(row),
|
||||
... print
|
||||
08/12/28 1230487200 1230491700
|
||||
08/12/28 19:00 20:15
|
||||
{'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
|
||||
=============
|
||||
|
|
|
@ -33,41 +33,57 @@ from cybertools.util.date import timeStamp2Date
|
|||
from cybertools.util.format import formatDate
|
||||
from cybertools.util.jeep import Jeep
|
||||
from loops.common import adapted, baseObject
|
||||
from loops.expert.field import TargetField
|
||||
from loops.expert.report import ReportInstance
|
||||
from loops import util
|
||||
|
||||
results_template = ViewPageTemplateFile('results.pt')
|
||||
|
||||
class DateField(Field):
|
||||
|
||||
class TargetField(Field):
|
||||
|
||||
renderer = 'target'
|
||||
part = 'date'
|
||||
format = 'short'
|
||||
renderer = 'right'
|
||||
|
||||
def getValue(self, row):
|
||||
value = self.getRawValue(row)
|
||||
return util.getObjectForUid(value)
|
||||
|
||||
def getDisplayValue(self, row):
|
||||
value = self.getValue(row)
|
||||
if value is None:
|
||||
return dict(title=self.getRawValue(row), url=u'')
|
||||
view = row.parent.context.view
|
||||
return dict(title=value.title, url=view.getUrlForTarget(value))
|
||||
|
||||
|
||||
class DayField(Field):
|
||||
|
||||
def getValue(self, row):
|
||||
return timeStamp2Date(self.getRawValue(row))
|
||||
return None
|
||||
return timeStamp2Date(value)
|
||||
|
||||
def getDisplayValue(self, row):
|
||||
value = self.getValue(row)
|
||||
if value:
|
||||
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''
|
||||
|
||||
|
||||
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',
|
||||
description=u'The tasks from which work items should be selected.',
|
||||
executionSteps=['query'])
|
||||
|
@ -77,13 +93,13 @@ dayFrom = Field('dayFrom', u'Start Day',
|
|||
dayTo = Field('dayTo', u'End Day',
|
||||
description=u'The last day until which to select work.',
|
||||
executionSteps=['query'])
|
||||
day = DayField('day', u'Day',
|
||||
day = DateField('day', u'Day',
|
||||
description=u'The day the work was done.',
|
||||
executionSteps=['sort', 'output'])
|
||||
timeStart = Field('start', u'Start Time',
|
||||
timeStart = TimeField('start', u'Start',
|
||||
description=u'The time the unit of work was started.',
|
||||
executionSteps=['sort', 'output'])
|
||||
timeEnd = Field('end', u'End Time',
|
||||
timeEnd = TimeField('end', u'End',
|
||||
description=u'The time the unit of work was finished.',
|
||||
executionSteps=['output'])
|
||||
task = TargetField('taskId', u'Task',
|
||||
|
@ -91,19 +107,19 @@ task = TargetField('taskId', u'Task',
|
|||
executionSteps=['output'])
|
||||
party = TargetField('userName', u'Party',
|
||||
description=u'The party (usually a person) who did the work.',
|
||||
executionSteps=['sort', 'output'])
|
||||
executionSteps=['query', 'sort', 'output'])
|
||||
title = Field('title', u'Title',
|
||||
description=u'The short description of the work.',
|
||||
executionSteps=['output'])
|
||||
description = Field('description', u'Description',
|
||||
description=u'The long description of the work.',
|
||||
executionSteps=['x_output'])
|
||||
duration = Field('duration', u'Duration',
|
||||
duration = DurationField('duration', u'Duration',
|
||||
description=u'The duration of the work.',
|
||||
executionSteps=['output'])
|
||||
effort = Field('effort', u'Effort',
|
||||
effort = DurationField('effort', u'Effort',
|
||||
description=u'The effort of the work.',
|
||||
executionSteps=['output', 'sum'])
|
||||
executionSteps=['output', 'totals'])
|
||||
state = Field('state', u'State',
|
||||
description=u'The state of the work.',
|
||||
executionSteps=['query', 'output'])
|
||||
|
@ -122,7 +138,19 @@ class WorkRow(BaseRow):
|
|||
def getDay(self, attr):
|
||||
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):
|
||||
|
@ -131,37 +159,46 @@ class WorkReportInstance(ReportInstance):
|
|||
label = u'Work Report'
|
||||
|
||||
rowFactory = WorkRow
|
||||
|
||||
fields = Jeep((dayFrom, dayTo, tasks,
|
||||
day, timeStart, timeEnd, task, party, title, description,
|
||||
duration, effort, state))
|
||||
|
||||
defaultOutputFields = fields
|
||||
defaultSortCriteria = (day, timeStart,)
|
||||
|
||||
@property
|
||||
def queryCriteria(self):
|
||||
form = self.view.request.form
|
||||
crit = self.context.queryCriteria
|
||||
if crit is None:
|
||||
f = self.fields['tasks']
|
||||
tasks = baseObject(self.context).getChildren([self.hasReportPredicate])
|
||||
tasks = [util.getUidForObject(task) for task in tasks]
|
||||
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)
|
||||
|
||||
def getResultsRenderer(self, name, defaultMacros):
|
||||
return results_template.macros[name]
|
||||
|
||||
def selectObjects(self, parts):
|
||||
result = []
|
||||
tasks = parts.pop('tasks').comparisonValue
|
||||
tasks = [util.getObjectForUid(t) for t in parts.pop('tasks').comparisonValue]
|
||||
for t in list(tasks):
|
||||
tasks.extend(self.getAllSubtasks(t))
|
||||
for t in tasks:
|
||||
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
|
||||
|
||||
def selectWorkItems(self, task, parts):
|
||||
wi = self.workItems
|
||||
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):
|
||||
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