From 758171f1586bae8265015356f65ad385fd1c4e84 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 11 Nov 2011 15:18:03 +0100 Subject: [PATCH] work in progress: configurable reports - basic interfaces and classes, example report (work_report) --- expert/browser/report.pt | 22 ++++++ expert/browser/report.py | 107 ++++++++++++++++++++++++++++ expert/browser/results.pt | 22 ++++++ expert/report.py | 143 ++++++++++++++++++++++++++++++++++++++ organize/work/report.py | 86 +++++++++++++++++++++++ organize/work/results.pt | 61 ++++++++++++++++ 6 files changed, 441 insertions(+) create mode 100644 expert/browser/report.pt create mode 100644 expert/browser/report.py create mode 100644 expert/browser/results.pt create mode 100644 expert/report.py create mode 100644 organize/work/report.py create mode 100644 organize/work/results.pt diff --git a/expert/browser/report.pt b/expert/browser/report.pt new file mode 100644 index 0000000..e1237dd --- /dev/null +++ b/expert/browser/report.pt @@ -0,0 +1,22 @@ + + + +
+
+ +
+ + +
+ +
+
+
+
+ + + diff --git a/expert/browser/report.py b/expert/browser/report.py new file mode 100644 index 0000000..b8f5f9b --- /dev/null +++ b/expert/browser/report.py @@ -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() + diff --git a/expert/browser/results.pt b/expert/browser/results.pt new file mode 100644 index 0000000..9c892ad --- /dev/null +++ b/expert/browser/results.pt @@ -0,0 +1,22 @@ + + + +
+
+ +
+
+ Back to the report definition +
+
+
+ + +
+ Default Results Listing +
+ + + diff --git a/expert/report.py b/expert/report.py new file mode 100644 index 0000000..fdcef10 --- /dev/null +++ b/expert/report.py @@ -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' + diff --git a/organize/work/report.py b/organize/work/report.py new file mode 100644 index 0000000..71ec849 --- /dev/null +++ b/organize/work/report.py @@ -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 [] + diff --git a/organize/work/results.pt b/organize/work/results.pt new file mode 100644 index 0000000..0d1cec0 --- /dev/null +++ b/organize/work/results.pt @@ -0,0 +1,61 @@ + + + +
+

Work Items

+ + + + + + + + +
+
+
+ + +
+ Work Items Listing + + + + + + + + + + + + + + + + + + + +
Task
2009-01
2007-03-3017:3020:002:30 + Task + JohnTitle
+
+ + +