work in progress: configurable reports - basic interfaces and classes, example report (work_report)
This commit is contained in:
parent
f072066b3a
commit
758171f158
6 changed files with 441 additions and 0 deletions
22
expert/browser/report.pt
Normal file
22
expert/browser/report.pt
Normal 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
107
expert/browser/report.py
Normal 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
22
expert/browser/results.pt
Normal 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
143
expert/report.py
Normal 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
86
organize/work/report.py
Normal 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
61
organize/work/results.pt
Normal 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>
|
Loading…
Add table
Reference in a new issue