WuttaWeb 0.20.0__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.20.0 → wuttaweb-0.20.1}/CHANGELOG.md +9 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/PKG-INFO +2 -2
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/pyproject.toml +5 -2
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/grids/filters.py +29 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/handler.py +75 -7
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/appinfo/configure.mako +17 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/views/master.py +10 -3
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/views/settings.py +12 -4
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/grids/test_filters.py +24 -0
- wuttaweb-0.20.1/tests/test_handler.py +149 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/views/test_master.py +42 -34
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/views/test_reports.py +7 -2
- wuttaweb-0.20.0/tests/test_handler.py +0 -76
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/.gitignore +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/COPYING.txt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/README.md +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/Makefile +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/_static/.keepme +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.app.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.auth.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.cli.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.cli.webapp.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.conf.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.db.continuum.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.db.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.db.sess.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.emails.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.forms.base.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.forms.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.forms.schema.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.forms.widgets.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.grids.base.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.grids.filters.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.grids.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.handler.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.helpers.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.menus.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.progress.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.static.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.subscribers.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.util.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.auth.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.base.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.batch.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.common.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.email.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.essential.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.master.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.people.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.progress.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.reports.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.roles.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.settings.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.upgrades.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.users.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/conf.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/glossary.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/index.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/make.bat +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/narr/cli/builtin.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/narr/cli/index.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/narr/templates/base.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/narr/templates/index.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/narr/templates/lookup.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/docs/narr/templates/overview.rst +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/__init__.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/_version.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/app.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/auth.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/cli/__init__.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/cli/webapp.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/conf.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/db/__init__.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/db/continuum.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/db/sess.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/email-templates/feedback.html.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/email-templates/feedback.txt.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/emails.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/forms/__init__.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/forms/base.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/forms/schema.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/forms/widgets.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/grids/__init__.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/grids/base.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/helpers.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/menus.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/progress.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/static/__init__.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/static/img/favicon.ico +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/static/img/logo.png +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/static/img/testing.png +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/subscribers.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/appinfo/index.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/auth/change_password.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/auth/login.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/base.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/base_meta.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/batch/view.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/configure.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/dateinput.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/datetimeinput.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/password.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/permissions.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/email_recips.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/select.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/textarea.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/textinput.pt +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/email/settings/view.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/forbidden.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/form.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/grids/table_element.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/grids/vue_template.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/home.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/configure.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/create.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/delete.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/edit.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/form.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/index.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/view.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/notfound.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/page.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/people/view_profile.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/progress.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/reports/view.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/setup.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/upgrade.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/upgrades/view.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/templates/wutta-components.mako +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/testing.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/util.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/views/__init__.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/views/auth.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/views/base.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/views/batch.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/views/common.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/views/email.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/views/essential.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/views/people.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/views/progress.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/views/reports.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/views/roles.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/views/upgrades.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/src/wuttaweb/views/users.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tasks.py +0 -0
- {wuttaweb-0.20.0/tests/views → wuttaweb-0.20.1/tests}/__init__.py +0 -0
- {wuttaweb-0.20.0/tests/grids → wuttaweb-0.20.1/tests/cli}/__init__.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/cli/test_webapp.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/db/__init__.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/db/test_continuum.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/forms/test_base.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/forms/test_schema.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/forms/test_widgets.py +0 -0
- {wuttaweb-0.20.0/tests/cli → wuttaweb-0.20.1/tests/grids}/__init__.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/grids/test_base.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/libcache/bb_oruga.js +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/libcache/bb_oruga_bulma.css +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/libcache/bb_oruga_bulma.js +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/libcache/bb_vue.js +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/libcache/bb_vue_fontawesome.js +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/libcache/buefy.css +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/libcache/buefy.js +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/libcache/fontawesome.js +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/libcache/vue.js +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/libcache/vue_resource.js +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/test_app.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/test_auth.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/test_emails.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/test_helpers.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/test_menus.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/test_progress.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/test_static.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/test_subscribers.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/test_util.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/util.py +0 -0
- {wuttaweb-0.20.0/tests → wuttaweb-0.20.1/tests/views}/__init__.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/views/test___init__.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/views/test_auth.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/views/test_base.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/views/test_batch.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/views/test_common.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/views/test_email.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/views/test_essential.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/views/test_people.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/views/test_progress.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/views/test_roles.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/views/test_settings.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/views/test_upgrades.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tests/views/test_users.py +0 -0
- {wuttaweb-0.20.0 → wuttaweb-0.20.1}/tox.ini +0 -0
|
@@ -5,6 +5,15 @@ 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
|
+
|
|
8
17
|
## v0.20.0 (2025-01-11)
|
|
9
18
|
|
|
10
19
|
### Feat
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: WuttaWeb
|
|
3
|
-
Version: 0.20.
|
|
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.20.
|
|
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'
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "WuttaWeb"
|
|
9
|
-
version = "0.20.
|
|
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.20.
|
|
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/"
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -2707,6 +2707,9 @@ class MasterView(View):
|
|
|
2707
2707
|
represents a Wutta-based SQLAlchemy model, the return value
|
|
2708
2708
|
for this method is: ``('uuid',)``
|
|
2709
2709
|
|
|
2710
|
+
Any class mapped via SQLAlchemy should be supported
|
|
2711
|
+
automatically, the keys are determined from class inspection.
|
|
2712
|
+
|
|
2710
2713
|
But there is no "sane" default for other scenarios, in which
|
|
2711
2714
|
case subclass should define :attr:`model_key`. If the model
|
|
2712
2715
|
key cannot be determined, raises ``AttributeError``.
|
|
@@ -2721,8 +2724,12 @@ class MasterView(View):
|
|
|
2721
2724
|
|
|
2722
2725
|
model_class = cls.get_model_class()
|
|
2723
2726
|
if model_class:
|
|
2724
|
-
|
|
2725
|
-
|
|
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])
|
|
2726
2733
|
|
|
2727
2734
|
raise AttributeError(f"you must define model_key for view class: {cls}")
|
|
2728
2735
|
|
|
@@ -129,6 +129,10 @@ class AppInfoView(MasterView):
|
|
|
129
129
|
{'name': f'{self.config.appname}.node_title'},
|
|
130
130
|
{'name': f'{self.config.appname}.production',
|
|
131
131
|
'type': bool},
|
|
132
|
+
{'name': f'{self.config.appname}.web.menus.handler.spec'},
|
|
133
|
+
# nb. this is deprecated; we define so it is auto-deleted
|
|
134
|
+
# when we replace with newer setting
|
|
135
|
+
{'name': f'{self.config.appname}.web.menus.handler_spec'},
|
|
132
136
|
|
|
133
137
|
# user/auth
|
|
134
138
|
{'name': 'wuttaweb.home_redirect_to_login',
|
|
@@ -164,11 +168,15 @@ class AppInfoView(MasterView):
|
|
|
164
168
|
|
|
165
169
|
def configure_get_context(self, **kwargs):
|
|
166
170
|
""" """
|
|
167
|
-
|
|
168
|
-
# normal context
|
|
169
171
|
context = super().configure_get_context(**kwargs)
|
|
170
172
|
|
|
171
|
-
#
|
|
173
|
+
# add registered menu handlers
|
|
174
|
+
web = self.app.get_web_handler()
|
|
175
|
+
handlers = web.get_menu_handler_specs()
|
|
176
|
+
handlers = [{'spec': spec} for spec in handlers]
|
|
177
|
+
context['menu_handlers'] = handlers
|
|
178
|
+
|
|
179
|
+
# add `weblibs` to context, based on config values
|
|
172
180
|
weblibs = self.get_weblibs()
|
|
173
181
|
for key in weblibs:
|
|
174
182
|
title = weblibs[key]
|
|
@@ -192,8 +200,8 @@ class AppInfoView(MasterView):
|
|
|
192
200
|
'live_url': get_liburl(self.request, key,
|
|
193
201
|
prefix=self.weblib_config_prefix),
|
|
194
202
|
}
|
|
195
|
-
|
|
196
203
|
context['weblibs'] = list(weblibs.values())
|
|
204
|
+
|
|
197
205
|
return context
|
|
198
206
|
|
|
199
207
|
|
|
@@ -326,6 +326,30 @@ class TestStringAlchemyFilter(WebTestCase):
|
|
|
326
326
|
self.assertEqual(filtered_query.count(), 6)
|
|
327
327
|
|
|
328
328
|
|
|
329
|
+
class TestIntegerAlchemyFilter(WebTestCase):
|
|
330
|
+
|
|
331
|
+
def make_filter(self, model_property, **kwargs):
|
|
332
|
+
factory = kwargs.pop('factory', mod.IntegerAlchemyFilter)
|
|
333
|
+
kwargs['model_property'] = model_property
|
|
334
|
+
return factory(self.request, model_property.key, **kwargs)
|
|
335
|
+
|
|
336
|
+
def test_coerce_value(self):
|
|
337
|
+
model = self.app.model
|
|
338
|
+
filtr = self.make_filter(model.Upgrade.exit_code)
|
|
339
|
+
|
|
340
|
+
# null
|
|
341
|
+
self.assertIsNone(filtr.coerce_value(None))
|
|
342
|
+
self.assertIsNone(filtr.coerce_value(''))
|
|
343
|
+
|
|
344
|
+
# typical
|
|
345
|
+
self.assertEqual(filtr.coerce_value('42'), 42)
|
|
346
|
+
self.assertEqual(filtr.coerce_value('-42'), -42)
|
|
347
|
+
|
|
348
|
+
# invalid
|
|
349
|
+
self.assertIsNone(filtr.coerce_value('42.12'))
|
|
350
|
+
self.assertIsNone(filtr.coerce_value('bogus'))
|
|
351
|
+
|
|
352
|
+
|
|
329
353
|
class TestBooleanAlchemyFilter(WebTestCase):
|
|
330
354
|
|
|
331
355
|
def setUp(self):
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# -*- coding: utf-8; -*-
|
|
2
|
+
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
|
|
5
|
+
from wuttaweb import handler as mod, static
|
|
6
|
+
from wuttaweb.forms import Form
|
|
7
|
+
from wuttaweb.grids import Grid
|
|
8
|
+
from wuttaweb.menus import MenuHandler
|
|
9
|
+
from wuttaweb.testing import WebTestCase
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MockMenuHandler(MenuHandler):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
class LegacyMenuHandler(MenuHandler):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
class AnotherMenuHandler(MenuHandler):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TestWebHandler(WebTestCase):
|
|
23
|
+
|
|
24
|
+
def make_handler(self):
|
|
25
|
+
return mod.WebHandler(self.config)
|
|
26
|
+
|
|
27
|
+
def test_get_fanstatic_url(self):
|
|
28
|
+
handler = self.make_handler()
|
|
29
|
+
|
|
30
|
+
# default with / root path
|
|
31
|
+
url = handler.get_fanstatic_url(self.request, static.logo)
|
|
32
|
+
self.assertEqual(url, '/fanstatic/wuttaweb_img/logo.png')
|
|
33
|
+
|
|
34
|
+
# what about a subpath
|
|
35
|
+
self.request.script_name = '/testing'
|
|
36
|
+
url = handler.get_fanstatic_url(self.request, static.logo)
|
|
37
|
+
self.assertEqual(url, '/testing/fanstatic/wuttaweb_img/logo.png')
|
|
38
|
+
|
|
39
|
+
def test_get_favicon_url(self):
|
|
40
|
+
handler = self.make_handler()
|
|
41
|
+
|
|
42
|
+
# default
|
|
43
|
+
url = handler.get_favicon_url(self.request)
|
|
44
|
+
self.assertEqual(url, '/fanstatic/wuttaweb_img/favicon.ico')
|
|
45
|
+
|
|
46
|
+
# config override
|
|
47
|
+
self.config.setdefault('wuttaweb.favicon_url', '/testing/other.ico')
|
|
48
|
+
url = handler.get_favicon_url(self.request)
|
|
49
|
+
self.assertEqual(url, '/testing/other.ico')
|
|
50
|
+
|
|
51
|
+
def test_get_header_logo_url(self):
|
|
52
|
+
handler = self.make_handler()
|
|
53
|
+
|
|
54
|
+
# default
|
|
55
|
+
url = handler.get_header_logo_url(self.request)
|
|
56
|
+
self.assertEqual(url, '/fanstatic/wuttaweb_img/favicon.ico')
|
|
57
|
+
|
|
58
|
+
# config override
|
|
59
|
+
self.config.setdefault('wuttaweb.header_logo_url', '/testing/header.png')
|
|
60
|
+
url = handler.get_header_logo_url(self.request)
|
|
61
|
+
self.assertEqual(url, '/testing/header.png')
|
|
62
|
+
|
|
63
|
+
def test_get_main_logo_url(self):
|
|
64
|
+
handler = self.make_handler()
|
|
65
|
+
|
|
66
|
+
# default
|
|
67
|
+
url = handler.get_main_logo_url(self.request)
|
|
68
|
+
self.assertEqual(url, '/fanstatic/wuttaweb_img/logo.png')
|
|
69
|
+
|
|
70
|
+
# config override
|
|
71
|
+
self.config.setdefault('wuttaweb.logo_url', '/testing/other.png')
|
|
72
|
+
url = handler.get_main_logo_url(self.request)
|
|
73
|
+
self.assertEqual(url, '/testing/other.png')
|
|
74
|
+
|
|
75
|
+
def test_get_menu_handler(self):
|
|
76
|
+
handler = self.make_handler()
|
|
77
|
+
|
|
78
|
+
# built-in default
|
|
79
|
+
menus = handler.get_menu_handler()
|
|
80
|
+
self.assertIsInstance(menus, MenuHandler)
|
|
81
|
+
self.assertIs(type(menus), MenuHandler)
|
|
82
|
+
|
|
83
|
+
# configured default
|
|
84
|
+
self.config.setdefault('wutta.web.menus.handler.default_spec',
|
|
85
|
+
'tests.test_handler:MockMenuHandler')
|
|
86
|
+
menus = handler.get_menu_handler()
|
|
87
|
+
self.assertIsInstance(menus, MockMenuHandler)
|
|
88
|
+
|
|
89
|
+
# configured handler (legacy)
|
|
90
|
+
self.config.setdefault('wutta.web.menus.handler_spec',
|
|
91
|
+
'tests.test_handler:LegacyMenuHandler')
|
|
92
|
+
menus = handler.get_menu_handler()
|
|
93
|
+
self.assertIsInstance(menus, LegacyMenuHandler)
|
|
94
|
+
|
|
95
|
+
# configued handler (proper)
|
|
96
|
+
self.config.setdefault('wutta.web.menus.handler.spec',
|
|
97
|
+
'tests.test_handler:AnotherMenuHandler')
|
|
98
|
+
menus = handler.get_menu_handler()
|
|
99
|
+
self.assertIsInstance(menus, AnotherMenuHandler)
|
|
100
|
+
|
|
101
|
+
def test_get_menu_handler_specs(self):
|
|
102
|
+
handler = self.make_handler()
|
|
103
|
+
|
|
104
|
+
# at least one spec by default
|
|
105
|
+
specs = handler.get_menu_handler_specs()
|
|
106
|
+
self.assertIn('wuttaweb.menus:MenuHandler', specs)
|
|
107
|
+
|
|
108
|
+
# caller can specify default as string
|
|
109
|
+
specs = handler.get_menu_handler_specs(default='tests.test_handler:MockMenuHandler')
|
|
110
|
+
self.assertIn('wuttaweb.menus:MenuHandler', specs)
|
|
111
|
+
self.assertIn('tests.test_handler:MockMenuHandler', specs)
|
|
112
|
+
self.assertNotIn('tests.test_handler:AnotherMenuHandler', specs)
|
|
113
|
+
|
|
114
|
+
# caller can specify default as list
|
|
115
|
+
specs = handler.get_menu_handler_specs(default=[
|
|
116
|
+
'tests.test_handler:MockMenuHandler',
|
|
117
|
+
'tests.test_handler:AnotherMenuHandler'])
|
|
118
|
+
self.assertIn('wuttaweb.menus:MenuHandler', specs)
|
|
119
|
+
self.assertIn('tests.test_handler:MockMenuHandler', specs)
|
|
120
|
+
self.assertIn('tests.test_handler:AnotherMenuHandler', specs)
|
|
121
|
+
|
|
122
|
+
# default can be configured
|
|
123
|
+
self.config.setdefault('wutta.web.menus.handler.default_spec',
|
|
124
|
+
'tests.test_handler:AnotherMenuHandler')
|
|
125
|
+
specs = handler.get_menu_handler_specs()
|
|
126
|
+
self.assertIn('wuttaweb.menus:MenuHandler', specs)
|
|
127
|
+
self.assertNotIn('tests.test_handler:MockMenuHandler', specs)
|
|
128
|
+
self.assertIn('tests.test_handler:AnotherMenuHandler', specs)
|
|
129
|
+
|
|
130
|
+
# the rest come from entry points
|
|
131
|
+
with patch.object(mod, 'load_entry_points', return_value={
|
|
132
|
+
'legacy': LegacyMenuHandler,
|
|
133
|
+
}):
|
|
134
|
+
specs = handler.get_menu_handler_specs()
|
|
135
|
+
self.assertNotIn('wuttaweb.menus:MenuHandler', specs)
|
|
136
|
+
self.assertNotIn('tests.test_handler:MockMenuHandler', specs)
|
|
137
|
+
self.assertIn('tests.test_handler:LegacyMenuHandler', specs)
|
|
138
|
+
# nb. this remains from previous config default
|
|
139
|
+
self.assertIn('tests.test_handler:AnotherMenuHandler', specs)
|
|
140
|
+
|
|
141
|
+
def test_make_form(self):
|
|
142
|
+
handler = self.make_handler()
|
|
143
|
+
form = handler.make_form(self.request)
|
|
144
|
+
self.assertIsInstance(form, Form)
|
|
145
|
+
|
|
146
|
+
def test_make_grid(self):
|
|
147
|
+
handler = self.make_handler()
|
|
148
|
+
grid = handler.make_grid(self.request)
|
|
149
|
+
self.assertIsInstance(grid, Grid)
|
|
@@ -1528,42 +1528,50 @@ class TestMasterView(WebTestCase):
|
|
|
1528
1528
|
get_index_url=MagicMock(return_value='/appinfo/'),
|
|
1529
1529
|
configure_get_simple_settings=MagicMock(return_value=settings)):
|
|
1530
1530
|
|
|
1531
|
-
#
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1531
|
+
# nb. appinfo/configure template requires menu_handlers
|
|
1532
|
+
original_context = view.configure_get_context
|
|
1533
|
+
def get_context(**kw):
|
|
1534
|
+
kw = original_context(**kw)
|
|
1535
|
+
kw['menu_handlers'] = []
|
|
1536
|
+
return kw
|
|
1537
|
+
with patch.object(view, 'configure_get_context', new=get_context):
|
|
1538
|
+
|
|
1539
|
+
# get the form page
|
|
1540
|
+
response = view.configure(session=self.session)
|
|
1541
|
+
self.assertIsInstance(response, Response)
|
|
1542
|
+
|
|
1543
|
+
# post request to save settings
|
|
1544
|
+
self.request.method = 'POST'
|
|
1545
|
+
self.request.POST = {
|
|
1546
|
+
'wutta.app_title': 'Wutta',
|
|
1547
|
+
'wutta.foo': 'bar',
|
|
1548
|
+
'wutta.flag': 'true',
|
|
1549
|
+
}
|
|
1550
|
+
response = view.configure(session=self.session)
|
|
1551
|
+
# nb. should get redirect back to configure page
|
|
1552
|
+
self.assertEqual(response.status_code, 302)
|
|
1545
1553
|
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1554
|
+
# should now have 5 settings
|
|
1555
|
+
count = self.session.query(model.Setting).count()
|
|
1556
|
+
self.assertEqual(count, 5)
|
|
1557
|
+
get_setting = functools.partial(self.app.get_setting, self.session)
|
|
1558
|
+
self.assertEqual(get_setting('wutta.app_title'), 'Wutta')
|
|
1559
|
+
self.assertEqual(get_setting('wutta.foo'), 'bar')
|
|
1560
|
+
self.assertEqual(get_setting('wutta.flag'), 'true')
|
|
1561
|
+
self.assertEqual(get_setting('wutta.number'), '42')
|
|
1562
|
+
self.assertEqual(get_setting('wutta.value1'), '')
|
|
1563
|
+
self.assertEqual(get_setting('wutta.value2'), None)
|
|
1564
|
+
|
|
1565
|
+
# post request to remove settings
|
|
1566
|
+
self.request.method = 'POST'
|
|
1567
|
+
self.request.POST = {'remove_settings': '1'}
|
|
1568
|
+
response = view.configure(session=self.session)
|
|
1569
|
+
# nb. should get redirect back to configure page
|
|
1570
|
+
self.assertEqual(response.status_code, 302)
|
|
1563
1571
|
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1572
|
+
# should now have 0 settings
|
|
1573
|
+
count = self.session.query(model.Setting).count()
|
|
1574
|
+
self.assertEqual(count, 0)
|
|
1567
1575
|
|
|
1568
1576
|
def test_configure_gather_settings(self):
|
|
1569
1577
|
view = self.make_view()
|
|
@@ -51,8 +51,13 @@ class TestReportViews(WebTestCase):
|
|
|
51
51
|
|
|
52
52
|
def test_get_grid_data(self):
|
|
53
53
|
view = self.make_view()
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
providers = dict(self.app.providers)
|
|
55
|
+
providers['wuttatest'] = MagicMock(report_modules=['tests.views.test_reports'])
|
|
56
|
+
with patch.object(self.app, 'providers', new=providers):
|
|
57
|
+
|
|
58
|
+
data = view.get_grid_data()
|
|
59
|
+
self.assertIsInstance(data, list)
|
|
60
|
+
self.assertTrue(data) # 1+ reports
|
|
56
61
|
|
|
57
62
|
def test_normalize_report(self):
|
|
58
63
|
view = self.make_view()
|