WuttaWeb 0.19.3__tar.gz → 0.20.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/CHANGELOG.md +12 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/PKG-INFO +2 -2
- wuttaweb-0.20.0/docs/api/wuttaweb.views.reports.rst +6 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/index.rst +1 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/pyproject.toml +2 -2
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/forms/base.py +26 -1
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/forms/vue_template.mako +11 -3
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/grids/vue_template.mako +3 -0
- wuttaweb-0.20.0/src/wuttaweb/templates/reports/view.mako +61 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/views/master.py +41 -11
- wuttaweb-0.20.0/src/wuttaweb/views/reports.py +266 -0
- wuttaweb-0.20.0/tests/views/test_reports.py +231 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/.gitignore +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/COPYING.txt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/README.md +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/Makefile +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/_static/.keepme +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.app.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.auth.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.cli.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.cli.webapp.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.conf.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.db.continuum.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.db.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.db.sess.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.emails.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.forms.base.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.forms.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.forms.schema.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.forms.widgets.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.grids.base.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.grids.filters.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.grids.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.handler.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.helpers.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.menus.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.progress.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.static.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.subscribers.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.util.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.auth.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.base.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.batch.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.common.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.email.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.essential.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.master.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.people.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.progress.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.roles.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.settings.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.upgrades.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.users.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/conf.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/glossary.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/make.bat +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/narr/cli/builtin.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/narr/cli/index.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/narr/templates/base.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/narr/templates/index.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/narr/templates/lookup.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/docs/narr/templates/overview.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/_version.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/app.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/auth.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/cli/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/cli/webapp.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/conf.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/db/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/db/continuum.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/db/sess.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/email-templates/feedback.html.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/email-templates/feedback.txt.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/emails.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/forms/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/forms/schema.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/forms/widgets.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/grids/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/grids/base.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/grids/filters.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/handler.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/helpers.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/menus.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/progress.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/static/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/static/img/favicon.ico +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/static/img/logo.png +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/static/img/testing.png +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/subscribers.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/appinfo/index.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/auth/change_password.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/auth/login.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/base.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/base_meta.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/batch/view.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/configure.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/dateinput.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/datetimeinput.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/password.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/permissions.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/readonly/email_recips.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/select.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/textarea.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/textinput.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/email/settings/view.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/forbidden.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/form.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/grids/table_element.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/home.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/master/configure.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/master/create.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/master/delete.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/master/edit.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/master/form.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/master/index.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/master/view.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/notfound.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/page.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/people/view_profile.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/progress.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/setup.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/upgrade.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/upgrades/view.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/templates/wutta-components.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/testing.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/util.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/views/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/views/auth.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/views/base.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/views/batch.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/views/common.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/views/email.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/views/essential.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/views/people.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/views/progress.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/views/roles.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/views/settings.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/views/upgrades.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/src/wuttaweb/views/users.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tasks.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/cli/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/cli/test_webapp.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/db/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/db/test_continuum.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/forms/test_base.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/forms/test_schema.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/forms/test_widgets.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/grids/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/grids/test_base.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/grids/test_filters.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/libcache/bb_oruga.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/libcache/bb_oruga_bulma.css +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/libcache/bb_oruga_bulma.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/libcache/bb_vue.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/libcache/bb_vue_fontawesome.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/libcache/buefy.css +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/libcache/buefy.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/libcache/fontawesome.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/libcache/vue.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/libcache/vue_resource.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/test_app.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/test_auth.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/test_emails.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/test_handler.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/test_helpers.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/test_menus.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/test_progress.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/test_static.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/test_subscribers.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/test_util.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/util.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/views/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/views/test___init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/views/test_auth.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/views/test_base.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/views/test_batch.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/views/test_common.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/views/test_email.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/views/test_essential.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/views/test_master.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/views/test_people.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/views/test_progress.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/views/test_roles.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/views/test_settings.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/views/test_upgrades.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tests/views/test_users.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.0}/tox.ini +0 -0
|
@@ -5,6 +5,18 @@ All notable changes to wuttaweb will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## v0.20.0 (2025-01-11)
|
|
9
|
+
|
|
10
|
+
### Feat
|
|
11
|
+
|
|
12
|
+
- add basic views for Reports
|
|
13
|
+
|
|
14
|
+
### Fix
|
|
15
|
+
|
|
16
|
+
- add `action_method` and `reset_url` params for Form class
|
|
17
|
+
- add placeholder when grid has no filters
|
|
18
|
+
- add `get_page_templates()` method for master view
|
|
19
|
+
|
|
8
20
|
## v0.19.3 (2025-01-09)
|
|
9
21
|
|
|
10
22
|
### Fix
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: WuttaWeb
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.20.0
|
|
4
4
|
Summary: Web App for Wutta Framework
|
|
5
5
|
Project-URL: Homepage, https://wuttaproject.org/
|
|
6
6
|
Project-URL: Repository, https://forgejo.wuttaproject.org/wutta/wuttaweb
|
|
@@ -39,7 +39,7 @@ Requires-Dist: pyramid-tm
|
|
|
39
39
|
Requires-Dist: pyramid>=2
|
|
40
40
|
Requires-Dist: waitress
|
|
41
41
|
Requires-Dist: webhelpers2
|
|
42
|
-
Requires-Dist: wuttjamaican[db]>=0.
|
|
42
|
+
Requires-Dist: wuttjamaican[db]>=0.20.0
|
|
43
43
|
Requires-Dist: zope-sqlalchemy>=1.5
|
|
44
44
|
Provides-Extra: continuum
|
|
45
45
|
Requires-Dist: wutta-continuum; extra == 'continuum'
|
|
@@ -60,6 +60,7 @@ the narrative docs are pretty scant. That will eventually change.
|
|
|
60
60
|
api/wuttaweb.views.master
|
|
61
61
|
api/wuttaweb.views.people
|
|
62
62
|
api/wuttaweb.views.progress
|
|
63
|
+
api/wuttaweb.views.reports
|
|
63
64
|
api/wuttaweb.views.roles
|
|
64
65
|
api/wuttaweb.views.settings
|
|
65
66
|
api/wuttaweb.views.upgrades
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "WuttaWeb"
|
|
9
|
-
version = "0.
|
|
9
|
+
version = "0.20.0"
|
|
10
10
|
description = "Web App for Wutta Framework"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
|
|
@@ -44,7 +44,7 @@ dependencies = [
|
|
|
44
44
|
"pyramid_tm",
|
|
45
45
|
"waitress",
|
|
46
46
|
"WebHelpers2",
|
|
47
|
-
"WuttJamaican[db]>=0.
|
|
47
|
+
"WuttJamaican[db]>=0.20.0",
|
|
48
48
|
"zope.sqlalchemy>=1.5",
|
|
49
49
|
]
|
|
50
50
|
|
|
@@ -161,10 +161,23 @@ class Form:
|
|
|
161
161
|
|
|
162
162
|
See also :meth:`set_required()` and :meth:`is_required()`.
|
|
163
163
|
|
|
164
|
+
.. attribute:: action_method
|
|
165
|
+
|
|
166
|
+
HTTP method to use when submitting form; ``'post'`` is default.
|
|
167
|
+
|
|
164
168
|
.. attribute:: action_url
|
|
165
169
|
|
|
166
170
|
String URL to which the form should be submitted, if applicable.
|
|
167
171
|
|
|
172
|
+
.. attribute:: reset_url
|
|
173
|
+
|
|
174
|
+
String URL to which the reset button should "always" redirect,
|
|
175
|
+
if applicable.
|
|
176
|
+
|
|
177
|
+
This is null by default, in which case it will use standard
|
|
178
|
+
browser behavior for the form reset button (if shown). See
|
|
179
|
+
also :attr:`show_button_reset`.
|
|
180
|
+
|
|
168
181
|
.. attribute:: cancel_url
|
|
169
182
|
|
|
170
183
|
String URL to which the Cancel button should "always" redirect,
|
|
@@ -227,6 +240,9 @@ class Form:
|
|
|
227
240
|
Flag indicating whether a Reset button should be shown.
|
|
228
241
|
Default is ``False``.
|
|
229
242
|
|
|
243
|
+
Unless there is a :attr:`reset_url`, the reset button will use
|
|
244
|
+
standard behavior per the browser.
|
|
245
|
+
|
|
230
246
|
.. attribute:: show_button_cancel
|
|
231
247
|
|
|
232
248
|
Flag indicating whether a Cancel button should be shown.
|
|
@@ -266,7 +282,9 @@ class Form:
|
|
|
266
282
|
readonly_fields=[],
|
|
267
283
|
required_fields={},
|
|
268
284
|
labels={},
|
|
285
|
+
action_method='post',
|
|
269
286
|
action_url=None,
|
|
287
|
+
reset_url=None,
|
|
270
288
|
cancel_url=None,
|
|
271
289
|
cancel_url_fallback=None,
|
|
272
290
|
vue_tagname='wutta-form',
|
|
@@ -290,9 +308,11 @@ class Form:
|
|
|
290
308
|
self.readonly_fields = set(readonly_fields or [])
|
|
291
309
|
self.required_fields = required_fields or {}
|
|
292
310
|
self.labels = labels or {}
|
|
311
|
+
self.action_method = action_method
|
|
293
312
|
self.action_url = action_url
|
|
294
313
|
self.cancel_url = cancel_url
|
|
295
314
|
self.cancel_url_fallback = cancel_url_fallback
|
|
315
|
+
self.reset_url = reset_url
|
|
296
316
|
self.vue_tagname = vue_tagname
|
|
297
317
|
self.align_buttons_right = align_buttons_right
|
|
298
318
|
self.auto_disable_submit = auto_disable_submit
|
|
@@ -940,10 +960,15 @@ class Form:
|
|
|
940
960
|
"""
|
|
941
961
|
context['form'] = self
|
|
942
962
|
context['dform'] = self.get_deform()
|
|
943
|
-
context.setdefault('form_attrs', {})
|
|
944
963
|
context.setdefault('request', self.request)
|
|
945
964
|
context['model_data'] = self.get_vue_model_data()
|
|
946
965
|
|
|
966
|
+
# set form method, enctype
|
|
967
|
+
context.setdefault('form_attrs', {})
|
|
968
|
+
context['form_attrs'].setdefault('method', self.action_method)
|
|
969
|
+
if self.action_method == 'post':
|
|
970
|
+
context['form_attrs'].setdefault('enctype', 'multipart/form-data')
|
|
971
|
+
|
|
947
972
|
# auto disable button on submit
|
|
948
973
|
if self.auto_disable_submit:
|
|
949
974
|
context['form_attrs']['@submit'] = 'formSubmitting = true'
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
## -*- coding: utf-8; -*-
|
|
2
2
|
|
|
3
3
|
<script type="text/x-template" id="${form.vue_tagname}-template">
|
|
4
|
-
${h.form(form.action_url,
|
|
5
|
-
|
|
4
|
+
${h.form(form.action_url, **form_attrs)}
|
|
5
|
+
% if form.action_method == 'post':
|
|
6
|
+
${h.csrf_token(request)}
|
|
7
|
+
% endif
|
|
6
8
|
|
|
7
9
|
% if form.has_global_errors():
|
|
8
10
|
% for msg in form.get_global_errors():
|
|
@@ -33,7 +35,13 @@
|
|
|
33
35
|
% endif
|
|
34
36
|
|
|
35
37
|
% if form.show_button_reset:
|
|
36
|
-
<b-button
|
|
38
|
+
<b-button
|
|
39
|
+
% if form.reset_url:
|
|
40
|
+
tag="a" href="${form.reset_url}"
|
|
41
|
+
% else:
|
|
42
|
+
native-type="reset"
|
|
43
|
+
% endif
|
|
44
|
+
>
|
|
37
45
|
Reset
|
|
38
46
|
</b-button>
|
|
39
47
|
% endif
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
## -*- coding: utf-8; mode: html; -*-
|
|
2
|
+
<%inherit file="/master/view.mako" />
|
|
3
|
+
|
|
4
|
+
<%def name="page_layout()">
|
|
5
|
+
${parent.page_layout()}
|
|
6
|
+
% if report_data is not Undefined:
|
|
7
|
+
<br />
|
|
8
|
+
<a name="report-output"></a>
|
|
9
|
+
<div style="display: flex; justify-content: space-between;">
|
|
10
|
+
<div class="report-header">
|
|
11
|
+
${self.report_output_header()}
|
|
12
|
+
</div>
|
|
13
|
+
<div class="report-tools">
|
|
14
|
+
${self.report_tools()}
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
${self.report_output_body()}
|
|
18
|
+
% endif
|
|
19
|
+
</%def>
|
|
20
|
+
|
|
21
|
+
<%def name="report_output_header()">
|
|
22
|
+
<h4 class="is-size-4"><a href="#report-output">{{ reportData.output_title }}</a></h4>
|
|
23
|
+
</%def>
|
|
24
|
+
|
|
25
|
+
<%def name="report_tools()"></%def>
|
|
26
|
+
|
|
27
|
+
<%def name="report_output_body()">
|
|
28
|
+
${self.report_output_table()}
|
|
29
|
+
</%def>
|
|
30
|
+
|
|
31
|
+
<%def name="report_output_table()">
|
|
32
|
+
<b-table :data="reportData.data"
|
|
33
|
+
narrowed
|
|
34
|
+
hoverable>
|
|
35
|
+
% for column in report_columns:
|
|
36
|
+
<b-table-column field="${column['name']}"
|
|
37
|
+
label="${column['label']}"
|
|
38
|
+
% if column.get('numeric'):
|
|
39
|
+
numeric
|
|
40
|
+
% endif
|
|
41
|
+
v-slot="props">
|
|
42
|
+
<span v-html="props.row.${column['name']}"></span>
|
|
43
|
+
</b-table-column>
|
|
44
|
+
% endfor
|
|
45
|
+
</b-table>
|
|
46
|
+
</%def>
|
|
47
|
+
|
|
48
|
+
<%def name="modify_vue_vars()">
|
|
49
|
+
${parent.modify_vue_vars()}
|
|
50
|
+
% if report_data is not Undefined:
|
|
51
|
+
<script>
|
|
52
|
+
|
|
53
|
+
ThisPageData.reportData = ${json.dumps(report_data)|n}
|
|
54
|
+
|
|
55
|
+
WholePageData.mountedHooks.push(function() {
|
|
56
|
+
location.href = '#report-output'
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
</script>
|
|
60
|
+
% endif
|
|
61
|
+
</%def>
|
|
@@ -1777,14 +1777,14 @@ class MasterView(View):
|
|
|
1777
1777
|
context = self.get_template_context(context)
|
|
1778
1778
|
|
|
1779
1779
|
# first try the template path most specific to this view
|
|
1780
|
-
|
|
1781
|
-
mako_path =
|
|
1780
|
+
page_templates = self.get_page_templates(template)
|
|
1781
|
+
mako_path = page_templates[0]
|
|
1782
1782
|
try:
|
|
1783
1783
|
return render_to_response(mako_path, context, request=self.request)
|
|
1784
1784
|
except IOError:
|
|
1785
1785
|
|
|
1786
1786
|
# failing that, try one or more fallback templates
|
|
1787
|
-
for fallback in
|
|
1787
|
+
for fallback in page_templates[1:]:
|
|
1788
1788
|
try:
|
|
1789
1789
|
return render_to_response(fallback, context, request=self.request)
|
|
1790
1790
|
except IOError:
|
|
@@ -1815,21 +1815,51 @@ class MasterView(View):
|
|
|
1815
1815
|
"""
|
|
1816
1816
|
return context
|
|
1817
1817
|
|
|
1818
|
+
def get_page_templates(self, template):
|
|
1819
|
+
"""
|
|
1820
|
+
Returns a list of all templates which can be attempted, to
|
|
1821
|
+
render the current page. This is called by
|
|
1822
|
+
:meth:`render_to_response()`.
|
|
1823
|
+
|
|
1824
|
+
The list should be in order of preference, e.g. the first
|
|
1825
|
+
entry will be the most "specific" template, with subsequent
|
|
1826
|
+
entries becoming more generic.
|
|
1827
|
+
|
|
1828
|
+
In practice this method defines the first entry but calls
|
|
1829
|
+
:meth:`get_fallback_templates()` for the rest.
|
|
1830
|
+
|
|
1831
|
+
:param template: Base name for a template (without prefix), e.g.
|
|
1832
|
+
``'view'``.
|
|
1833
|
+
|
|
1834
|
+
:returns: List of template paths to be tried, based on the
|
|
1835
|
+
specified template. For instance if ``template`` is
|
|
1836
|
+
``'view'`` this will (by default) return::
|
|
1837
|
+
|
|
1838
|
+
[
|
|
1839
|
+
'/widgets/view.mako',
|
|
1840
|
+
'/master/view.mako',
|
|
1841
|
+
]
|
|
1842
|
+
|
|
1843
|
+
"""
|
|
1844
|
+
template_prefix = self.get_template_prefix()
|
|
1845
|
+
page_templates = [f'{template_prefix}/{template}.mako']
|
|
1846
|
+
page_templates.extend(self.get_fallback_templates(template))
|
|
1847
|
+
return page_templates
|
|
1848
|
+
|
|
1818
1849
|
def get_fallback_templates(self, template):
|
|
1819
1850
|
"""
|
|
1820
1851
|
Returns a list of "fallback" template paths which may be
|
|
1821
|
-
attempted for rendering
|
|
1822
|
-
:meth:`
|
|
1823
|
-
file was not found.
|
|
1852
|
+
attempted for rendering the current page. See also
|
|
1853
|
+
:meth:`get_page_templates()`.
|
|
1824
1854
|
|
|
1825
1855
|
:param template: Base name for a template (without prefix), e.g.
|
|
1826
|
-
``'
|
|
1856
|
+
``'view'``.
|
|
1827
1857
|
|
|
1828
|
-
:returns: List of
|
|
1829
|
-
|
|
1830
|
-
``'
|
|
1858
|
+
:returns: List of template paths to be tried, based on the
|
|
1859
|
+
specified template. For instance if ``template`` is
|
|
1860
|
+
``'view'`` this will (by default) return::
|
|
1831
1861
|
|
|
1832
|
-
['/master/
|
|
1862
|
+
['/master/view.mako']
|
|
1833
1863
|
"""
|
|
1834
1864
|
return [f'/master/{template}.mako']
|
|
1835
1865
|
|
|
@@ -0,0 +1,266 @@
|
|
|
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
|
+
"""
|
|
24
|
+
Report Views
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import datetime
|
|
28
|
+
import logging
|
|
29
|
+
import os
|
|
30
|
+
import tempfile
|
|
31
|
+
|
|
32
|
+
import deform
|
|
33
|
+
|
|
34
|
+
from wuttaweb.views import MasterView
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
log = logging.getLogger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ReportView(MasterView):
|
|
41
|
+
"""
|
|
42
|
+
Master view for :term:`reports <report>`; route prefix is
|
|
43
|
+
``reports``.
|
|
44
|
+
|
|
45
|
+
Notable URLs provided by this class:
|
|
46
|
+
|
|
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'
|
|
59
|
+
|
|
60
|
+
grid_columns = [
|
|
61
|
+
'report_title',
|
|
62
|
+
'help_text',
|
|
63
|
+
'report_key',
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
form_fields = [
|
|
67
|
+
'help_text',
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
def __init__(self, request, context=None):
|
|
71
|
+
super().__init__(request, context=context)
|
|
72
|
+
self.report_handler = self.app.get_report_handler()
|
|
73
|
+
|
|
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
|
|
80
|
+
|
|
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
|
+
}
|
|
88
|
+
|
|
89
|
+
def configure_grid(self, g):
|
|
90
|
+
""" """
|
|
91
|
+
super().configure_grid(g)
|
|
92
|
+
|
|
93
|
+
# report_key
|
|
94
|
+
g.set_link('report_key')
|
|
95
|
+
|
|
96
|
+
# report_title
|
|
97
|
+
g.set_link('report_title')
|
|
98
|
+
g.set_searchable('report_title')
|
|
99
|
+
|
|
100
|
+
# help_text
|
|
101
|
+
g.set_searchable('help_text')
|
|
102
|
+
|
|
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)
|
|
109
|
+
|
|
110
|
+
raise self.notfound()
|
|
111
|
+
|
|
112
|
+
def get_instance_title(self, report):
|
|
113
|
+
""" """
|
|
114
|
+
return report['report_title']
|
|
115
|
+
|
|
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)
|
|
125
|
+
|
|
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')
|
|
135
|
+
|
|
136
|
+
context = {
|
|
137
|
+
'instance': normal,
|
|
138
|
+
'report': report,
|
|
139
|
+
'form': form,
|
|
140
|
+
'xref_buttons': self.get_xref_buttons(report),
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if self.request.GET:
|
|
144
|
+
form.show_button_cancel = False
|
|
145
|
+
context = self.run_report(report, context)
|
|
146
|
+
|
|
147
|
+
return self.render_to_response('view', context)
|
|
148
|
+
|
|
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)
|
|
154
|
+
|
|
155
|
+
# help_text
|
|
156
|
+
f.set_readonly('help_text')
|
|
157
|
+
|
|
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])
|
|
162
|
+
|
|
163
|
+
def run_report(self, report, context):
|
|
164
|
+
"""
|
|
165
|
+
Run the given report and update view template context.
|
|
166
|
+
|
|
167
|
+
This is called automatically from :meth:`view()`.
|
|
168
|
+
|
|
169
|
+
:param report:
|
|
170
|
+
:class:`~wuttjamaican:wuttjamaican.reports.Report` instance
|
|
171
|
+
to run.
|
|
172
|
+
|
|
173
|
+
:param context: Current view template context.
|
|
174
|
+
|
|
175
|
+
:returns: Final view template context.
|
|
176
|
+
"""
|
|
177
|
+
form = context['form']
|
|
178
|
+
controls = list(self.request.GET.items())
|
|
179
|
+
|
|
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__))
|
|
183
|
+
|
|
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
|
|
190
|
+
|
|
191
|
+
data = self.report_handler.make_report_data(report, params)
|
|
192
|
+
|
|
193
|
+
columns = self.normalize_columns(report.get_output_columns())
|
|
194
|
+
context['report_columns'] = columns
|
|
195
|
+
|
|
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)
|
|
203
|
+
|
|
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
|
|
209
|
+
|
|
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
|
|
219
|
+
|
|
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
|
|
228
|
+
|
|
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)
|
|
234
|
+
|
|
235
|
+
@classmethod
|
|
236
|
+
def defaults(cls, config):
|
|
237
|
+
""" """
|
|
238
|
+
cls._defaults(config)
|
|
239
|
+
cls._report_defaults(config)
|
|
240
|
+
|
|
241
|
+
@classmethod
|
|
242
|
+
def _report_defaults(cls, config):
|
|
243
|
+
permission_prefix = cls.get_permission_prefix()
|
|
244
|
+
model_title = cls.get_model_title()
|
|
245
|
+
|
|
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}")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# separate permission to download report files
|
|
253
|
+
config.add_wutta_permission(permission_prefix,
|
|
254
|
+
f'{permission_prefix}.download',
|
|
255
|
+
f"Download {model_title}")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def defaults(config, **kwargs):
|
|
259
|
+
base = globals()
|
|
260
|
+
|
|
261
|
+
ReportView = kwargs.get('ReportView', base['ReportView'])
|
|
262
|
+
ReportView.defaults(config)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def includeme(config):
|
|
266
|
+
defaults(config)
|