Coverage for .tox/coverage/lib/python3.11/site-packages/wuttaweb/views/reports.py: 100%
122 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-11 23:59 -0600
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-11 23:59 -0600
1# -*- coding: utf-8; -*-
2################################################################################
3#
4# wuttaweb -- Web App for Wutta Framework
5# Copyright © 2024 Lance Edgar
6#
7# This file is part of Wutta Framework.
8#
9# Wutta Framework is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by the Free
11# Software Foundation, either version 3 of the License, or (at your option) any
12# later version.
13#
14# Wutta Framework is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17# more details.
18#
19# You should have received a copy of the GNU General Public License along with
20# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
21#
22################################################################################
23"""
24Report Views
25"""
27import datetime
28import logging
29import os
30import tempfile
32import deform
34from wuttaweb.views import MasterView
37log = logging.getLogger(__name__)
40class ReportView(MasterView):
41 """
42 Master view for :term:`reports <report>`; route prefix is
43 ``reports``.
45 Notable URLs provided by this class:
47 * ``/reports/``
48 * ``/reports/XXX``
49 """
50 model_title = "Report"
51 model_key = 'report_key'
52 filterable = False
53 sort_on_backend = False
54 creatable = False
55 editable = False
56 deletable = False
57 route_prefix = 'reports'
58 template_prefix = '/reports'
60 grid_columns = [
61 'report_title',
62 'help_text',
63 'report_key',
64 ]
66 form_fields = [
67 'help_text',
68 ]
70 def __init__(self, request, context=None):
71 super().__init__(request, context=context)
72 self.report_handler = self.app.get_report_handler()
74 def get_grid_data(self, columns=None, session=None):
75 """ """
76 data = []
77 for report in self.report_handler.get_reports().values():
78 data.append(self.normalize_report(report))
79 return data
81 def normalize_report(self, report):
82 """ """
83 return {
84 'report_key': report.report_key,
85 'report_title': report.report_title,
86 'help_text': report.__doc__,
87 }
89 def configure_grid(self, g):
90 """ """
91 super().configure_grid(g)
93 # report_key
94 g.set_link('report_key')
96 # report_title
97 g.set_link('report_title')
98 g.set_searchable('report_title')
100 # help_text
101 g.set_searchable('help_text')
103 def get_instance(self):
104 """ """
105 key = self.request.matchdict['report_key']
106 report = self.report_handler.get_report(key)
107 if report:
108 return self.normalize_report(report)
110 raise self.notfound()
112 def get_instance_title(self, report):
113 """ """
114 return report['report_title']
116 def view(self):
117 """
118 This lets user "view" the report but in this context that
119 means showing them a form with report params, so they can run
120 it.
121 """
122 key = self.request.matchdict['report_key']
123 report = self.report_handler.get_report(key)
124 normal = self.normalize_report(report)
126 report_url = self.get_action_url('view', normal)
127 form = self.make_model_form(normal,
128 action_method='get',
129 action_url=report_url,
130 cancel_url=self.get_index_url(),
131 show_button_reset=True,
132 reset_url=report_url,
133 button_label_submit="Run Report",
134 button_icon_submit='arrow-circle-right')
136 context = {
137 'instance': normal,
138 'report': report,
139 'form': form,
140 'xref_buttons': self.get_xref_buttons(report),
141 }
143 if self.request.GET:
144 form.show_button_cancel = False
145 context = self.run_report(report, context)
147 return self.render_to_response('view', context)
149 def configure_form(self, f):
150 """ """
151 super().configure_form(f)
152 key = self.request.matchdict['report_key']
153 report = self.report_handler.get_report(key)
155 # help_text
156 f.set_readonly('help_text')
158 # add widget fields for all report params
159 schema = f.get_schema()
160 report.add_params(schema)
161 f.set_fields([node.name for node in schema.children])
163 def run_report(self, report, context):
164 """
165 Run the given report and update view template context.
167 This is called automatically from :meth:`view()`.
169 :param report:
170 :class:`~wuttjamaican:wuttjamaican.reports.Report` instance
171 to run.
173 :param context: Current view template context.
175 :returns: Final view template context.
176 """
177 form = context['form']
178 controls = list(self.request.GET.items())
180 # TODO: must re-inject help_text value for some reason,
181 # otherwise its absence screws things up. why?
182 controls.append(('help_text', report.__doc__))
184 dform = form.get_deform()
185 try:
186 params = dform.validate(controls)
187 except deform.ValidationFailure:
188 log.debug("form not valid: %s", dform.error)
189 return context
191 data = self.report_handler.make_report_data(report, params)
193 columns = self.normalize_columns(report.get_output_columns())
194 context['report_columns'] = columns
196 format_cols = [col for col in columns if col.get('formatter')]
197 if format_cols:
198 for record in data['data']:
199 for column in format_cols:
200 if column['name'] in record:
201 value = record[column['name']]
202 record[column['name']] = column['formatter'](value)
204 params.pop('help_text')
205 context['report_params'] = params
206 context['report_data'] = data
207 context['report_generated'] = datetime.datetime.now()
208 return context
210 def normalize_columns(self, columns):
211 """ """
212 normal = []
213 for column in columns:
214 if isinstance(column, str):
215 column = {'name': column}
216 column.setdefault('label', column['name'])
217 normal.append(column)
218 return normal
220 def get_download_data(self):
221 """ """
222 key = self.request.matchdict['report_key']
223 report = self.report_handler.get_report(key)
224 params = dict(self.request.GET)
225 columns = self.normalize_columns(report.get_output_columns())
226 data = self.report_handler.make_report_data(report, params)
227 return params, columns, data
229 def get_download_path(self, data, ext):
230 """ """
231 tempdir = tempfile.mkdtemp()
232 filename = f"{data['output_title']}.{ext}"
233 return os.path.join(tempdir, filename)
235 @classmethod
236 def defaults(cls, config):
237 """ """
238 cls._defaults(config)
239 cls._report_defaults(config)
241 @classmethod
242 def _report_defaults(cls, config):
243 permission_prefix = cls.get_permission_prefix()
244 model_title = cls.get_model_title()
246 # overwrite title for "view" perm since it also implies "run"
247 config.add_wutta_permission(permission_prefix,
248 f'{permission_prefix}.view',
249 f"View / run {model_title}")
252 # separate permission to download report files
253 config.add_wutta_permission(permission_prefix,
254 f'{permission_prefix}.download',
255 f"Download {model_title}")
258def defaults(config, **kwargs):
259 base = globals()
261 ReportView = kwargs.get('ReportView', base['ReportView'])
262 ReportView.defaults(config)
265def includeme(config):
266 defaults(config)