## $Id$ ## ## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann ## 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 ## """Components for report generation. """ import grok import zc.async.interfaces from persistent.list import PersistentList from zope.component import getUtility, getUtilitiesFor from zope.component.hooks import setSite from zope.interface import implementer from zope.interface import Interface, Attribute from waeup.kofa.async import AsyncJob from waeup.kofa.interfaces import ( IJobManager, JOB_STATUS_MAP,) from waeup.kofa.utils.helpers import now class IReport(Interface): """A report. """ creation_dt = Attribute( """Datetime when a report was created. The datetime should """ """reflect the point of time when the data was fetched, """ """not when any output was created.""") def create_pdf(): """Generate a PDF copy. """ class IReportGenerator(Interface): """A report generator. """ def generate(site): """Generate a report. """ return Report() class IReportJob(zc.async.interfaces.IJob): def __init__(site, exporter_name): pass class IReportJobContainer(Interface): """A component that contains (maybe virtually) report jobs. """ def start_report_job(report_generator_name, user_id, args=[], kw={}): """Start asynchronous report job. `report_generator_name` is the name of a report generator utility to be used. `user_id` is the ID of the user that triggers the report generation. `args` and `kw` args and keywords passed to the generators `generate()` method. The job_id is stored along with exporter name and user id in a persistent list. Returns the job ID of the job started. """ def get_running_report_jobs(user_id=None): """Get report jobs for user with `user_id` as list of tuples. Each tuples holds ``, , `` in that order. The ```` is the utility name of the used report generator. If `user_id` is ``None``, all running report jobs are returned. """ def get_report_jobs_status(user_id=None): """Get running/completed report jobs for `user_id` as list of tuples. Each tuple holds ``, , `` in that order, where ```` and ```` are translated strings representing the status of the job and the human readable title of the report generator used. """ def delete_report_entry(entry): """Delete the report job denoted by `entry`. Removes `entry` from the local `running_report_jobs` list and also removes the regarding job via the local job manager. `entry` is a tuple ``(, , )`` as created by :meth:`start_report_job` or returned by :meth:`get_running_report_jobs`. """ def report_entry_from_job_id(job_id): """Get entry tuple for `job_id`. Returns ``None`` if no such entry can be found. """ def get_generators(): """Get available report generators. Returns an iterator of tuples ```` with ``NAME`` being the name under which the respective generator was registered. """ for name, util in getUtilitiesFor(IReportGenerator): yield name, util pass @implementer(IReport) class Report(object): creation_dt = None def __init__(self): self.creation_dt = now() def create_pdf(self): raise NotImplemented @implementer(IReportGenerator) class ReportGenerator(object): def generate(self, site): result = Report() return result def report_job(site, generator_name, args=[], kw={}): """Get a generator and perform report creation. `site` is the site for which the report should be created. `generator_name` the global utility name under which the desired generator is registered. `args` and `kw` Arguments and keywords to be passed to the `generate()` method of the desired generator. While `args` should be a list, `kw` should be a dictionary. """ setSite(site) generator = getUtility(IReportGenerator, name=generator_name) report = generator.generate(site, *args, **kw) return report @implementer(IReportJob) class AsyncReportJob(AsyncJob): """An IJob that creates reports. `AsyncReportJob` instances are regular `AsyncJob` instances with a different constructor API. Instead of a callable to execute, you must pass a `site`, some `generator_name`, and additional args and keywords to create a report. The real work is done when an instance of this class is put into a queue. See :mod:`waeup.kofa.async` to learn more about asynchronous jobs. The `generator_name` must be the name under which an IReportGenerator utility was registered with the ZCA. The `site` must be a valid site or ``None``. The result of an `AsyncReportJob` is an IReport object. """ def __init__(self, site, generator_name, args=[], kw={}): super(AsyncReportJob, self).__init__( report_job, site, generator_name, *args, **kw) @implementer(IReportJobContainer) class ReportJobContainer(object): """A mix-in that provides functionality for asynchronous report jobs. """ running_report_jobs = PersistentList() def start_report_job(self, generator_name, user_id, args=[], kw={}): """Start asynchronous export job. `generator_name` is the name of a report generator utility to be used. `user_id` is the ID of the user that triggers the report generation. `args` and `kw` args and keywords passed to the generators `generate()` method. The job_id is stored along with exporter name and user id in a persistent list. Returns the job ID of the job started. """ site = grok.getSite() manager = getUtility(IJobManager) job = AsyncReportJob(site, generator_name, args=args, kw=kw) job_id = manager.put(job) # Make sure that the persisted list is stored in ZODB self.running_report_jobs = PersistentList(self.running_report_jobs) self.running_report_jobs.append((job_id, generator_name, user_id)) return job_id def get_running_report_jobs(self, user_id=None): """Get report jobs for user with `user_id` as list of tuples. Each tuples holds ``, , `` in that order. The ```` is the utility name of the used report generator. If `user_id` is ``None``, all running report jobs are returned. """ entries = [] to_delete = [] manager = getUtility(IJobManager) for entry in self.running_report_jobs: if user_id is not None and entry[2] != user_id: continue if manager.get(entry[0]) is None: to_delete.append(entry) continue entries.append(entry) if to_delete: self.running_report_jobs = PersistentList( [x for x in self.running_report_jobs if x not in to_delete]) return entries def get_report_jobs_status(self, user_id=None): """Get running/completed report jobs for `user_id` as list of tuples. Each tuple holds ``, , `` in that order, where ```` and ```` are translated strings representing the status of the job and the human readable title of the report generator used. """ entries = self.get_running_report_jobs(user_id) result = [] manager = getUtility(IJobManager) for entry in entries: job = manager.get(entry[0]) if job is None: continue status, status_translated = JOB_STATUS_MAP[job.status] generator = getUtility(IReportGenerator, name=entry[1]) generator_name = getattr(generator, 'title', 'unnamed') result.append((status, status_translated, generator_name)) return result def delete_report_entry(self, entry): """Delete the report job denoted by `entry`. Removes `entry` from the local `running_report_jobs` list and also removes the regarding job via the local job manager. `entry` is a tuple ``(, , )`` as created by :meth:`start_report_job` or returned by :meth:`get_running_report_jobs`. """ manager = getUtility(IJobManager) manager.remove(entry[0], self) new_entries = [x for x in self.running_report_jobs if x != entry] self.running_report_jobs = PersistentList(new_entries) return def report_entry_from_job_id(self, job_id): """Get entry tuple for `job_id`. Returns ``None`` if no such entry can be found. """ for entry in self.running_report_jobs: if entry[0] == job_id: return entry return None