merge branch master
This commit is contained in:
commit
0aa539e8f8
8 changed files with 162 additions and 47 deletions
|
@ -483,6 +483,7 @@ class NodeView(BaseView):
|
||||||
def virtualTarget(self):
|
def virtualTarget(self):
|
||||||
tv = self.viewAnnotations.get('targetView')
|
tv = self.viewAnnotations.get('targetView')
|
||||||
if tv is not None:
|
if tv is not None:
|
||||||
|
tv.setupController()
|
||||||
return tv
|
return tv
|
||||||
setup = self.setupTarget
|
setup = self.setupTarget
|
||||||
return self.getViewForTarget(self.virtualTargetObject, setup=setup)
|
return self.getViewForTarget(self.virtualTargetObject, setup=setup)
|
||||||
|
|
|
@ -2,21 +2,73 @@
|
||||||
|
|
||||||
|
|
||||||
<div metal:define-macro="main">
|
<div metal:define-macro="main">
|
||||||
<div tal:attributes="class string:content-$level;">
|
<div tal:define="report item/reportInstance;
|
||||||
|
reportView nocall:item"
|
||||||
|
tal:attributes="class string:content-$level;">
|
||||||
<metal:block use-macro="view/concept_macros/concepttitle" />
|
<metal:block use-macro="view/concept_macros/concepttitle" />
|
||||||
<form method="post" name="report_data" action="results.html">
|
<form method="post" name="report_data">
|
||||||
<tal:hidden define="params item/dynamicParams">
|
<tal:hidden define="params item/dynamicParams"
|
||||||
|
tal:condition="nothing">
|
||||||
<input type="hidden"
|
<input type="hidden"
|
||||||
tal:repeat="name params"
|
tal:repeat="name params"
|
||||||
tal:attributes="name name;
|
tal:attributes="name name;
|
||||||
value params/?name" /></tal:hidden>
|
value params/?name" /></tal:hidden>
|
||||||
|
<div metal:use-macro="item/report_macros/params" />
|
||||||
<div metal:define-macro="buttons">
|
<div metal:define-macro="buttons">
|
||||||
<input type="submit" name="report_execute" value="Execute Report"
|
<input type="submit" name="report_execute" value="Execute Report"
|
||||||
i18n:attributes="value" />
|
i18n:attributes="value" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div metal:use-macro="item/resultsRenderer" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:block define-macro="params">
|
||||||
|
<metal:block use-macro="item/report_macros/query" />
|
||||||
|
</metal:block>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:block define-macro="query"
|
||||||
|
tal:define="criteria
|
||||||
|
item/reportInstance/queryCriteria/parts|nothing">
|
||||||
|
<table>
|
||||||
|
<tr tal:repeat="field item/queryFields">
|
||||||
|
<tal:field define="fieldType field/fieldType;
|
||||||
|
crit python:criteria and criteria.get(field.name)">
|
||||||
|
<td><b><span tal:content="field/title" />: </b></td>
|
||||||
|
<td><metal:field use-macro="item/report_macros/?fieldType" /></td>
|
||||||
|
</tal:field>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br />
|
||||||
|
</metal:block>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:field define-macro="textline">
|
||||||
|
<input tal:attributes="name string:${field/name};
|
||||||
|
value crit/comparisonValue|nothing" />
|
||||||
|
</metal:field>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:field define-macro="number">
|
||||||
|
<input tal:attributes="name string:query.field.${field/name};
|
||||||
|
value crit/comparisonValue|nothing" />
|
||||||
|
</metal:field>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:field define-macro="date">
|
||||||
|
<input dojoType="dijit.form.DateTextBox" style="width: 8em"
|
||||||
|
constraints="{datePattern: 'd.M.y',
|
||||||
|
min: '1980-01-01', max: '2020-12-31'}"
|
||||||
|
tal:attributes="name string:${field/name};
|
||||||
|
value crit/comparisonValue|nothing" />
|
||||||
|
</metal:field>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:field define-macro="selection">
|
||||||
|
<metal:use use-macro="item/report_macros/textline" />
|
||||||
|
</metal:field>
|
||||||
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -134,7 +134,7 @@ class ResultsView(NodeView):
|
||||||
|
|
||||||
|
|
||||||
class ResultsConceptView(ConceptView):
|
class ResultsConceptView(ConceptView):
|
||||||
""" View on a concept using a report.
|
""" View on a concept using the results of a report.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
reportName = None # define in subclass if applicable
|
reportName = None # define in subclass if applicable
|
||||||
|
@ -170,12 +170,11 @@ class ResultsConceptView(ConceptView):
|
||||||
reportType = self.reportType or self.report.reportType
|
reportType = self.reportType or self.report.reportType
|
||||||
ri = component.getAdapter(self.report, IReportInstance,
|
ri = component.getAdapter(self.report, IReportInstance,
|
||||||
name=reportType)
|
name=reportType)
|
||||||
#ri.view = self.nodeView
|
|
||||||
ri.view = self
|
ri.view = self
|
||||||
return ri
|
return ri
|
||||||
|
|
||||||
def results(self):
|
def results(self):
|
||||||
return self.reportInstance.getResults(dict(tasks=util.getUidForObject(self.context)))
|
return self.reportInstance.getResults()
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def displayedColumns(self):
|
def displayedColumns(self):
|
||||||
|
@ -183,3 +182,24 @@ class ResultsConceptView(ConceptView):
|
||||||
|
|
||||||
def getColumnRenderer(self, col):
|
def getColumnRenderer(self, col):
|
||||||
return self.result_macros[col.renderer]
|
return self.result_macros[col.renderer]
|
||||||
|
|
||||||
|
|
||||||
|
class ReportConceptView(ResultsConceptView, ReportView):
|
||||||
|
""" View on a concept using a report.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setupController(self):
|
||||||
|
super(ReportConceptView, self).setupController()
|
||||||
|
self.registerDojoDateWidget()
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def macro(self):
|
||||||
|
return self.report_macros['main']
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def queryFields(self):
|
||||||
|
ri = self.reportInstance
|
||||||
|
qf = ri.getAllQueryFields()
|
||||||
|
if ri.userSettings:
|
||||||
|
return [f for f in qf if f in ri.userSettings]
|
||||||
|
return qf
|
||||||
|
|
|
@ -35,7 +35,8 @@
|
||||||
i18n:translate="" />
|
i18n:translate="" />
|
||||||
</tr>
|
</tr>
|
||||||
<tr tal:repeat="row results">
|
<tr tal:repeat="row results">
|
||||||
<td tal:repeat="col results/displayedColumns">
|
<td tal:repeat="col results/displayedColumns"
|
||||||
|
tal:attributes="class col/cssClass">
|
||||||
<metal:column use-macro="python:
|
<metal:column use-macro="python:
|
||||||
reportView.getColumnRenderer(col)" />
|
reportView.getColumnRenderer(col)" />
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -25,7 +25,8 @@ from zope import component
|
||||||
from zope.i18n.locales import locales
|
from zope.i18n.locales import locales
|
||||||
from zope.schema.interfaces import IVocabularyFactory, IContextSourceBinder
|
from zope.schema.interfaces import IVocabularyFactory, IContextSourceBinder
|
||||||
|
|
||||||
from cybertools.composer.report.field import Field, TableCellStyle
|
from cybertools.composer.report.field import Field as BaseField
|
||||||
|
from cybertools.composer.report.field import TableCellStyle
|
||||||
from cybertools.composer.report.result import ResultSet
|
from cybertools.composer.report.result import ResultSet
|
||||||
from cybertools.util.date import timeStamp2Date
|
from cybertools.util.date import timeStamp2Date
|
||||||
from loops.common import baseObject
|
from loops.common import baseObject
|
||||||
|
@ -33,6 +34,12 @@ from loops.expert.report import ReportInstance
|
||||||
from loops import util
|
from loops import util
|
||||||
|
|
||||||
|
|
||||||
|
class Field(BaseField):
|
||||||
|
|
||||||
|
def getSelectValue(self, row):
|
||||||
|
return self.getRawValue(row)
|
||||||
|
|
||||||
|
|
||||||
class TextField(Field):
|
class TextField(Field):
|
||||||
|
|
||||||
format = 'text/restructured'
|
format = 'text/restructured'
|
||||||
|
|
|
@ -212,25 +212,23 @@ The executable report is a report instance that is an adapter to the
|
||||||
... provides=IReportInstance,
|
... provides=IReportInstance,
|
||||||
... name='work_report')
|
... name='work_report')
|
||||||
|
|
||||||
The user interface to the report is a report view, the results are presented
|
The user interface is a ReportConceptView subclass that is directly associated with the task.
|
||||||
in a results view.
|
|
||||||
|
|
||||||
>>> from loops.view import Node
|
>>> from loops.organize.work.report import WorkStatementView
|
||||||
>>> reportNode = addAndConfigureObject(home, Node, 'report',
|
>>> reportView = WorkStatementView(task01, TestRequest())
|
||||||
... title=u'Report', target=workStatement)
|
>>> reportView.nodeView = nodeView
|
||||||
>>> from loops.expert.browser.report import ReportView, ResultsView
|
|
||||||
>>> resultsView = ResultsView(reportNode, TestRequest())
|
|
||||||
|
|
||||||
>>> results = resultsView.results()#.getResult()
|
>>> results = reportView.results()
|
||||||
>>> len(list(results))
|
>>> len(list(results))
|
||||||
1
|
1
|
||||||
|
|
||||||
>>> for row in results:
|
>>> for row in results:
|
||||||
... for col in resultsView.displayedColumns:
|
... for col in reportView.displayedColumns:
|
||||||
... print col.getDisplayValue(row),
|
... print col.getDisplayValue(row),
|
||||||
... print
|
... print
|
||||||
08/12/28 19:00 20:15
|
08/12/28 19:00 20:15
|
||||||
{'url': '.../home/report/.36', 'title': u'loops Development'}
|
{'url': '.../home/.36', 'title': u'loops Development'}
|
||||||
{'url': '.../home/report/.33', 'title': u'john'} 01:15 00:15 finished
|
{'url': '.../home/.33', 'title': u'john'} 01:15 00:15 finished
|
||||||
|
|
||||||
>>> results.totals.data
|
>>> results.totals.data
|
||||||
{'effort': 900}
|
{'effort': 900}
|
||||||
|
@ -266,18 +264,17 @@ report is available.
|
||||||
... reportType='meeting_minutes')
|
... reportType='meeting_minutes')
|
||||||
>>> meetingMinutes.assignChild(tEvent, hasReport)
|
>>> meetingMinutes.assignChild(tEvent, hasReport)
|
||||||
|
|
||||||
We can now access the report using a results view.
|
We can now access the report using a corresponding report-based view.
|
||||||
|
|
||||||
>>> from loops.util import getUidForObject
|
>>> from loops.organize.work.meeting import MeetingMinutes
|
||||||
>>> input = dict(tasks=getUidForObject(ev01))
|
>>> reportView = MeetingMinutes(ev01, TestRequest())
|
||||||
>>> resultsView = ResultsView(home, TestRequest(form=input))
|
>>> reportView.nodeView = nodeView
|
||||||
>>> resultsView.virtualTargetObject = meetingMinutes
|
|
||||||
|
|
||||||
>>> results = resultsView.results()
|
>>> results = reportView.results()
|
||||||
>>> len(list(results))
|
>>> len(list(results))
|
||||||
1
|
1
|
||||||
>>> for row in results:
|
>>> for row in results:
|
||||||
... for col in resultsView.displayedColumns:
|
... for col in reportView.displayedColumns:
|
||||||
... print col.getDisplayValue(row),
|
... print col.getDisplayValue(row),
|
||||||
... print
|
... print
|
||||||
{'url': 'http://127.0.0.1/loops/views/home/.36', 'title': u'loops Development'}
|
{'url': 'http://127.0.0.1/loops/views/home/.36', 'title': u'loops Development'}
|
||||||
|
|
|
@ -75,10 +75,11 @@
|
||||||
|
|
||||||
<!-- reporting -->
|
<!-- reporting -->
|
||||||
|
|
||||||
<zope:adapter factory="loops.organize.work.report.WorkReportInstance"
|
<zope:adapter
|
||||||
name="work_report"
|
name="work_report"
|
||||||
provides="loops.expert.report.IReportInstance"
|
factory="loops.organize.work.report.WorkReportInstance"
|
||||||
trusted="True" />
|
provides="loops.expert.report.IReportInstance"
|
||||||
|
trusted="True" />
|
||||||
<zope:class class="loops.organize.work.report.WorkReportInstance">
|
<zope:class class="loops.organize.work.report.WorkReportInstance">
|
||||||
<require permission="zope.View"
|
<require permission="zope.View"
|
||||||
interface="loops.expert.report.IReportInstance" />
|
interface="loops.expert.report.IReportInstance" />
|
||||||
|
@ -86,10 +87,17 @@
|
||||||
set_schema="loops.expert.report.IReportInstance" />
|
set_schema="loops.expert.report.IReportInstance" />
|
||||||
</zope:class>
|
</zope:class>
|
||||||
|
|
||||||
<zope:adapter factory="loops.organize.work.report.MeetingMinutes"
|
<browser:page
|
||||||
name="meeting_minutes"
|
name="work.html"
|
||||||
provides="loops.expert.report.IReportInstance"
|
for="loops.organize.interfaces.ITask"
|
||||||
trusted="True" />
|
class="loops.organize.work.report.WorkStatementView"
|
||||||
|
permission="zope.View" />
|
||||||
|
|
||||||
|
<zope:adapter
|
||||||
|
name="meeting_minutes"
|
||||||
|
factory="loops.organize.work.report.MeetingMinutes"
|
||||||
|
provides="loops.expert.report.IReportInstance"
|
||||||
|
trusted="True" />
|
||||||
<zope:class class="loops.organize.work.report.MeetingMinutes">
|
<zope:class class="loops.organize.work.report.MeetingMinutes">
|
||||||
<require permission="zope.View"
|
<require permission="zope.View"
|
||||||
interface="loops.expert.report.IReportInstance" />
|
interface="loops.expert.report.IReportInstance" />
|
||||||
|
|
|
@ -26,20 +26,32 @@ from zope.component import adapter
|
||||||
|
|
||||||
from cybertools.composer.report.base import Report
|
from cybertools.composer.report.base import Report
|
||||||
from cybertools.composer.report.base import LeafQueryCriteria, CompoundQueryCriteria
|
from cybertools.composer.report.base import LeafQueryCriteria, CompoundQueryCriteria
|
||||||
from cybertools.composer.report.field import Field, CalculatedField
|
from cybertools.composer.report.field import CalculatedField
|
||||||
from cybertools.composer.report.result import ResultSet, Row as BaseRow
|
from cybertools.composer.report.result import ResultSet, Row as BaseRow
|
||||||
from cybertools.organize.interfaces import IWorkItems
|
from cybertools.organize.interfaces import IWorkItems
|
||||||
from cybertools.util.date import timeStamp2Date
|
from cybertools.util.date import timeStamp2Date, timeStamp2ISO
|
||||||
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, DateField, TextField, UrlField
|
from loops.expert.browser.report import ReportConceptView
|
||||||
|
from loops.expert.field import Field, TargetField, DateField, \
|
||||||
|
TextField, UrlField
|
||||||
from loops.expert.field import SubReport, SubReportField
|
from loops.expert.field import SubReport, SubReportField
|
||||||
from loops.expert.report import ReportInstance
|
from loops.expert.report import ReportInstance
|
||||||
from loops import util
|
from loops import util
|
||||||
|
|
||||||
|
|
||||||
|
# reporting views
|
||||||
|
|
||||||
|
class WorkStatementView(ReportConceptView):
|
||||||
|
|
||||||
|
reportName = 'work_statement'
|
||||||
|
|
||||||
|
|
||||||
|
# fields
|
||||||
|
|
||||||
class StateField(Field):
|
class StateField(Field):
|
||||||
|
|
||||||
def getDisplayValue(self, row):
|
def getDisplayValue(self, row):
|
||||||
value = self.getValue(row)
|
value = self.getValue(row)
|
||||||
return util._(value)
|
return util._(value)
|
||||||
|
@ -47,9 +59,10 @@ class StateField(Field):
|
||||||
|
|
||||||
class TrackDateField(Field):
|
class TrackDateField(Field):
|
||||||
|
|
||||||
|
fieldType = 'date'
|
||||||
part = 'date'
|
part = 'date'
|
||||||
format = 'short'
|
format = 'short'
|
||||||
renderer = 'right'
|
cssClass = 'right'
|
||||||
|
|
||||||
def getValue(self, row):
|
def getValue(self, row):
|
||||||
value = self.getRawValue(row)
|
value = self.getRawValue(row)
|
||||||
|
@ -65,6 +78,12 @@ class TrackDateField(Field):
|
||||||
view.languageInfo.language)
|
view.languageInfo.language)
|
||||||
return u''
|
return u''
|
||||||
|
|
||||||
|
def getSelectValue(self, row):
|
||||||
|
value = self.getRawValue(row)
|
||||||
|
if not value:
|
||||||
|
return ''
|
||||||
|
return timeStamp2ISO(value)[:10]
|
||||||
|
|
||||||
|
|
||||||
class TrackTimeField(TrackDateField):
|
class TrackTimeField(TrackDateField):
|
||||||
|
|
||||||
|
@ -73,7 +92,7 @@ class TrackTimeField(TrackDateField):
|
||||||
|
|
||||||
class DurationField(Field):
|
class DurationField(Field):
|
||||||
|
|
||||||
renderer = 'right'
|
cssClass = 'right'
|
||||||
|
|
||||||
def getValue(self, row):
|
def getValue(self, row):
|
||||||
value = self.getRawValue(row)
|
value = self.getRawValue(row)
|
||||||
|
@ -100,11 +119,15 @@ tasks = Field('tasks', u'Tasks',
|
||||||
|
|
||||||
# work report fields
|
# work report fields
|
||||||
|
|
||||||
dayFrom = Field('dayFrom', u'Start Day',
|
dayFrom = TrackDateField('dayFrom', u'Start Day',
|
||||||
description=u'The first day from which to select work.',
|
description=u'The first day from which to select work.',
|
||||||
|
fieldType='date',
|
||||||
|
operator=u'gt',
|
||||||
executionSteps=['query'])
|
executionSteps=['query'])
|
||||||
dayTo = Field('dayTo', u'End Day',
|
dayTo = TrackDateField('dayTo', u'End Day',
|
||||||
description=u'The last day until which to select work.',
|
description=u'The last day until which to select work.',
|
||||||
|
fieldType='date',
|
||||||
|
operator=u'le',
|
||||||
executionSteps=['query'])
|
executionSteps=['query'])
|
||||||
day = TrackDateField('day', u'Day',
|
day = TrackDateField('day', u'Day',
|
||||||
description=u'The day the work was done.',
|
description=u'The day the work was done.',
|
||||||
|
@ -121,6 +144,7 @@ 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.',
|
||||||
|
fieldType='selection',
|
||||||
executionSteps=['query', 'sort', 'output'])
|
executionSteps=['query', 'sort', 'output'])
|
||||||
workTitle = Field('title', u'Title',
|
workTitle = Field('title', u'Title',
|
||||||
description=u'The short description of the work.',
|
description=u'The short description of the work.',
|
||||||
|
@ -167,7 +191,8 @@ class WorkRow(BaseRow):
|
||||||
value = self.getDuration(attr)
|
value = self.getDuration(attr)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
attributeHandlers = dict(day=getDay, duration=getDuration, effort=getEffort)
|
attributeHandlers = dict(day=getDay, dayFrom=getDay, dayTo=getDay,
|
||||||
|
duration=getDuration, effort=getEffort)
|
||||||
|
|
||||||
|
|
||||||
class WorkReportInstance(ReportInstance):
|
class WorkReportInstance(ReportInstance):
|
||||||
|
@ -178,9 +203,11 @@ class WorkReportInstance(ReportInstance):
|
||||||
rowFactory = WorkRow
|
rowFactory = WorkRow
|
||||||
|
|
||||||
fields = Jeep((dayFrom, dayTo, tasks,
|
fields = Jeep((dayFrom, dayTo, tasks,
|
||||||
day, timeStart, timeEnd, task, party, workTitle, #description,
|
day, timeStart, timeEnd, task, party, workTitle,
|
||||||
|
#description,
|
||||||
duration, effort, state))
|
duration, effort, state))
|
||||||
|
|
||||||
|
userSettings = (dayFrom, dayTo, party)
|
||||||
defaultOutputFields = fields
|
defaultOutputFields = fields
|
||||||
defaultSortCriteria = (day, timeStart,)
|
defaultSortCriteria = (day, timeStart,)
|
||||||
states = ('done', 'done_x', 'finished')
|
states = ('done', 'done_x', 'finished')
|
||||||
|
@ -192,12 +219,13 @@ class WorkReportInstance(ReportInstance):
|
||||||
crit = self.context.queryCriteria or []
|
crit = self.context.queryCriteria or []
|
||||||
if not crit and 'tasks' not in form:
|
if not crit and 'tasks' not in form:
|
||||||
f = self.fields['tasks']
|
f = self.fields['tasks']
|
||||||
tasks = baseObject(self.context).getChildren([self.hasReportPredicate])
|
tasks = [self.view.context]
|
||||||
tasks = [util.getUidForObject(task) for task in tasks]
|
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():
|
for f in self.getAllQueryFields():
|
||||||
if f.name in form:
|
if f.name in form:
|
||||||
crit.append(LeafQueryCriteria(f.name, f.operator, form[f.name], f))
|
crit.append(
|
||||||
|
LeafQueryCriteria(f.name, f.operator, form[f.name], f))
|
||||||
return CompoundQueryCriteria(crit)
|
return CompoundQueryCriteria(crit)
|
||||||
|
|
||||||
def selectObjects(self, parts):
|
def selectObjects(self, parts):
|
||||||
|
@ -229,8 +257,9 @@ class WorkReportInstance(ReportInstance):
|
||||||
# TODO: take states from parts
|
# TODO: take states from parts
|
||||||
kw = dict(task=util.getUidForObject(baseObject(task)),
|
kw = dict(task=util.getUidForObject(baseObject(task)),
|
||||||
state=self.states)
|
state=self.states)
|
||||||
if 'userName' in parts:
|
userNameCrit = parts.get('userName')
|
||||||
kw['userName'] = parts['userName'].comparisonValue
|
if userNameCrit and userNameCrit.comparisonValue:
|
||||||
|
kw['userName'] = userNameCrit.comparisonValue
|
||||||
wi = self.workItems
|
wi = self.workItems
|
||||||
return wi.query(**kw)
|
return wi.query(**kw)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue