Source code for wuttaweb.views.reports

# -*- coding: utf-8; -*-
################################################################################
#
#  wuttaweb -- Web App for Wutta Framework
#  Copyright © 2024-2025 Lance Edgar
#
#  This file is part of Wutta Framework.
#
#  Wutta Framework 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 3 of the License, or (at your option) any
#  later version.
#
#  Wutta Framework 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
#  Wutta Framework.  If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Report Views
"""

import datetime
import logging
import os
import tempfile

import deform

from wuttaweb.views import MasterView


log = logging.getLogger(__name__)


[docs] class ReportView(MasterView): # pylint: disable=abstract-method """ Master view for :term:`reports <report>`; route prefix is ``reports``. Notable URLs provided by this class: * ``/reports/`` * ``/reports/XXX`` """ model_name = "report" model_title = "Report" model_key = "report_key" filterable = False sort_on_backend = False creatable = False editable = False deletable = False route_prefix = "reports" template_prefix = "/reports" grid_columns = [ "report_title", "help_text", "report_key", ] form_fields = [ "help_text", ] def __init__(self, request, context=None): super().__init__(request, context=context) self.report_handler = self.app.get_report_handler() def get_grid_data( # pylint: disable=empty-docstring self, columns=None, session=None ): """ """ data = [] for report in self.report_handler.get_reports().values(): data.append(self.normalize_report(report)) return data def normalize_report(self, report): # pylint: disable=empty-docstring """ """ return { "report_key": report.report_key, "report_title": report.report_title, "help_text": report.__doc__, } def configure_grid(self, grid): # pylint: disable=empty-docstring """ """ g = grid super().configure_grid(g) # report_key g.set_link("report_key") # report_title g.set_link("report_title") g.set_searchable("report_title") # help_text g.set_searchable("help_text") def get_instance( # pylint: disable=empty-docstring,arguments-differ,unused-argument self, **kwargs ): """ """ key = self.request.matchdict["report_key"] report = self.report_handler.get_report(key) if report: return self.normalize_report(report) raise self.notfound() def get_instance_title(self, instance): # pylint: disable=empty-docstring """ """ report = instance return report["report_title"]
[docs] def view(self): """ This lets user "view" the report but in this context that means showing them a form with report params, so they can run it. """ key = self.request.matchdict["report_key"] report = self.report_handler.get_report(key) normal = self.normalize_report(report) report_url = self.get_action_url("view", normal) form = self.make_model_form( normal, action_method="get", action_url=report_url, cancel_url=self.get_index_url(), show_button_reset=True, reset_url=report_url, button_label_submit="Run Report", button_icon_submit="arrow-circle-right", ) context = { "instance": normal, "report": report, "form": form, "xref_buttons": self.get_xref_buttons(report), } if self.request.GET: form.show_button_cancel = False context = self.run_report(report, context) return self.render_to_response("view", context)
def configure_form(self, form): # pylint: disable=empty-docstring """ """ f = form super().configure_form(f) key = self.request.matchdict["report_key"] report = self.report_handler.get_report(key) # help_text f.set_readonly("help_text") # add widget fields for all report params schema = f.get_schema() report.add_params(schema) f.set_fields([node.name for node in schema.children])
[docs] def run_report(self, report, context): """ Run the given report and update view template context. This is called automatically from :meth:`view()`. :param report: :class:`~wuttjamaican:wuttjamaican.reports.Report` instance to run. :param context: Current view template context. :returns: Final view template context. """ form = context["form"] controls = list(self.request.GET.items()) # TODO: must re-inject help_text value for some reason, # otherwise its absence screws things up. why? controls.append(("help_text", report.__doc__)) dform = form.get_deform() try: params = dform.validate(controls) except deform.ValidationFailure: log.debug("form not valid: %s", dform.error) return context data = self.report_handler.make_report_data(report, params) columns = self.normalize_columns(report.get_output_columns()) context["report_columns"] = columns format_cols = [col for col in columns if col.get("formatter")] if format_cols: for record in data["data"]: for column in format_cols: if column["name"] in record: value = record[column["name"]] record[column["name"]] = column["formatter"](value) params.pop("help_text") context["report_params"] = params context["report_data"] = data context["report_generated"] = datetime.datetime.now() return context
def normalize_columns(self, columns): # pylint: disable=empty-docstring """ """ normal = [] for column in columns: if isinstance(column, str): column = {"name": column} column.setdefault("label", column["name"]) normal.append(column) return normal def get_download_data(self): # pylint: disable=empty-docstring """ """ key = self.request.matchdict["report_key"] report = self.report_handler.get_report(key) params = dict(self.request.GET) columns = self.normalize_columns(report.get_output_columns()) data = self.report_handler.make_report_data(report, params) return params, columns, data def get_download_path(self, data, ext): # pylint: disable=empty-docstring """ """ tempdir = tempfile.mkdtemp() filename = f"{data['output_title']}.{ext}" return os.path.join(tempdir, filename) @classmethod def defaults(cls, config): # pylint: disable=empty-docstring """ """ cls._defaults(config) cls._report_defaults(config) @classmethod def _report_defaults(cls, config): permission_prefix = cls.get_permission_prefix() model_title = cls.get_model_title() # overwrite title for "view" perm since it also implies "run" config.add_wutta_permission( permission_prefix, f"{permission_prefix}.view", f"View / run {model_title}" ) # separate permission to download report files config.add_wutta_permission( permission_prefix, f"{permission_prefix}.download", f"Download {model_title}", )
def defaults(config, **kwargs): # pylint: disable=missing-function-docstring base = globals() ReportView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name "ReportView", base["ReportView"] ) ReportView.defaults(config) def includeme(config): # pylint: disable=missing-function-docstring defaults(config)