WuttaWeb 0.19.3__tar.gz → 0.20.1__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.1}/CHANGELOG.md +21 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/PKG-INFO +2 -2
- wuttaweb-0.20.1/docs/api/wuttaweb.views.reports.rst +6 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/index.rst +1 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/pyproject.toml +5 -2
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/forms/base.py +26 -1
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/grids/filters.py +29 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/handler.py +75 -7
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/appinfo/configure.mako +17 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/forms/vue_template.mako +11 -3
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/grids/vue_template.mako +3 -0
- wuttaweb-0.20.1/src/wuttaweb/templates/reports/view.mako +61 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/master.py +51 -14
- wuttaweb-0.20.1/src/wuttaweb/views/reports.py +266 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/settings.py +12 -4
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/grids/test_filters.py +24 -0
- wuttaweb-0.20.1/tests/test_handler.py +149 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_master.py +42 -34
- wuttaweb-0.20.1/tests/views/test_reports.py +236 -0
- wuttaweb-0.19.3/tests/test_handler.py +0 -76
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/.gitignore +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/COPYING.txt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/README.md +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/Makefile +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/_static/.keepme +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.app.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.auth.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.cli.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.cli.webapp.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.conf.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.db.continuum.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.db.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.db.sess.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.emails.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.forms.base.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.forms.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.forms.schema.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.forms.widgets.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.grids.base.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.grids.filters.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.grids.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.handler.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.helpers.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.menus.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.progress.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.static.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.subscribers.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.util.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.auth.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.base.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.batch.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.common.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.email.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.essential.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.master.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.people.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.progress.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.roles.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.settings.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.upgrades.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.users.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/conf.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/glossary.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/make.bat +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/narr/cli/builtin.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/narr/cli/index.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/narr/templates/base.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/narr/templates/index.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/narr/templates/lookup.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/narr/templates/overview.rst +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/_version.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/app.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/auth.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/cli/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/cli/webapp.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/conf.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/db/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/db/continuum.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/db/sess.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/email-templates/feedback.html.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/email-templates/feedback.txt.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/emails.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/forms/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/forms/schema.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/forms/widgets.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/grids/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/grids/base.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/helpers.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/menus.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/progress.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/static/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/static/img/favicon.ico +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/static/img/logo.png +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/static/img/testing.png +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/subscribers.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/appinfo/index.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/auth/change_password.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/auth/login.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/base.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/base_meta.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/batch/view.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/configure.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/dateinput.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/datetimeinput.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/password.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/permissions.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/email_recips.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/select.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/textarea.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/textinput.pt +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/email/settings/view.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/forbidden.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/form.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/grids/table_element.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/home.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/configure.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/create.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/delete.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/edit.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/form.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/index.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/view.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/notfound.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/page.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/people/view_profile.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/progress.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/setup.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/upgrade.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/upgrades/view.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/wutta-components.mako +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/testing.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/util.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/auth.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/base.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/batch.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/common.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/email.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/essential.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/people.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/progress.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/roles.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/upgrades.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/users.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tasks.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/cli/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/cli/test_webapp.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/db/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/db/test_continuum.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/forms/test_base.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/forms/test_schema.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/forms/test_widgets.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/grids/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/grids/test_base.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/bb_oruga.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/bb_oruga_bulma.css +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/bb_oruga_bulma.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/bb_vue.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/bb_vue_fontawesome.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/buefy.css +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/buefy.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/fontawesome.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/vue.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/vue_resource.js +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_app.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_auth.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_emails.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_helpers.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_menus.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_progress.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_static.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_subscribers.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_util.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/util.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/__init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test___init__.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_auth.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_base.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_batch.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_common.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_email.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_essential.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_people.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_progress.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_roles.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_settings.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_upgrades.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_users.py +0 -0
- {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tox.ini +0 -0
|
@@ -5,6 +5,27 @@ 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.1 (2025-01-13)
|
|
9
|
+
|
|
10
|
+
### Fix
|
|
11
|
+
|
|
12
|
+
- expose setting to choose menu handler, in appinfo/configure
|
|
13
|
+
- use prop key instead of column name, for master view model key
|
|
14
|
+
- add grid filters specific to numeric, integer types
|
|
15
|
+
- use default value for config settings
|
|
16
|
+
|
|
17
|
+
## v0.20.0 (2025-01-11)
|
|
18
|
+
|
|
19
|
+
### Feat
|
|
20
|
+
|
|
21
|
+
- add basic views for Reports
|
|
22
|
+
|
|
23
|
+
### Fix
|
|
24
|
+
|
|
25
|
+
- add `action_method` and `reset_url` params for Form class
|
|
26
|
+
- add placeholder when grid has no filters
|
|
27
|
+
- add `get_page_templates()` method for master view
|
|
28
|
+
|
|
8
29
|
## v0.19.3 (2025-01-09)
|
|
9
30
|
|
|
10
31
|
### Fix
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: WuttaWeb
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.20.1
|
|
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.1
|
|
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.1"
|
|
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.1",
|
|
48
48
|
"zope.sqlalchemy>=1.5",
|
|
49
49
|
]
|
|
50
50
|
|
|
@@ -70,6 +70,9 @@ wuttaweb = "wuttaweb.conf:WuttaWebConfigExtension"
|
|
|
70
70
|
[project.entry-points."wutta.typer_imports"]
|
|
71
71
|
wuttaweb = "wuttaweb.cli"
|
|
72
72
|
|
|
73
|
+
[project.entry-points."wutta.web.menus"]
|
|
74
|
+
wuttaweb = "wuttaweb.menus:MenuHandler"
|
|
75
|
+
|
|
73
76
|
|
|
74
77
|
[project.urls]
|
|
75
78
|
Homepage = "https://wuttaproject.org/"
|
|
@@ -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'
|
|
@@ -465,6 +465,33 @@ class StringAlchemyFilter(AlchemyFilter):
|
|
|
465
465
|
sa.and_(*criteria)))
|
|
466
466
|
|
|
467
467
|
|
|
468
|
+
class NumericAlchemyFilter(AlchemyFilter):
|
|
469
|
+
"""
|
|
470
|
+
SQLAlchemy filter option for a numeric data column.
|
|
471
|
+
|
|
472
|
+
Subclass of :class:`AlchemyFilter`.
|
|
473
|
+
"""
|
|
474
|
+
default_verbs = ['equal', 'not_equal',
|
|
475
|
+
'greater_than', 'greater_equal',
|
|
476
|
+
'less_than', 'less_equal']
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
class IntegerAlchemyFilter(NumericAlchemyFilter):
|
|
480
|
+
"""
|
|
481
|
+
SQLAlchemy filter option for an integer data column.
|
|
482
|
+
|
|
483
|
+
Subclass of :class:`NumericAlchemyFilter`.
|
|
484
|
+
"""
|
|
485
|
+
|
|
486
|
+
def coerce_value(self, value):
|
|
487
|
+
""" """
|
|
488
|
+
if value:
|
|
489
|
+
try:
|
|
490
|
+
return int(value)
|
|
491
|
+
except:
|
|
492
|
+
pass
|
|
493
|
+
|
|
494
|
+
|
|
468
495
|
class BooleanAlchemyFilter(AlchemyFilter):
|
|
469
496
|
"""
|
|
470
497
|
SQLAlchemy filter option for a boolean data column.
|
|
@@ -568,6 +595,8 @@ default_sqlalchemy_filters = {
|
|
|
568
595
|
None: AlchemyFilter,
|
|
569
596
|
sa.String: StringAlchemyFilter,
|
|
570
597
|
sa.Text: StringAlchemyFilter,
|
|
598
|
+
sa.Numeric: NumericAlchemyFilter,
|
|
599
|
+
sa.Integer: IntegerAlchemyFilter,
|
|
571
600
|
sa.Boolean: BooleanAlchemyFilter,
|
|
572
601
|
sa.Date: DateAlchemyFilter,
|
|
573
602
|
}
|
|
@@ -24,7 +24,10 @@
|
|
|
24
24
|
Web Handler
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
|
+
import warnings
|
|
28
|
+
|
|
27
29
|
from wuttjamaican.app import GenericHandler
|
|
30
|
+
from wuttjamaican.util import load_entry_points
|
|
28
31
|
|
|
29
32
|
from wuttaweb import static, forms, grids
|
|
30
33
|
|
|
@@ -106,22 +109,87 @@ class WebHandler(GenericHandler):
|
|
|
106
109
|
|
|
107
110
|
def get_menu_handler(self, **kwargs):
|
|
108
111
|
"""
|
|
109
|
-
Get the configured
|
|
112
|
+
Get the configured :term:`menu handler` for the web app.
|
|
110
113
|
|
|
111
114
|
Specify a custom handler in your config file like this:
|
|
112
115
|
|
|
113
116
|
.. code-block:: ini
|
|
114
117
|
|
|
115
118
|
[wutta.web]
|
|
116
|
-
menus.
|
|
119
|
+
menus.handler.spec = poser.web.menus:PoserMenuHandler
|
|
117
120
|
|
|
118
121
|
:returns: Instance of :class:`~wuttaweb.menus.MenuHandler`.
|
|
119
122
|
"""
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
spec = self.config.get(f'{self.appname}.web.menus.handler.spec')
|
|
124
|
+
if not spec:
|
|
125
|
+
spec = self.config.get(f'{self.appname}.web.menus.handler_spec')
|
|
126
|
+
if spec:
|
|
127
|
+
warnings.warn(f"setting '{self.appname}.web.menus.handler_spec' is deprecated; "
|
|
128
|
+
f"please use '{self.appname}.web.menus.handler_spec' instead",
|
|
129
|
+
DeprecationWarning)
|
|
130
|
+
else:
|
|
131
|
+
spec = self.config.get(f'{self.appname}.web.menus.handler.default_spec',
|
|
132
|
+
default='wuttaweb.menus:MenuHandler')
|
|
133
|
+
factory = self.app.load_object(spec)
|
|
134
|
+
return factory(self.config)
|
|
135
|
+
|
|
136
|
+
def get_menu_handler_specs(self, default=None):
|
|
137
|
+
"""
|
|
138
|
+
Get the :term:`spec` strings for all available :term:`menu
|
|
139
|
+
handlers <menu handler>`. See also
|
|
140
|
+
:meth:`get_menu_handler()`.
|
|
141
|
+
|
|
142
|
+
:param default: Default spec string(s) to include, even if not
|
|
143
|
+
registered. Can be a string or list of strings.
|
|
144
|
+
|
|
145
|
+
:returns: List of menu handler spec strings.
|
|
146
|
+
|
|
147
|
+
This will gather available spec strings from the following:
|
|
148
|
+
|
|
149
|
+
First, the ``default`` as provided by caller.
|
|
150
|
+
|
|
151
|
+
Second, the default spec from config, if set; for example:
|
|
152
|
+
|
|
153
|
+
.. code-block:: ini
|
|
154
|
+
|
|
155
|
+
[wutta.web]
|
|
156
|
+
menus.handler.default_spec = poser.web.menus:PoserMenuHandler
|
|
157
|
+
|
|
158
|
+
Third, each spec registered via entry points. For instance in
|
|
159
|
+
``pyproject.toml``:
|
|
160
|
+
|
|
161
|
+
.. code-block:: toml
|
|
162
|
+
|
|
163
|
+
[project.entry-points."wutta.web.menus"]
|
|
164
|
+
poser = "poser.web.menus:PoserMenuHandler"
|
|
165
|
+
|
|
166
|
+
The final list will be "sorted" according to the above, with
|
|
167
|
+
the latter registered handlers being sorted alphabetically.
|
|
168
|
+
"""
|
|
169
|
+
handlers = []
|
|
170
|
+
|
|
171
|
+
# defaults from caller
|
|
172
|
+
if isinstance(default, str):
|
|
173
|
+
handlers.append(default)
|
|
174
|
+
elif default:
|
|
175
|
+
handlers.extend(default)
|
|
176
|
+
|
|
177
|
+
# configured default, if applicable
|
|
178
|
+
default = self.config.get(f'{self.config.appname}.web.menus.handler.default_spec')
|
|
179
|
+
if default and default not in handlers:
|
|
180
|
+
handlers.append(default)
|
|
181
|
+
|
|
182
|
+
# registered via entry points
|
|
183
|
+
registered = []
|
|
184
|
+
for Handler in load_entry_points(f'{self.appname}.web.menus').values():
|
|
185
|
+
spec = Handler.get_spec()
|
|
186
|
+
if spec not in handlers:
|
|
187
|
+
registered.append(spec)
|
|
188
|
+
if registered:
|
|
189
|
+
registered.sort()
|
|
190
|
+
handlers.extend(registered)
|
|
191
|
+
|
|
192
|
+
return handlers
|
|
125
193
|
|
|
126
194
|
def make_form(self, request, **kwargs):
|
|
127
195
|
"""
|
|
@@ -41,6 +41,21 @@
|
|
|
41
41
|
</b-checkbox>
|
|
42
42
|
</b-field>
|
|
43
43
|
|
|
44
|
+
<b-field label="Menu Handler">
|
|
45
|
+
<input type="hidden"
|
|
46
|
+
name="${app.appname}.web.menus.handler.spec"
|
|
47
|
+
:value="simpleSettings['${app.appname}.web.menus.handler.spec']" />
|
|
48
|
+
<b-select v-model="simpleSettings['${app.appname}.web.menus.handler.spec']"
|
|
49
|
+
@input="settingsNeedSaved = true">
|
|
50
|
+
<option :value="null">(use default)</option>
|
|
51
|
+
<option v-for="handler in menuHandlers"
|
|
52
|
+
:key="handler.spec"
|
|
53
|
+
:value="handler.spec">
|
|
54
|
+
{{ handler.spec }}
|
|
55
|
+
</option>
|
|
56
|
+
</b-select>
|
|
57
|
+
</b-field>
|
|
58
|
+
|
|
44
59
|
</div>
|
|
45
60
|
|
|
46
61
|
<h3 class="block is-size-3">User/Auth</h3>
|
|
@@ -241,6 +256,8 @@
|
|
|
241
256
|
${parent.modify_vue_vars()}
|
|
242
257
|
<script>
|
|
243
258
|
|
|
259
|
+
ThisPageData.menuHandlers = ${json.dumps(menu_handlers)|n}
|
|
260
|
+
|
|
244
261
|
ThisPageData.weblibs = ${json.dumps(weblibs or [])|n}
|
|
245
262
|
|
|
246
263
|
ThisPageData.editWebLibraryShowDialog = false
|
|
@@ -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>
|
|
@@ -1224,7 +1224,7 @@ class MasterView(View):
|
|
|
1224
1224
|
elif simple.get('type') is bool:
|
|
1225
1225
|
value = self.config.get_bool(name, default=simple.get('default', False))
|
|
1226
1226
|
else:
|
|
1227
|
-
value = self.config.get(name)
|
|
1227
|
+
value = self.config.get(name, default=simple.get('default'))
|
|
1228
1228
|
|
|
1229
1229
|
normalized[name] = value
|
|
1230
1230
|
|
|
@@ -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
|
|
|
@@ -2677,6 +2707,9 @@ class MasterView(View):
|
|
|
2677
2707
|
represents a Wutta-based SQLAlchemy model, the return value
|
|
2678
2708
|
for this method is: ``('uuid',)``
|
|
2679
2709
|
|
|
2710
|
+
Any class mapped via SQLAlchemy should be supported
|
|
2711
|
+
automatically, the keys are determined from class inspection.
|
|
2712
|
+
|
|
2680
2713
|
But there is no "sane" default for other scenarios, in which
|
|
2681
2714
|
case subclass should define :attr:`model_key`. If the model
|
|
2682
2715
|
key cannot be determined, raises ``AttributeError``.
|
|
@@ -2691,8 +2724,12 @@ class MasterView(View):
|
|
|
2691
2724
|
|
|
2692
2725
|
model_class = cls.get_model_class()
|
|
2693
2726
|
if model_class:
|
|
2694
|
-
|
|
2695
|
-
|
|
2727
|
+
# nb. we want the primary key but must avoid column names
|
|
2728
|
+
# in case mapped class uses different prop keys
|
|
2729
|
+
inspector = sa.inspect(model_class)
|
|
2730
|
+
keys = [col.name for col in inspector.primary_key]
|
|
2731
|
+
return tuple([prop.key for prop in inspector.column_attrs
|
|
2732
|
+
if [col.name for col in prop.columns] == keys])
|
|
2696
2733
|
|
|
2697
2734
|
raise AttributeError(f"you must define model_key for view class: {cls}")
|
|
2698
2735
|
|