work in progress: configurable reports - basic interfaces and classes, example report (work_report)

This commit is contained in:
Helmut Merz 2011-11-11 15:18:03 +01:00
parent f072066b3a
commit 758171f158
6 changed files with 441 additions and 0 deletions

22
expert/browser/report.pt Normal file
View file

@ -0,0 +1,22 @@
<html i18n:domain="loops">
<div metal:define-macro="main">
<div tal:attributes="class string:content-$level;">
<metal:block use-macro="view/concept_macros/concepttitle" />
<form method="post" name="report_data" action="results.html">
<tal:hidden define="params item/dynamicParams">
<input type="hidden"
tal:repeat="name params"
tal:attributes="name name;
value params/?name" /></tal:hidden>
<div metal:define-macro="buttons">
<input type="submit" name="report_execute" value="Execute Report"
i18n:attributes="value" />
</div>
</form>
</div>
</div>
</html>

107
expert/browser/report.py Normal file
View file

@ -0,0 +1,107 @@
#
# 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
#
"""
View classes for reporting.
"""
from urllib import urlencode
from zope import interface, component
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope.traversing.api import getName, getParent
from cybertools.browser.form import FormController
from loops.browser.concept import ConceptView
from loops.browser.node import NodeView
from loops.common import adapted, AdapterBase
from loops.expert.report import IReportInstance
from loops.organize.personal.browser.filter import FilterView
from loops.security.common import canWriteObject, checkPermission
from loops import util
from loops.util import _
report_template = ViewPageTemplateFile('report.pt')
results_template = ViewPageTemplateFile('results.pt')
class ReportView(ConceptView):
@Lazy
def report_macros(self):
return self.controller.getTemplateMacros('report', report_template)
@Lazy
def macro(self):
return self.report_macros['main']
@Lazy
def dynamicParams(self):
return self.request.form
class ResultsView(NodeView):
@Lazy
def result_macros(self):
return self.controller.getTemplateMacros('results', results_template)
@Lazy
def macro(self):
return self.result_macros['main']
@Lazy
def item(self):
return self
@Lazy
def params(self):
params = dict(self.request.form)
if 'report_execute' in params:
del params['report_execute']
return params
@Lazy
def report(self):
return adapted(self.virtualTargetObject)
@Lazy
def reportInstance(self):
instance = component.getAdapter(self.report, IReportInstance,
name=self.report.reportType)
instance.request = self.request
return instance
#@Lazy
def results(self):
return self.reportInstance.getResults(self.params)
@Lazy
def resultsRenderer(self):
return self.reportInstance.getResultsRenderer('results', self.result_macros)
@Lazy
def reportUrl(self):
url = self.virtualTargetUrl
return '?'.join((url, urlencode(self.params)))
@Lazy
def displayedColumns(self):
return self.reportInstance.getActiveOutputFields()

22
expert/browser/results.pt Normal file
View file

@ -0,0 +1,22 @@
<html i18n:domain="loops">
<div metal:define-macro="main"
tal:define="item nocall:item/virtualTarget;
report view/reportInstance">
<div tal:attributes="class string:content-$level;">
<metal:block use-macro="view/concept_macros/concepttitle" />
</div>
<div>
<a tal:attributes="href view/reportUrl">Back to the report definition</a>
</div>
<div metal:use-macro="view/resultsRenderer" />
</div>
<div metal:define-macro="results">
Default Results Listing
</div>
</html>

143
expert/report.py Normal file
View file

@ -0,0 +1,143 @@
#
# 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
#
"""
Report type, report concept adapter, and other reporting stuff.
"""
from zope import schema, component
from zope.component import adapts
from zope.interface import Interface, Attribute, implements
from zope.cachedescriptors.property import Lazy
from zope.security.proxy import removeSecurityProxy
from cybertools.composer.report.base import Report as BaseReport
from cybertools.composer.report.base import LeafQueryCriteria, CompoundQueryCriteria
from cybertools.composer.report.interfaces import IReport as IBaseReport
from cybertools.composer.report.result import ResultSet, Row
from cybertools.util.jeep import Jeep
from loops.common import AdapterBase
from loops.interfaces import ILoopsAdapter
from loops.type import TypeInterfaceSourceList
from loops import util
from loops.util import _
# interfaces
class IReport(ILoopsAdapter):
""" The report adapter for the persistent object (concept) that stores
the report in the concept map.
"""
reportType = schema.Choice(
title=_(u'Report Type'),
description=_(u'The type of the report.'),
default=None,
source='loops.expert.reportTypeSource',
required=True)
class IReportSchema(IBaseReport, IReport):
""" All report attributes - use for security declarations.
"""
class IReportInstance(Interface):
""" The report-type-specific object (an adapter on the report) that
does the real report execution stuff.
"""
label = Attribute('The user-friendly label of the report type specified '
'by this instance class.')
# report concept adapter and instances
class Report(AdapterBase):
implements(IReport)
_contextAttributes = list(IReport)
TypeInterfaceSourceList.typeInterfaces += (IReport,)
class ReportInstance(BaseReport):
implements(IReportInstance)
adapts(IReport)
rowFactory = Row
def __init__(self, context):
self.context = context
def getResultsRenderer(self, name, macros):
return macros[name]
def getResults(self, dynaParams=None):
crit = self.queryCriteria
if crit is None:
return []
for k, v in dynaParams.items():
if k in crit.parts.keys():
crit.parts[k].value = v
parts = Jeep(crit.parts)
result = list(self.selectObjects(parts)) # may modify parts
qc = CompoundQueryCriteria(parts)
return ResultSet(self, result, rowFactory=self.rowFactory,
sortCriteria=self.getSortCriteria(), queryCriteria=qc)
def selectObjects(self, parts):
return []
class ReportTypeSourceList(object):
implements(schema.interfaces.IIterableSource)
def __init__(self, context):
self.context = context
def __iter__(self):
return iter(self.reportTypes)
def __contains__(self, value):
return value in [item[0] for item in self]
@Lazy
def reportTypes(self):
result = []
for item in component.getAdapters([self.context], IReportInstance):
name, adapter = item
adapter = removeSecurityProxy(adapter)
label = getattr(adapter, 'label', name)
result.append((name, label,))
return result
def __len__(self):
return len(self.reportTypes)
# default concept report
class DefaultConceptReportInstance(ReportInstance):
label = u'Default Concept Report'

86
organize/work/report.py Normal file
View file

@ -0,0 +1,86 @@
#
# 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
#
"""
Work report definitions.
"""
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope.component import adapter
from cybertools.composer.report.base import Report
from cybertools.composer.report.base import LeafQueryCriteria, CompoundQueryCriteria
from cybertools.composer.report.field import Field
from cybertools.composer.report.result import ResultSet, Row as BaseRow
from cybertools.util.jeep import Jeep
from loops.expert.report import ReportInstance
results_template = ViewPageTemplateFile('results.pt')
task = Field('task', u'Task',
description=u'The task to which work items belong.',
executionSteps=['query', 'output', 'sort'])
work = Field('work', u'Work',
description=u'The short description of the work.',
executionSteps=['output'])
workDescription = Field('workDescription', u'Work Description',
description=u'The long description of the work.',
executionSteps=['output'])
day = Field('day', u'Day',
description=u'The day the work was done.',
executionSteps=['output', 'sort'])
dayFrom = Field('dayFrom', u'Start Day',
description=u'The first day from which to select work.',
executionSteps=['query'])
dayTo = Field('dayTo', u'End Day',
description=u'The last day until which to select work.',
executionSteps=['query'])
class WorkRow(BaseRow):
pass
class WorkReportInstance(ReportInstance):
type = "deliverables"
label = u'Work Report'
rowFactory = WorkRow
fields = Jeep((day, dayFrom, dayTo, task, work, workDescription))
defaultOutputFields = fields
@Lazy
def queryCriteria(self):
# TODO: take from persistent report where appropriate
crit = [LeafQueryCriteria(f.name, f.operator, None, f)
for f in self.getAllQueryFields()]
return CompoundQueryCriteria(crit)
def getResultsRenderer(self, name, defaultMacros):
return results_template.macros[name]
def selectObjects(self, parts):
task = parts.get('task')
if not task:
return []
return []

61
organize/work/results.pt Normal file
View file

@ -0,0 +1,61 @@
<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>