WuttaWeb 0.24.0__tar.gz → 0.25.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.24.0 → wuttaweb-0.25.0}/CHANGELOG.md +10 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/PKG-INFO +2 -2
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/pyproject.toml +2 -2
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/forms/schema.py +22 -3
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/forms/widgets.py +3 -1
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/appinfo/configure.mako +78 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/appinfo/index.mako +3 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/views/settings.py +48 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/forms/test_schema.py +72 -18
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/forms/test_widgets.py +29 -11
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/grids/test_base.py +16 -10
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/views/test_master.py +4 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/views/test_settings.py +20 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/.gitignore +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/.hgignore +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/.pylintrc +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/COPYING.txt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/README.md +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/Makefile +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/_static/.keepme +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.app.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.auth.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.cli.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.cli.webapp.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.conf.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.db.continuum.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.db.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.db.sess.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.diffs.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.emails.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.forms.base.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.forms.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.forms.schema.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.forms.widgets.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.grids.base.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.grids.filters.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.grids.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.handler.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.helpers.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.menus.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.progress.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.static.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.subscribers.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.util.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.auth.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.base.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.batch.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.common.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.email.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.essential.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.master.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.people.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.progress.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.reports.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.roles.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.settings.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.upgrades.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/api/wuttaweb.views.users.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/conf.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/glossary.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/index.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/make.bat +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/narr/cli/builtin.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/narr/cli/index.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/narr/templates/base.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/narr/templates/index.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/narr/templates/lookup.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/docs/narr/templates/overview.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/_version.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/app.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/auth.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/cli/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/cli/webapp.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/conf.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/db/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/db/continuum.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/db/sess.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/diffs.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/email-templates/feedback.html.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/email-templates/feedback.txt.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/emails.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/forms/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/forms/base.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/grids/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/grids/base.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/grids/filters.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/handler.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/helpers.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/menus.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/progress.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/static/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/static/img/favicon.ico +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/static/img/logo.png +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/static/img/testing.png +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/subscribers.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/auth/change_password.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/auth/login.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/base.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/base_meta.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/batch/view.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/configure.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/dateinput.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/datetimeinput.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/password.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/permissions.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/readonly/email_recips.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/select.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/textarea.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/textinput.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/deform/wutta_checked_password.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/diff.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/email/settings/view.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/forbidden.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/form.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/grids/table_element.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/grids/vue_template.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/home.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/master/configure.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/master/create.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/master/create_row.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/master/delete.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/master/edit.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/master/form.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/master/index.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/master/view.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/master/view_version.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/master/view_versions.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/notfound.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/page.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/people/view_profile.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/progress.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/reports/view.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/setup.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/themes/butterfly/base.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/themes/butterfly/buefy-components.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/themes/butterfly/buefy-plugin.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/themes/butterfly/http-plugin.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/upgrade.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/upgrades/view.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/users/view.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/templates/wutta-components.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/testing.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/util.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/views/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/views/auth.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/views/base.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/views/batch.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/views/common.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/views/email.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/views/essential.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/views/master.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/views/people.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/views/progress.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/views/reports.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/views/roles.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/views/upgrades.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/src/wuttaweb/views/users.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tasks.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/cli/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/cli/test_webapp.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/db/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/db/test_continuum.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/forms/test_base.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/grids/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/grids/test_filters.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/libcache/bb_oruga.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/libcache/bb_oruga_bulma.css +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/libcache/bb_oruga_bulma.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/libcache/bb_vue.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/libcache/bb_vue_fontawesome.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/libcache/buefy.css +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/libcache/buefy.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/libcache/fontawesome.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/libcache/vue.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/libcache/vue_resource.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/test_app.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/test_auth.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/test_diffs.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/test_emails.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/test_handler.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/test_helpers.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/test_menus.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/test_progress.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/test_static.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/test_subscribers.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/test_util.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/util.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/views/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/views/test___init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/views/test_auth.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/views/test_base.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/views/test_batch.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/views/test_common.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/views/test_email.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/views/test_essential.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/views/test_people.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/views/test_progress.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/views/test_reports.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/views/test_roles.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/views/test_upgrades.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tests/views/test_users.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.0}/tox.ini +0 -0
|
@@ -5,6 +5,16 @@ 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.25.0 (2025-12-17)
|
|
9
|
+
|
|
10
|
+
### Feat
|
|
11
|
+
|
|
12
|
+
- add "complete" (sic) timezone support
|
|
13
|
+
|
|
14
|
+
### Fix
|
|
15
|
+
|
|
16
|
+
- add local timezone awareness for datetime fields
|
|
17
|
+
|
|
8
18
|
## v0.24.0 (2025-12-15)
|
|
9
19
|
|
|
10
20
|
### Feat
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: WuttaWeb
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.25.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.26.0
|
|
43
43
|
Requires-Dist: zope-sqlalchemy>=1.5
|
|
44
44
|
Provides-Extra: continuum
|
|
45
45
|
Requires-Dist: wutta-continuum>=0.2.2; extra == 'continuum'
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "WuttaWeb"
|
|
9
|
-
version = "0.
|
|
9
|
+
version = "0.25.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.26.0",
|
|
48
48
|
"zope.sqlalchemy>=1.5",
|
|
49
49
|
]
|
|
50
50
|
|
|
@@ -48,10 +48,22 @@ class WuttaDateTime(colander.DateTime):
|
|
|
48
48
|
the Buefy datepicker + timepicker widgets.
|
|
49
49
|
"""
|
|
50
50
|
|
|
51
|
-
def
|
|
51
|
+
def serialize(self, node, appstruct):
|
|
52
|
+
if not appstruct:
|
|
53
|
+
return colander.null
|
|
54
|
+
|
|
55
|
+
request = node.widget.request
|
|
56
|
+
config = request.wutta_config
|
|
57
|
+
app = config.get_app()
|
|
58
|
+
|
|
59
|
+
dt = app.localtime(appstruct)
|
|
60
|
+
if self.format:
|
|
61
|
+
return dt.strftime(self.format)
|
|
62
|
+
return dt.isoformat()
|
|
63
|
+
|
|
64
|
+
def deserialize( # pylint: disable=inconsistent-return-statements
|
|
52
65
|
self, node, cstruct
|
|
53
66
|
):
|
|
54
|
-
""" """
|
|
55
67
|
if not cstruct:
|
|
56
68
|
return colander.null
|
|
57
69
|
|
|
@@ -60,9 +72,16 @@ class WuttaDateTime(colander.DateTime):
|
|
|
60
72
|
"%Y-%m-%dT%I:%M %p",
|
|
61
73
|
]
|
|
62
74
|
|
|
75
|
+
request = node.widget.request
|
|
76
|
+
config = request.wutta_config
|
|
77
|
+
app = config.get_app()
|
|
78
|
+
|
|
63
79
|
for fmt in formats:
|
|
64
80
|
try:
|
|
65
|
-
|
|
81
|
+
dt = datetime.datetime.strptime(cstruct, fmt)
|
|
82
|
+
if not dt.tzinfo:
|
|
83
|
+
dt = app.localtime(dt, from_utc=False)
|
|
84
|
+
return app.make_utc(dt)
|
|
66
85
|
except Exception: # pylint: disable=broad-exception-caught
|
|
67
86
|
pass
|
|
68
87
|
|
|
@@ -252,7 +252,9 @@ class WuttaDateTimeWidget(DateTimeInputWidget):
|
|
|
252
252
|
def serialize(self, field, cstruct, **kw): # pylint: disable=empty-docstring
|
|
253
253
|
""" """
|
|
254
254
|
readonly = kw.get("readonly", self.readonly)
|
|
255
|
-
if readonly
|
|
255
|
+
if readonly:
|
|
256
|
+
if not cstruct:
|
|
257
|
+
return ""
|
|
256
258
|
dt = datetime.datetime.fromisoformat(cstruct)
|
|
257
259
|
return self.app.render_datetime(dt)
|
|
258
260
|
|
|
@@ -48,6 +48,16 @@
|
|
|
48
48
|
</b-checkbox>
|
|
49
49
|
</b-field>
|
|
50
50
|
|
|
51
|
+
<b-field label="Time Zone"
|
|
52
|
+
:message="timezoneFieldMessage"
|
|
53
|
+
:type="timezoneFieldType">
|
|
54
|
+
<b-input name="${app.appname}.timezone.default"
|
|
55
|
+
v-model="simpleSettings['${app.appname}.timezone.default']"
|
|
56
|
+
## TODO: ideally could use @change here but it does not work..?
|
|
57
|
+
##@change="timezoneCheck()"
|
|
58
|
+
@input="timezoneCheck(); settingsNeedSaved = true" />
|
|
59
|
+
</b-field>
|
|
60
|
+
|
|
51
61
|
<b-field label="Menu Handler">
|
|
52
62
|
<input type="hidden"
|
|
53
63
|
name="${app.appname}.web.menus.handler.spec"
|
|
@@ -270,6 +280,74 @@
|
|
|
270
280
|
|
|
271
281
|
ThisPageData.menuHandlers = ${json.dumps(menu_handlers)|n}
|
|
272
282
|
|
|
283
|
+
ThisPageData.timezoneChecking = false
|
|
284
|
+
ThisPageData.timezoneInvalid = false
|
|
285
|
+
ThisPageData.timezoneError = false
|
|
286
|
+
|
|
287
|
+
ThisPage.computed.timezoneFieldMessage = function() {
|
|
288
|
+
if (this.timezoneChecking) {
|
|
289
|
+
return "Working, please wait..."
|
|
290
|
+
}
|
|
291
|
+
if (this.timezoneInvalid) {
|
|
292
|
+
return this.timezoneInvalid
|
|
293
|
+
}
|
|
294
|
+
if (this.timezoneError) {
|
|
295
|
+
return this.timezoneError
|
|
296
|
+
}
|
|
297
|
+
return "RESTART REQUIRED IF YOU CHANGE THIS. The system (default) timezone is: ${default_timezone}"
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
ThisPage.computed.timezoneFieldType = function() {
|
|
301
|
+
if (this.timezoneChecking) {
|
|
302
|
+
return 'is-warning'
|
|
303
|
+
}
|
|
304
|
+
if (this.timezoneInvalid || this.timezoneError) {
|
|
305
|
+
return 'is-danger'
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
ThisPage.methods.timezoneCheck = function() {
|
|
310
|
+
if (this.timezoneChecking) {
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
this.timezoneError = false
|
|
315
|
+
|
|
316
|
+
if (!this.simpleSettings['${config.appname}.timezone.default']) {
|
|
317
|
+
this.timezoneInvalid = false
|
|
318
|
+
|
|
319
|
+
} else {
|
|
320
|
+
this.timezoneChecking = true
|
|
321
|
+
const url = '${url(f"{route_prefix}.check_timezone")}'
|
|
322
|
+
const params = {
|
|
323
|
+
tzname: this.simpleSettings['${config.appname}.timezone.default'],
|
|
324
|
+
}
|
|
325
|
+
this.wuttaGET(url, params, response => {
|
|
326
|
+
this.timezoneInvalid = response.data.invalid
|
|
327
|
+
this.timezoneChecking = false
|
|
328
|
+
}, response => {
|
|
329
|
+
this.timezoneError = response?.data?.error || "unknown error"
|
|
330
|
+
this.timezoneChecking = false
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
ThisPage.methods.timezoneValidate = function() {
|
|
336
|
+
if (this.timezoneChecking) {
|
|
337
|
+
return "Still checking time zone, please try again in a moment."
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (this.timezoneError) {
|
|
341
|
+
return "Error checking time zone! Please reload page and try again."
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (this.timezoneInvalid) {
|
|
345
|
+
return "The time zone is invalid!"
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
ThisPageData.validators.push(ThisPage.methods.timezoneValidate)
|
|
350
|
+
|
|
273
351
|
ThisPageData.weblibs = ${json.dumps(weblibs or [])|n}
|
|
274
352
|
|
|
275
353
|
ThisPageData.editWebLibraryShowDialog = false
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
<b-field horizontal label="Node Title">
|
|
20
20
|
<span>${app.get_node_title()}</span>
|
|
21
21
|
</b-field>
|
|
22
|
+
<b-field horizontal label="Time Zone">
|
|
23
|
+
<span>${app.get_timezone_name()}</span>
|
|
24
|
+
</b-field>
|
|
22
25
|
<b-field horizontal label="Production Mode">
|
|
23
26
|
<span>${"Yes" if config.production() else "No"}</span>
|
|
24
27
|
</b-field>
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
Views for app settings
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
|
+
import datetime
|
|
27
28
|
import json
|
|
28
29
|
import os
|
|
29
30
|
import sys
|
|
@@ -31,6 +32,7 @@ import subprocess
|
|
|
31
32
|
from collections import OrderedDict
|
|
32
33
|
|
|
33
34
|
from wuttjamaican.db.model import Setting
|
|
35
|
+
from wuttjamaican.util import get_timezone_by_name
|
|
34
36
|
from wuttaweb.views import MasterView
|
|
35
37
|
from wuttaweb.util import get_libver, get_liburl
|
|
36
38
|
|
|
@@ -134,6 +136,7 @@ class AppInfoView(MasterView): # pylint: disable=abstract-method
|
|
|
134
136
|
{"name": f"{self.config.appname}.node_title"},
|
|
135
137
|
{"name": f"{self.config.appname}.production", "type": bool},
|
|
136
138
|
{"name": "wuttaweb.themes.expose_picker", "type": bool},
|
|
139
|
+
{"name": f"{self.config.appname}.timezone.default"},
|
|
137
140
|
{"name": f"{self.config.appname}.web.menus.handler.spec"},
|
|
138
141
|
# nb. this is deprecated; we define so it is auto-deleted
|
|
139
142
|
# when we replace with newer setting
|
|
@@ -174,12 +177,31 @@ class AppInfoView(MasterView): # pylint: disable=abstract-method
|
|
|
174
177
|
|
|
175
178
|
return simple_settings
|
|
176
179
|
|
|
180
|
+
def configure_check_timezone(self):
|
|
181
|
+
"""
|
|
182
|
+
AJAX view to validate a user-specified timezone name.
|
|
183
|
+
|
|
184
|
+
Route name for this is: ``appinfo.check_timezone``
|
|
185
|
+
"""
|
|
186
|
+
tzname = self.request.GET.get("tzname")
|
|
187
|
+
if not tzname:
|
|
188
|
+
return {"invalid": "Must provide 'tzname' parameter."}
|
|
189
|
+
try:
|
|
190
|
+
get_timezone_by_name(tzname)
|
|
191
|
+
return {"invalid": False}
|
|
192
|
+
except Exception as err: # pylint: disable=broad-exception-caught
|
|
193
|
+
return {"invalid": str(err)}
|
|
194
|
+
|
|
177
195
|
def configure_get_context( # pylint: disable=empty-docstring,arguments-differ
|
|
178
196
|
self, **kwargs
|
|
179
197
|
):
|
|
180
198
|
""" """
|
|
181
199
|
context = super().configure_get_context(**kwargs)
|
|
182
200
|
|
|
201
|
+
# default system timezone
|
|
202
|
+
dt = datetime.datetime.now().astimezone()
|
|
203
|
+
context["default_timezone"] = dt.tzname()
|
|
204
|
+
|
|
183
205
|
# add registered menu handlers
|
|
184
206
|
web = self.app.get_web_handler()
|
|
185
207
|
handlers = web.get_menu_handler_specs()
|
|
@@ -222,6 +244,32 @@ class AppInfoView(MasterView): # pylint: disable=abstract-method
|
|
|
222
244
|
|
|
223
245
|
return context
|
|
224
246
|
|
|
247
|
+
@classmethod
|
|
248
|
+
def defaults(cls, config): # pylint: disable=empty-docstring
|
|
249
|
+
""" """
|
|
250
|
+
cls._defaults(config)
|
|
251
|
+
cls._appinfo_defaults(config)
|
|
252
|
+
|
|
253
|
+
@classmethod
|
|
254
|
+
def _appinfo_defaults(cls, config):
|
|
255
|
+
route_prefix = cls.get_route_prefix()
|
|
256
|
+
permission_prefix = cls.get_permission_prefix()
|
|
257
|
+
url_prefix = cls.get_url_prefix()
|
|
258
|
+
|
|
259
|
+
# check timezone
|
|
260
|
+
config.add_route(
|
|
261
|
+
f"{route_prefix}.check_timezone",
|
|
262
|
+
f"{url_prefix}/check-timezone",
|
|
263
|
+
request_method="GET",
|
|
264
|
+
)
|
|
265
|
+
config.add_view(
|
|
266
|
+
cls,
|
|
267
|
+
attr="configure_check_timezone",
|
|
268
|
+
route_name=f"{route_prefix}.check_timezone",
|
|
269
|
+
permission=f"{permission_prefix}.configure",
|
|
270
|
+
renderer="json",
|
|
271
|
+
)
|
|
272
|
+
|
|
225
273
|
|
|
226
274
|
class SettingView(MasterView): # pylint: disable=abstract-method
|
|
227
275
|
"""
|
|
@@ -11,32 +11,86 @@ from pyramid import testing
|
|
|
11
11
|
from sqlalchemy import orm
|
|
12
12
|
|
|
13
13
|
from wuttjamaican.conf import WuttaConfig
|
|
14
|
+
from wuttjamaican.util import get_timezone_by_name
|
|
14
15
|
from wuttjamaican.testing import DataTestCase
|
|
15
16
|
from wuttaweb.forms import schema as mod
|
|
16
17
|
from wuttaweb.forms import widgets
|
|
17
18
|
from wuttaweb.testing import WebTestCase
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
class TestWuttaDateTime(
|
|
21
|
+
class TestWuttaDateTime(WebTestCase):
|
|
21
22
|
|
|
22
|
-
def
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
23
|
+
def test_serialize(self):
|
|
24
|
+
tzlocal = get_timezone_by_name("America/Los_Angeles")
|
|
25
|
+
with patch.object(self.app, "get_timezone", return_value=tzlocal):
|
|
26
|
+
typ = mod.WuttaDateTime()
|
|
27
|
+
node = colander.SchemaNode(
|
|
28
|
+
typ, widget=widgets.WuttaDateTimeWidget(self.request)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# null
|
|
32
|
+
self.assertIs(typ.serialize(node, colander.null), colander.null)
|
|
33
|
+
self.assertIs(typ.serialize(node, None), colander.null)
|
|
34
|
+
self.assertIs(typ.serialize(node, ""), colander.null)
|
|
35
|
+
|
|
36
|
+
# naive, UTC
|
|
37
|
+
result = typ.serialize(node, datetime.datetime(2024, 12, 11, 22, 33))
|
|
38
|
+
self.assertEqual(result, "2024-12-11T14:33:00-08:00")
|
|
39
|
+
|
|
40
|
+
# aware, UTC
|
|
41
|
+
result = typ.serialize(
|
|
42
|
+
node,
|
|
43
|
+
datetime.datetime(2024, 12, 11, 22, 33, tzinfo=datetime.timezone.utc),
|
|
44
|
+
)
|
|
45
|
+
self.assertEqual(result, "2024-12-11T14:33:00-08:00")
|
|
46
|
+
|
|
47
|
+
# aware, local
|
|
48
|
+
result = typ.serialize(
|
|
49
|
+
node,
|
|
50
|
+
datetime.datetime(2024, 12, 11, 14, 33, tzinfo=tzlocal),
|
|
51
|
+
)
|
|
52
|
+
self.assertEqual(result, "2024-12-11T14:33:00-08:00")
|
|
53
|
+
|
|
54
|
+
# custom format
|
|
55
|
+
typ = mod.WuttaDateTime(format="%Y-%m-%d %I:%M %p")
|
|
56
|
+
node = colander.SchemaNode(
|
|
57
|
+
typ, widget=widgets.WuttaDateTimeWidget(self.request)
|
|
58
|
+
)
|
|
59
|
+
result = typ.serialize(
|
|
60
|
+
node,
|
|
61
|
+
datetime.datetime(2024, 12, 11, 22, 33, tzinfo=datetime.timezone.utc),
|
|
62
|
+
)
|
|
63
|
+
self.assertEqual(result, "2024-12-11 02:33 PM")
|
|
38
64
|
|
|
39
|
-
|
|
65
|
+
def test_deserialize(self):
|
|
66
|
+
tzlocal = get_timezone_by_name("America/Los_Angeles")
|
|
67
|
+
with patch.object(self.app, "get_timezone", return_value=tzlocal):
|
|
68
|
+
typ = mod.WuttaDateTime()
|
|
69
|
+
node = colander.SchemaNode(
|
|
70
|
+
typ, widget=widgets.WuttaDateTimeWidget(self.request)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# null
|
|
74
|
+
self.assertIs(typ.deserialize(node, colander.null), colander.null)
|
|
75
|
+
self.assertIs(typ.deserialize(node, None), colander.null)
|
|
76
|
+
self.assertIs(typ.deserialize(node, ""), colander.null)
|
|
77
|
+
|
|
78
|
+
# format #1
|
|
79
|
+
result = typ.deserialize(node, "2024-12-11T22:33:00")
|
|
80
|
+
self.assertIsInstance(result, datetime.datetime)
|
|
81
|
+
self.assertEqual(
|
|
82
|
+
result, datetime.datetime(2024, 12, 12, 6, 33, tzinfo=None)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# format #2
|
|
86
|
+
result = typ.deserialize(node, "2024-12-11T10:33 PM")
|
|
87
|
+
self.assertIsInstance(result, datetime.datetime)
|
|
88
|
+
self.assertEqual(
|
|
89
|
+
result, datetime.datetime(2024, 12, 12, 6, 33, tzinfo=None)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# invalid
|
|
93
|
+
self.assertRaises(colander.Invalid, typ.deserialize, node, "bogus")
|
|
40
94
|
|
|
41
95
|
|
|
42
96
|
class TestObjectNode(DataTestCase):
|
|
@@ -8,6 +8,7 @@ import colander
|
|
|
8
8
|
import deform
|
|
9
9
|
from pyramid import testing
|
|
10
10
|
|
|
11
|
+
from wuttjamaican.util import get_timezone_by_name
|
|
11
12
|
from wuttaweb import grids
|
|
12
13
|
from wuttaweb.forms import widgets as mod
|
|
13
14
|
from wuttaweb.forms import schema
|
|
@@ -145,19 +146,36 @@ class TestWuttaDateTimeWidget(WebTestCase):
|
|
|
145
146
|
def make_widget(self, **kwargs):
|
|
146
147
|
return mod.WuttaDateTimeWidget(self.request, **kwargs)
|
|
147
148
|
|
|
148
|
-
def
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
149
|
+
def test_serialize_editable(self):
|
|
150
|
+
tzlocal = get_timezone_by_name("America/New_York")
|
|
151
|
+
with patch.object(self.app, "get_timezone", return_value=tzlocal):
|
|
152
|
+
widget = self.make_widget()
|
|
153
|
+
self.assertFalse(widget.readonly)
|
|
154
|
+
node = colander.SchemaNode(WuttaDateTime(), widget=widget)
|
|
155
|
+
field = self.make_field(node)
|
|
153
156
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
# nb. input data (from schema type) is always "local, zone-aware, isoformat"
|
|
158
|
+
dt = datetime.datetime(2024, 12, 12, 13, 49, tzinfo=tzlocal)
|
|
159
|
+
result = widget.serialize(field, dt.isoformat())
|
|
160
|
+
self.assertIn("<wutta-datepicker", result)
|
|
161
|
+
|
|
162
|
+
def test_serialize_readonly(self):
|
|
163
|
+
tzlocal = get_timezone_by_name("America/New_York")
|
|
164
|
+
with patch.object(self.app, "get_timezone", return_value=tzlocal):
|
|
165
|
+
widget = self.make_widget(readonly=True)
|
|
166
|
+
self.assertTrue(widget.readonly)
|
|
167
|
+
node = colander.SchemaNode(WuttaDateTime(), widget=widget)
|
|
168
|
+
field = self.make_field(node)
|
|
157
169
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
170
|
+
# null
|
|
171
|
+
self.assertEqual(widget.serialize(field, colander.null), "")
|
|
172
|
+
self.assertEqual(widget.serialize(field, None), "")
|
|
173
|
+
self.assertEqual(widget.serialize(field, ""), "")
|
|
174
|
+
|
|
175
|
+
# input data (from schema type) is always "local, zone-aware, isoformat"
|
|
176
|
+
dt = datetime.datetime(2024, 12, 12, 13, 49, tzinfo=tzlocal)
|
|
177
|
+
result = widget.serialize(field, dt.isoformat())
|
|
178
|
+
self.assertEqual(result, "2024-12-12 13:49-0500")
|
|
161
179
|
|
|
162
180
|
|
|
163
181
|
class TestWuttaMoneyInputWidget(WebTestCase):
|
|
@@ -14,6 +14,7 @@ from paginate_sqlalchemy import SqlalchemyOrmPage
|
|
|
14
14
|
from pyramid import testing
|
|
15
15
|
|
|
16
16
|
from wuttjamaican.conf import WuttaConfig
|
|
17
|
+
from wuttjamaican.util import get_timezone_by_name
|
|
17
18
|
from wuttaweb.grids import base as mod
|
|
18
19
|
from wuttaweb.grids.filters import (
|
|
19
20
|
GridFilter,
|
|
@@ -1654,16 +1655,21 @@ class TestGrid(WebTestCase):
|
|
|
1654
1655
|
self.assertEqual(result, "2025-01-13")
|
|
1655
1656
|
|
|
1656
1657
|
def test_render_datetime(self):
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1658
|
+
tzlocal = get_timezone_by_name("America/Los_Angeles")
|
|
1659
|
+
with patch.object(self.app, "get_timezone", return_value=tzlocal):
|
|
1660
|
+
grid = self.make_grid(columns=["foo", "bar"])
|
|
1661
|
+
|
|
1662
|
+
# null
|
|
1663
|
+
obj = MagicMock(dt=None)
|
|
1664
|
+
result = grid.render_datetime(obj, "dt", None)
|
|
1665
|
+
self.assertEqual(result, "")
|
|
1666
|
+
|
|
1667
|
+
# normal (naive utc)
|
|
1668
|
+
dt = datetime.datetime(2024, 12, 12, 13, 44)
|
|
1669
|
+
obj = MagicMock(dt=dt)
|
|
1670
|
+
result = grid.render_datetime(obj, "dt", str(dt))
|
|
1671
|
+
self.assertEqual(result, "2024-12-12 05:44-0800")
|
|
1672
|
+
self.assertNotEqual(result, str(dt))
|
|
1667
1673
|
|
|
1668
1674
|
def test_render_vue_tag(self):
|
|
1669
1675
|
grid = self.make_grid(columns=["foo", "bar"])
|
|
@@ -1663,6 +1663,9 @@ class TestMasterView(WebTestCase):
|
|
|
1663
1663
|
def test_configure(self):
|
|
1664
1664
|
self.pyramid_config.include("wuttaweb.views.common")
|
|
1665
1665
|
self.pyramid_config.include("wuttaweb.views.auth")
|
|
1666
|
+
self.pyramid_config.add_route(
|
|
1667
|
+
"appinfo.check_timezone", "/appinfo/check-timezone"
|
|
1668
|
+
)
|
|
1666
1669
|
model = self.app.model
|
|
1667
1670
|
|
|
1668
1671
|
# mock settings
|
|
@@ -1697,6 +1700,7 @@ class TestMasterView(WebTestCase):
|
|
|
1697
1700
|
def get_context(**kw):
|
|
1698
1701
|
kw = original_context(**kw)
|
|
1699
1702
|
kw["menu_handlers"] = []
|
|
1703
|
+
kw["default_timezone"] = "UTC"
|
|
1700
1704
|
return kw
|
|
1701
1705
|
|
|
1702
1706
|
with patch.object(view, "configure_get_context", new=get_context):
|
|
@@ -46,6 +46,26 @@ class TestAppInfoView(WebTestCase):
|
|
|
46
46
|
view = self.make_view()
|
|
47
47
|
context = view.configure_get_context()
|
|
48
48
|
|
|
49
|
+
def test_configure_check_timezone(self):
|
|
50
|
+
view = self.make_view()
|
|
51
|
+
|
|
52
|
+
# normal
|
|
53
|
+
with patch.object(self.request, "GET", new={"tzname": "America/Chicago"}):
|
|
54
|
+
result = view.configure_check_timezone()
|
|
55
|
+
self.assertFalse(result["invalid"])
|
|
56
|
+
|
|
57
|
+
# invalid
|
|
58
|
+
with patch.object(self.request, "GET", new={"tzname": "bad_name"}):
|
|
59
|
+
result = view.configure_check_timezone()
|
|
60
|
+
self.assertEqual(
|
|
61
|
+
result["invalid"], "'No time zone found with key bad_name'"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# missing input
|
|
65
|
+
with patch.object(self.request, "GET", new={}):
|
|
66
|
+
result = view.configure_check_timezone()
|
|
67
|
+
self.assertEqual(result["invalid"], "Must provide 'tzname' parameter.")
|
|
68
|
+
|
|
49
69
|
|
|
50
70
|
class TestSettingView(WebTestCase):
|
|
51
71
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|