feat: add basic views for Reports
not entirely useful as-is yet, that may change later but for now keeping things minimal to avoid being painted into any corner
This commit is contained in:
parent
ffd4ee929c
commit
65511a26b2
6
docs/api/wuttaweb.views.reports.rst
Normal file
6
docs/api/wuttaweb.views.reports.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
``wuttaweb.views.reports``
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. automodule:: wuttaweb.views.reports
|
||||||
|
:members:
|
|
@ -60,6 +60,7 @@ the narrative docs are pretty scant. That will eventually change.
|
||||||
api/wuttaweb.views.master
|
api/wuttaweb.views.master
|
||||||
api/wuttaweb.views.people
|
api/wuttaweb.views.people
|
||||||
api/wuttaweb.views.progress
|
api/wuttaweb.views.progress
|
||||||
|
api/wuttaweb.views.reports
|
||||||
api/wuttaweb.views.roles
|
api/wuttaweb.views.roles
|
||||||
api/wuttaweb.views.settings
|
api/wuttaweb.views.settings
|
||||||
api/wuttaweb.views.upgrades
|
api/wuttaweb.views.upgrades
|
||||||
|
|
61
src/wuttaweb/templates/reports/view.mako
Normal file
61
src/wuttaweb/templates/reports/view.mako
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
## -*- coding: utf-8; mode: html; -*-
|
||||||
|
<%inherit file="/master/view.mako" />
|
||||||
|
|
||||||
|
<%def name="page_layout()">
|
||||||
|
${parent.page_layout()}
|
||||||
|
% if report_data is not Undefined:
|
||||||
|
<br />
|
||||||
|
<a name="report-output"></a>
|
||||||
|
<div style="display: flex; justify-content: space-between;">
|
||||||
|
<div class="report-header">
|
||||||
|
${self.report_output_header()}
|
||||||
|
</div>
|
||||||
|
<div class="report-tools">
|
||||||
|
${self.report_tools()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${self.report_output_body()}
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="report_output_header()">
|
||||||
|
<h4 class="is-size-4"><a href="#report-output">{{ reportData.output_title }}</a></h4>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="report_tools()"></%def>
|
||||||
|
|
||||||
|
<%def name="report_output_body()">
|
||||||
|
${self.report_output_table()}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="report_output_table()">
|
||||||
|
<b-table :data="reportData.data"
|
||||||
|
narrowed
|
||||||
|
hoverable>
|
||||||
|
% for column in report_columns:
|
||||||
|
<b-table-column field="${column['name']}"
|
||||||
|
label="${column['label']}"
|
||||||
|
% if column.get('numeric'):
|
||||||
|
numeric
|
||||||
|
% endif
|
||||||
|
v-slot="props">
|
||||||
|
<span v-html="props.row.${column['name']}"></span>
|
||||||
|
</b-table-column>
|
||||||
|
% endfor
|
||||||
|
</b-table>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="modify_vue_vars()">
|
||||||
|
${parent.modify_vue_vars()}
|
||||||
|
% if report_data is not Undefined:
|
||||||
|
<script>
|
||||||
|
|
||||||
|
ThisPageData.reportData = ${json.dumps(report_data)|n}
|
||||||
|
|
||||||
|
WholePageData.mountedHooks.push(function() {
|
||||||
|
location.href = '#report-output'
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
% endif
|
||||||
|
</%def>
|
266
src/wuttaweb/views/reports.py
Normal file
266
src/wuttaweb/views/reports.py
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# wuttaweb -- Web App for Wutta Framework
|
||||||
|
# Copyright © 2024 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__)
|
||||||
|
|
||||||
|
|
||||||
|
class ReportView(MasterView):
|
||||||
|
"""
|
||||||
|
Master view for :term:`reports <report>`; route prefix is
|
||||||
|
``reports``.
|
||||||
|
|
||||||
|
Notable URLs provided by this class:
|
||||||
|
|
||||||
|
* ``/reports/``
|
||||||
|
* ``/reports/XXX``
|
||||||
|
"""
|
||||||
|
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(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):
|
||||||
|
""" """
|
||||||
|
return {
|
||||||
|
'report_key': report.report_key,
|
||||||
|
'report_title': report.report_title,
|
||||||
|
'help_text': report.__doc__,
|
||||||
|
}
|
||||||
|
|
||||||
|
def configure_grid(self, g):
|
||||||
|
""" """
|
||||||
|
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(self):
|
||||||
|
""" """
|
||||||
|
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, report):
|
||||||
|
""" """
|
||||||
|
return report['report_title']
|
||||||
|
|
||||||
|
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, f):
|
||||||
|
""" """
|
||||||
|
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])
|
||||||
|
|
||||||
|
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):
|
||||||
|
""" """
|
||||||
|
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):
|
||||||
|
""" """
|
||||||
|
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):
|
||||||
|
""" """
|
||||||
|
tempdir = tempfile.mkdtemp()
|
||||||
|
filename = f"{data['output_title']}.{ext}"
|
||||||
|
return os.path.join(tempdir, filename)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def defaults(cls, config):
|
||||||
|
""" """
|
||||||
|
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):
|
||||||
|
base = globals()
|
||||||
|
|
||||||
|
ReportView = kwargs.get('ReportView', base['ReportView'])
|
||||||
|
ReportView.defaults(config)
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
defaults(config)
|
231
tests/views/test_reports.py
Normal file
231
tests/views/test_reports.py
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
from wuttjamaican.reports import Report
|
||||||
|
|
||||||
|
import colander
|
||||||
|
from pyramid.httpexceptions import HTTPNotFound
|
||||||
|
|
||||||
|
from wuttaweb.views import reports as mod
|
||||||
|
from wuttaweb.testing import WebTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class SomeRandomReport(Report):
|
||||||
|
"""
|
||||||
|
This report shows something random.
|
||||||
|
"""
|
||||||
|
report_key = 'testing_some_random'
|
||||||
|
report_title = "Random Test Report"
|
||||||
|
|
||||||
|
def add_params(self, schema):
|
||||||
|
|
||||||
|
schema.add(colander.SchemaNode(
|
||||||
|
colander.String(),
|
||||||
|
name='foo',
|
||||||
|
missing=colander.null))
|
||||||
|
|
||||||
|
schema.add(colander.SchemaNode(
|
||||||
|
colander.Date(),
|
||||||
|
name='start_date',
|
||||||
|
missing=colander.null))
|
||||||
|
|
||||||
|
def get_output_columns(self):
|
||||||
|
return ['foo']
|
||||||
|
|
||||||
|
def make_data(self, params, **kwargs):
|
||||||
|
return {
|
||||||
|
'output_title': "Testing Output",
|
||||||
|
'data': [{'foo': 'bar'}],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestReportViews(WebTestCase):
|
||||||
|
|
||||||
|
def make_view(self):
|
||||||
|
return mod.ReportView(self.request)
|
||||||
|
|
||||||
|
def test_includeme(self):
|
||||||
|
self.pyramid_config.include('wuttaweb.views.reports')
|
||||||
|
|
||||||
|
def test_get_grid_data(self):
|
||||||
|
view = self.make_view()
|
||||||
|
data = view.get_grid_data()
|
||||||
|
self.assertIsInstance(data, list)
|
||||||
|
|
||||||
|
def test_normalize_report(self):
|
||||||
|
view = self.make_view()
|
||||||
|
report = SomeRandomReport(self.config)
|
||||||
|
normal = view.normalize_report(report)
|
||||||
|
help_text = normal.pop('help_text').strip()
|
||||||
|
self.assertEqual(help_text, "This report shows something random.")
|
||||||
|
self.assertEqual(normal, {
|
||||||
|
'report_key': 'testing_some_random',
|
||||||
|
'report_title': "Random Test Report",
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_configure_grid(self):
|
||||||
|
view = self.make_view()
|
||||||
|
grid = view.make_model_grid()
|
||||||
|
self.assertIn('report_title', grid.searchable_columns)
|
||||||
|
self.assertIn('help_text', grid.searchable_columns)
|
||||||
|
|
||||||
|
def test_get_instance(self):
|
||||||
|
view = self.make_view()
|
||||||
|
providers = {
|
||||||
|
'wuttatest': MagicMock(report_modules=['tests.views.test_reports']),
|
||||||
|
}
|
||||||
|
with patch.object(self.app, 'providers', new=providers):
|
||||||
|
|
||||||
|
# normal
|
||||||
|
with patch.object(self.request, 'matchdict', new={'report_key': 'testing_some_random'}):
|
||||||
|
report = view.get_instance()
|
||||||
|
self.assertIsInstance(report, dict)
|
||||||
|
self.assertEqual(report['report_key'], 'testing_some_random')
|
||||||
|
self.assertEqual(report['report_title'], "Random Test Report")
|
||||||
|
|
||||||
|
# not found
|
||||||
|
with patch.object(self.request, 'matchdict', new={'report_key': 'this-should_notEXIST'}):
|
||||||
|
self.assertRaises(HTTPNotFound, view.get_instance)
|
||||||
|
|
||||||
|
def test_get_instance_title(self):
|
||||||
|
view = self.make_view()
|
||||||
|
result = view.get_instance_title({'report_title': 'whatever'})
|
||||||
|
self.assertEqual(result, 'whatever')
|
||||||
|
|
||||||
|
def test_view(self):
|
||||||
|
self.pyramid_config.add_route('home', '/')
|
||||||
|
self.pyramid_config.add_route('login', '/auth/login')
|
||||||
|
self.pyramid_config.add_route('reports', '/reports/')
|
||||||
|
self.pyramid_config.add_route('reports.view', '/reports/{report_key}')
|
||||||
|
view = self.make_view()
|
||||||
|
providers = dict(self.app.providers)
|
||||||
|
providers['wuttatest'] = MagicMock(report_modules=['tests.views.test_reports'])
|
||||||
|
with patch.object(self.app, 'providers', new=providers):
|
||||||
|
with patch.object(self.request, 'matchdict', new={'report_key': 'testing_some_random'}):
|
||||||
|
|
||||||
|
# initial view
|
||||||
|
response = view.view()
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
# nb. there's a button in there somewhere, but no output title
|
||||||
|
self.assertIn("Run Report", response.text)
|
||||||
|
self.assertNotIn("Testing Output", response.text)
|
||||||
|
|
||||||
|
# run the report
|
||||||
|
with patch.object(self.request, 'GET', new={
|
||||||
|
'__start__': 'start_date:mapping',
|
||||||
|
'date': '2025-01-11',
|
||||||
|
'__end__': 'start_date',
|
||||||
|
}):
|
||||||
|
response = view.view()
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
# nb. there's a button in there somewhere, *and* an output title
|
||||||
|
self.assertIn("Run Report", response.text)
|
||||||
|
self.assertIn("Testing Output", response.text)
|
||||||
|
|
||||||
|
def test_configure_form(self):
|
||||||
|
view = self.make_view()
|
||||||
|
providers = dict(self.app.providers)
|
||||||
|
providers['wuttatest'] = MagicMock(report_modules=['tests.views.test_reports'])
|
||||||
|
with patch.object(self.app, 'providers', new=providers):
|
||||||
|
|
||||||
|
with patch.object(self.request, 'matchdict', new={'report_key': 'testing_some_random'}):
|
||||||
|
report = view.get_instance()
|
||||||
|
form = view.make_model_form(report)
|
||||||
|
self.assertIn('help_text', form.readonly_fields)
|
||||||
|
self.assertIn('foo', form)
|
||||||
|
|
||||||
|
def test_normalize_columns(self):
|
||||||
|
view = self.make_view()
|
||||||
|
|
||||||
|
columns = view.normalize_columns(['foo'])
|
||||||
|
self.assertEqual(columns, [
|
||||||
|
{'name': 'foo', 'label': 'foo'},
|
||||||
|
])
|
||||||
|
|
||||||
|
columns = view.normalize_columns([{'name': 'foo'}])
|
||||||
|
self.assertEqual(columns, [
|
||||||
|
{'name': 'foo', 'label': 'foo'},
|
||||||
|
])
|
||||||
|
|
||||||
|
columns = view.normalize_columns([{'name': 'foo', 'label': "FOO"}])
|
||||||
|
self.assertEqual(columns, [
|
||||||
|
{'name': 'foo', 'label': 'FOO'},
|
||||||
|
])
|
||||||
|
|
||||||
|
columns = view.normalize_columns([{'name': 'foo', 'label': "FOO", 'numeric': True}])
|
||||||
|
self.assertEqual(columns, [
|
||||||
|
{'name': 'foo', 'label': 'FOO', 'numeric': True},
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_run_report(self):
|
||||||
|
view = self.make_view()
|
||||||
|
providers = dict(self.app.providers)
|
||||||
|
providers['wuttatest'] = MagicMock(report_modules=['tests.views.test_reports'])
|
||||||
|
with patch.object(self.app, 'providers', new=providers):
|
||||||
|
|
||||||
|
with patch.object(self.request, 'matchdict', new={'report_key': 'testing_some_random'}):
|
||||||
|
report = view.report_handler.get_report('testing_some_random')
|
||||||
|
normal = view.normalize_report(report)
|
||||||
|
form = view.make_model_form(normal)
|
||||||
|
|
||||||
|
# typical
|
||||||
|
context = view.run_report(report, {'form': form})
|
||||||
|
self.assertEqual(sorted(context['report_params']), ['foo', 'start_date'])
|
||||||
|
self.assertEqual(context['report_data'], {
|
||||||
|
'output_title': "Testing Output",
|
||||||
|
'data': [{'foo': 'bar'}],
|
||||||
|
})
|
||||||
|
self.assertIn('report_generated', context)
|
||||||
|
|
||||||
|
# invalid params
|
||||||
|
with patch.object(self.request, 'GET', new={'start_date': 'NOT_GOOD'}):
|
||||||
|
context = view.run_report(report, {'form': form})
|
||||||
|
self.assertNotIn('report_params', context)
|
||||||
|
self.assertNotIn('report_data', context)
|
||||||
|
self.assertNotIn('report_generated', context)
|
||||||
|
|
||||||
|
# custom formatter
|
||||||
|
with patch.object(report, 'get_output_columns') as get_output_columns:
|
||||||
|
get_output_columns.return_value = [
|
||||||
|
'foo',
|
||||||
|
{'name': 'start_date',
|
||||||
|
'formatter': lambda val: "FORMATTED VALUE"},
|
||||||
|
]
|
||||||
|
|
||||||
|
with patch.object(report, 'make_data') as make_data:
|
||||||
|
make_data.return_value = [
|
||||||
|
{'foo': 'bar', 'start_date': datetime.date(2025, 1, 11)},
|
||||||
|
]
|
||||||
|
|
||||||
|
context = view.run_report(report, {'form': form})
|
||||||
|
get_output_columns.assert_called_once_with()
|
||||||
|
self.assertEqual(len(context['report_columns']), 2)
|
||||||
|
self.assertEqual(context['report_columns'][0]['name'], 'foo')
|
||||||
|
self.assertEqual(context['report_columns'][1]['name'], 'start_date')
|
||||||
|
self.assertEqual(context['report_data'], {
|
||||||
|
'output_title': "Random Test Report",
|
||||||
|
'data': [{'foo': 'bar', 'start_date': 'FORMATTED VALUE'}],
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_download_data(self):
|
||||||
|
view = self.make_view()
|
||||||
|
providers = dict(self.app.providers)
|
||||||
|
providers['wuttatest'] = MagicMock(report_modules=['tests.views.test_reports'])
|
||||||
|
with patch.object(self.app, 'providers', new=providers):
|
||||||
|
with patch.object(self.request, 'matchdict', new={'report_key': 'testing_some_random'}):
|
||||||
|
|
||||||
|
params, columns, data = view.get_download_data()
|
||||||
|
self.assertEqual(params, {})
|
||||||
|
self.assertEqual(columns, [{'name': 'foo', 'label': 'foo'}])
|
||||||
|
self.assertEqual(data, {
|
||||||
|
'output_title': "Testing Output",
|
||||||
|
'data': [{'foo': 'bar'}],
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_download_path(self):
|
||||||
|
view = self.make_view()
|
||||||
|
data = {'output_title': "My Report"}
|
||||||
|
path = view.get_download_path(data, 'csv')
|
||||||
|
self.assertTrue(path.endswith('My Report.csv'))
|
Loading…
Reference in a new issue