source: main/waeup.kofa/trunk/src/waeup/kofa/reports.py @ 9373

Last change on this file since 9373 was 9344, checked in by uli, 12 years ago

Base components for reports (non-UI).

File size: 9.9 KB
Line 
1## $Id$
2##
3## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""Components for report generation.
19"""
20import grok
21import zc.async.interfaces
22from persistent.list import PersistentList
23from zope.component import getUtility, getUtilitiesFor
24from zope.component.hooks import setSite
25from zope.interface import implementer
26from zope.interface import Interface, Attribute
27from waeup.kofa.async import AsyncJob
28from waeup.kofa.interfaces import (
29    IJobManager, JOB_STATUS_MAP,)
30from waeup.kofa.utils.helpers import now
31
32class IReport(Interface):
33    """A report.
34    """
35    creation_dt = Attribute(
36        """Datetime when a report was created. The datetime should """
37        """reflect the point of time when the data was fetched, """
38        """not when any output was created.""")
39
40    def create_pdf():
41        """Generate a PDF copy.
42        """
43
44class IReportGenerator(Interface):
45    """A report generator.
46    """
47    def generate(site):
48        """Generate a report.
49        """
50        return Report()
51
52class IReportJob(zc.async.interfaces.IJob):
53    def __init__(site, exporter_name):
54        pass
55
56class IReportJobContainer(Interface):
57    """A component that contains (maybe virtually) report jobs.
58    """
59    def start_report_job(report_generator_name, user_id, args=[], kw={}):
60        """Start asynchronous report job.
61
62        `report_generator_name`
63            is the name of a report generator utility to be used.
64
65        `user_id`
66            is the ID of the user that triggers the report generation.
67
68        `args` and `kw`
69            args and keywords passed to the generators `generate()`
70            method.
71
72        The job_id is stored along with exporter name and user id in a
73        persistent list.
74
75        Returns the job ID of the job started.
76        """
77
78    def get_running_report_jobs(user_id=None):
79        """Get report jobs for user with `user_id` as list of tuples.
80
81        Each tuples holds ``<job_id>, <generator_name>, <user_id>`` in
82        that order. The ``<generator_name>`` is the utility name of the
83        used report generator.
84
85        If `user_id` is ``None``, all running report jobs are returned.
86        """
87
88    def get_report_jobs_status(user_id=None):
89        """Get running/completed report jobs for `user_id` as list of tuples.
90
91        Each tuple holds ``<raw status>, <status translated>,
92        <generator title>`` in that order, where ``<status
93        translated>`` and ``<generator title>`` are translated strings
94        representing the status of the job and the human readable
95        title of the report generator used.
96        """
97
98    def delete_report_entry(entry):
99        """Delete the report job denoted by `entry`.
100
101        Removes `entry` from the local `running_report_jobs` list and
102        also removes the regarding job via the local job manager.
103
104        `entry` is a tuple ``(<job id>, <generator name>, <user id>)``
105        as created by :meth:`start_report_job` or returned by
106        :meth:`get_running_report_jobs`.
107        """
108
109    def report_entry_from_job_id(job_id):
110        """Get entry tuple for `job_id`.
111
112        Returns ``None`` if no such entry can be found.
113        """
114
115def get_generators():
116    """Get available report generators.
117
118    Returns an iterator of tuples ``<NAME, GENERATOR>`` with ``NAME``
119    being the name under which the respective generator was
120    registered.
121    """
122    for name, util in getUtilitiesFor(IReportGenerator):
123        yield name, util
124    pass
125
126@implementer(IReport)
127class Report(object):
128    creation_dt = None
129
130    def __init__(self):
131        self.creation_dt = now()
132
133    def create_pdf(self):
134        raise NotImplemented
135
136@implementer(IReportGenerator)
137class ReportGenerator(object):
138    def generate(self, site):
139        result = Report()
140        return result
141
142def report_job(site, generator_name, args=[], kw={}):
143    """Get a generator and perform report creation.
144
145    `site`
146        is the site for which the report should be created.
147    `generator_name`
148        the global utility name under which the desired generator is
149        registered.
150    `args` and `kw`
151        Arguments and keywords to be passed to the `generate()` method of
152        the desired generator. While `args` should be a list, `kw` should
153        be a dictionary.
154    """
155    setSite(site)
156    generator = getUtility(IReportGenerator, name=generator_name)
157    report = generator.generate(site, *args, **kw)
158    return report
159
160@implementer(IReportJob)
161class AsyncReportJob(AsyncJob):
162    """An IJob that creates reports.
163
164    `AsyncReportJob` instances are regular `AsyncJob` instances with a
165    different constructor API. Instead of a callable to execute, you
166    must pass a `site`, some `generator_name`, and additional args and
167    keywords to create a report.
168
169    The real work is done when an instance of this class is put into a
170    queue. See :mod:`waeup.kofa.async` to learn more about
171    asynchronous jobs.
172
173    The `generator_name` must be the name under which an IReportGenerator
174    utility was registered with the ZCA.
175
176    The `site` must be a valid site  or ``None``.
177
178    The result of an `AsyncReportJob` is an IReport object.
179    """
180    def __init__(self, site, generator_name, args=[], kw={}):
181        super(AsyncReportJob, self).__init__(
182            report_job, site, generator_name, *args, **kw)
183
184@implementer(IReportJobContainer)
185class ReportJobContainer(object):
186    """A mix-in that provides functionality for asynchronous report jobs.
187    """
188    running_report_jobs = PersistentList()
189
190    def start_report_job(self, generator_name, user_id, args=[], kw={}):
191        """Start asynchronous export job.
192
193        `generator_name`
194            is the name of a report generator utility to be used.
195
196        `user_id`
197            is the ID of the user that triggers the report generation.
198
199        `args` and `kw`
200            args and keywords passed to the generators `generate()`
201            method.
202
203        The job_id is stored along with exporter name and user id in a
204        persistent list.
205
206        Returns the job ID of the job started.
207        """
208        site = grok.getSite()
209        manager = getUtility(IJobManager)
210        job = AsyncReportJob(site, generator_name, args=args, kw=kw)
211        job_id = manager.put(job)
212        # Make sure that the persisted list is stored in ZODB
213        self.running_report_jobs = PersistentList(self.running_report_jobs)
214        self.running_report_jobs.append((job_id, generator_name, user_id))
215        return job_id
216
217    def get_running_report_jobs(self, user_id=None):
218        """Get report jobs for user with `user_id` as list of tuples.
219
220        Each tuples holds ``<job_id>, <generator_name>, <user_id>`` in
221        that order. The ``<generator_name>`` is the utility name of the
222        used report generator.
223
224        If `user_id` is ``None``, all running report jobs are returned.
225        """
226        entries = []
227        to_delete = []
228        manager = getUtility(IJobManager)
229        for entry in self.running_report_jobs:
230            if user_id is not None and entry[2] != user_id:
231                continue
232            if manager.get(entry[0]) is None:
233                to_delete.append(entry)
234                continue
235            entries.append(entry)
236        if to_delete:
237            self.running_report_jobs = PersistentList(
238                [x for x in self.running_report_jobs if x not in to_delete])
239        return entries
240
241    def get_report_jobs_status(self, user_id=None):
242        """Get running/completed report jobs for `user_id` as list of tuples.
243
244        Each tuple holds ``<raw status>, <status translated>,
245        <generator title>`` in that order, where ``<status
246        translated>`` and ``<generator title>`` are translated strings
247        representing the status of the job and the human readable
248        title of the report generator used.
249        """
250        entries = self.get_running_report_jobs(user_id)
251        result = []
252        manager = getUtility(IJobManager)
253        for entry in entries:
254            job = manager.get(entry[0])
255            if job is None:
256                continue
257            status, status_translated = JOB_STATUS_MAP[job.status]
258            generator = getUtility(IReportGenerator, name=entry[1])
259            generator_name = getattr(generator, 'title', 'unnamed')
260            result.append((status, status_translated, generator_name))
261        return result
262
263    def delete_report_entry(self, entry):
264        """Delete the report job denoted by `entry`.
265
266        Removes `entry` from the local `running_report_jobs` list and
267        also removes the regarding job via the local job manager.
268
269        `entry` is a tuple ``(<job id>, <generator name>, <user id>)``
270        as created by :meth:`start_report_job` or returned by
271        :meth:`get_running_report_jobs`.
272        """
273        manager = getUtility(IJobManager)
274        manager.remove(entry[0], self)
275        new_entries = [x for x in self.running_report_jobs
276                       if x != entry]
277        self.running_report_jobs = PersistentList(new_entries)
278        return
279
280    def report_entry_from_job_id(self, job_id):
281        """Get entry tuple for `job_id`.
282
283        Returns ``None`` if no such entry can be found.
284        """
285        for entry in self.running_report_jobs:
286            if entry[0] == job_id:
287                return entry
288        return None
Note: See TracBrowser for help on using the repository browser.