WuttaWeb 0.24.0__tar.gz → 0.25.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.24.0 → wuttaweb-0.25.1}/CHANGELOG.md +21 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/PKG-INFO +3 -3
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/pyproject.toml +3 -3
- wuttaweb-0.25.1/src/wuttaweb/diffs.py +199 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/forms/schema.py +37 -6
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/forms/widgets.py +3 -1
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/grids/base.py +3 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/appinfo/configure.mako +78 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/appinfo/index.mako +3 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/master/view_version.mako +4 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/views/email.py +17 -5
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/views/master.py +10 -1
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/views/settings.py +48 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/forms/test_schema.py +79 -18
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/forms/test_widgets.py +29 -11
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/grids/test_base.py +28 -10
- wuttaweb-0.25.1/tests/test_diffs.py +132 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/views/test_master.py +5 -1
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/views/test_settings.py +23 -0
- wuttaweb-0.24.0/src/wuttaweb/diffs.py +0 -224
- wuttaweb-0.24.0/tests/test_diffs.py +0 -222
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/.gitignore +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/.hgignore +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/.pylintrc +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/COPYING.txt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/README.md +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/Makefile +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/_static/.keepme +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.app.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.auth.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.cli.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.cli.webapp.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.conf.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.db.continuum.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.db.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.db.sess.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.diffs.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.emails.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.forms.base.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.forms.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.forms.schema.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.forms.widgets.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.grids.base.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.grids.filters.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.grids.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.handler.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.helpers.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.menus.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.progress.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.static.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.subscribers.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.util.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.views.auth.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.views.base.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.views.batch.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.views.common.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.views.email.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.views.essential.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.views.master.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.views.people.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.views.progress.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.views.reports.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.views.roles.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.views.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.views.settings.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.views.upgrades.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/api/wuttaweb.views.users.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/conf.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/glossary.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/index.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/make.bat +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/narr/cli/builtin.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/narr/cli/index.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/narr/templates/base.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/narr/templates/index.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/narr/templates/lookup.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/docs/narr/templates/overview.rst +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/_version.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/app.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/auth.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/cli/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/cli/webapp.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/conf.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/db/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/db/continuum.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/db/sess.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/email-templates/feedback.html.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/email-templates/feedback.txt.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/emails.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/forms/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/forms/base.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/grids/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/grids/filters.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/handler.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/helpers.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/menus.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/progress.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/static/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/static/img/favicon.ico +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/static/img/logo.png +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/static/img/testing.png +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/subscribers.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/auth/change_password.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/auth/login.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/base.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/base_meta.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/batch/view.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/configure.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/dateinput.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/datetimeinput.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/password.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/permissions.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/readonly/email_recips.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/select.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/textarea.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/textinput.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/deform/wutta_checked_password.pt +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/diff.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/email/settings/view.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/forbidden.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/form.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/grids/table_element.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/grids/vue_template.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/home.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/master/configure.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/master/create.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/master/create_row.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/master/delete.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/master/edit.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/master/form.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/master/index.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/master/view.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/master/view_versions.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/notfound.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/page.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/people/view_profile.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/progress.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/reports/view.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/setup.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/themes/butterfly/base.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/themes/butterfly/buefy-components.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/themes/butterfly/buefy-plugin.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/themes/butterfly/http-plugin.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/upgrade.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/upgrades/view.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/users/view.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/templates/wutta-components.mako +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/testing.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/util.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/views/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/views/auth.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/views/base.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/views/batch.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/views/common.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/views/essential.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/views/people.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/views/progress.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/views/reports.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/views/roles.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/views/upgrades.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/src/wuttaweb/views/users.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tasks.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/cli/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/cli/test_webapp.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/db/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/db/test_continuum.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/forms/test_base.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/grids/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/grids/test_filters.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/libcache/bb_oruga.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/libcache/bb_oruga_bulma.css +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/libcache/bb_oruga_bulma.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/libcache/bb_vue.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/libcache/bb_vue_fontawesome.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/libcache/buefy.css +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/libcache/buefy.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/libcache/fontawesome.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/libcache/vue.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/libcache/vue_resource.js +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/test_app.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/test_auth.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/test_emails.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/test_handler.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/test_helpers.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/test_menus.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/test_progress.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/test_static.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/test_subscribers.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/test_util.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/util.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/views/__init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/views/test___init__.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/views/test_auth.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/views/test_base.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/views/test_batch.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/views/test_common.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/views/test_email.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/views/test_essential.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/views/test_people.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/views/test_progress.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/views/test_reports.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/views/test_roles.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/views/test_upgrades.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.1}/tests/views/test_users.py +0 -0
- {wuttaweb-0.24.0 → wuttaweb-0.25.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.25.1 (2025-12-20)
|
|
9
|
+
|
|
10
|
+
### Fix
|
|
11
|
+
|
|
12
|
+
- add `WebDiff` class now that `Diff` lives in wuttjamaican
|
|
13
|
+
- expose fallback key for email settings
|
|
14
|
+
- expose transaction comment for version history
|
|
15
|
+
- show display text for related objects, in version diff
|
|
16
|
+
- discard non-declared field values for grid vue data
|
|
17
|
+
- prevent error in DateTime schema type if no widget/request set
|
|
18
|
+
|
|
19
|
+
## v0.25.0 (2025-12-17)
|
|
20
|
+
|
|
21
|
+
### Feat
|
|
22
|
+
|
|
23
|
+
- add "complete" (sic) timezone support
|
|
24
|
+
|
|
25
|
+
### Fix
|
|
26
|
+
|
|
27
|
+
- add local timezone awareness for datetime fields
|
|
28
|
+
|
|
8
29
|
## v0.24.0 (2025-12-15)
|
|
9
30
|
|
|
10
31
|
### Feat
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: WuttaWeb
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.25.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,10 +39,10 @@ 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.27.0
|
|
43
43
|
Requires-Dist: zope-sqlalchemy>=1.5
|
|
44
44
|
Provides-Extra: continuum
|
|
45
|
-
Requires-Dist: wutta-continuum>=0.
|
|
45
|
+
Requires-Dist: wutta-continuum>=0.3.0; extra == 'continuum'
|
|
46
46
|
Provides-Extra: docs
|
|
47
47
|
Requires-Dist: furo; extra == 'docs'
|
|
48
48
|
Requires-Dist: sphinx; extra == 'docs'
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "WuttaWeb"
|
|
9
|
-
version = "0.
|
|
9
|
+
version = "0.25.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,13 +44,13 @@ dependencies = [
|
|
|
44
44
|
"pyramid_tm",
|
|
45
45
|
"waitress",
|
|
46
46
|
"WebHelpers2",
|
|
47
|
-
"WuttJamaican[db]>=0.
|
|
47
|
+
"WuttJamaican[db]>=0.27.0",
|
|
48
48
|
"zope.sqlalchemy>=1.5",
|
|
49
49
|
]
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
[project.optional-dependencies]
|
|
53
|
-
continuum = ["Wutta-Continuum>=0.
|
|
53
|
+
continuum = ["Wutta-Continuum>=0.3.0"]
|
|
54
54
|
docs = ["Sphinx", "furo", "sphinxcontrib-programoutput"]
|
|
55
55
|
tests = ["pylint", "pytest", "pytest-cov", "tox"]
|
|
56
56
|
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# -*- coding: utf-8; -*-
|
|
2
|
+
################################################################################
|
|
3
|
+
#
|
|
4
|
+
# wuttaweb -- Web App for Wutta Framework
|
|
5
|
+
# Copyright © 2024-2025 Lance Edgar
|
|
6
|
+
#
|
|
7
|
+
# This file is part of Wutta Framework.
|
|
8
|
+
#
|
|
9
|
+
# Wutta Framework is free software: you can redistribute it and/or modify it
|
|
10
|
+
# under the terms of the GNU General Public License as published by the Free
|
|
11
|
+
# Software Foundation, either version 3 of the License, or (at your option) any
|
|
12
|
+
# later version.
|
|
13
|
+
#
|
|
14
|
+
# Wutta Framework is distributed in the hope that it will be useful, but
|
|
15
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
17
|
+
# more details.
|
|
18
|
+
#
|
|
19
|
+
# You should have received a copy of the GNU General Public License along with
|
|
20
|
+
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
|
21
|
+
#
|
|
22
|
+
################################################################################
|
|
23
|
+
"""
|
|
24
|
+
Tools for displaying simple data diffs
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import sqlalchemy as sa
|
|
28
|
+
|
|
29
|
+
from pyramid.renderers import render
|
|
30
|
+
from webhelpers2.html import HTML
|
|
31
|
+
|
|
32
|
+
from wuttjamaican.diffs import Diff
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class WebDiff(Diff):
|
|
36
|
+
"""
|
|
37
|
+
Simple diff class for the web app.
|
|
38
|
+
|
|
39
|
+
This is based on the
|
|
40
|
+
:class:`~wuttjamaican:wuttjamaican.diffs.Diff` class; it just
|
|
41
|
+
tweaks :meth:`render_html()` to use the web template lookup
|
|
42
|
+
engine.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
cell_padding = None
|
|
46
|
+
|
|
47
|
+
def render_html(self, template="/diff.mako", **kwargs):
|
|
48
|
+
"""
|
|
49
|
+
Render the diff as HTML table.
|
|
50
|
+
|
|
51
|
+
:param template: Name of template to render, if you need to
|
|
52
|
+
override the default.
|
|
53
|
+
|
|
54
|
+
:param \\**kwargs: Remaining kwargs are passed as context to
|
|
55
|
+
the template renderer.
|
|
56
|
+
|
|
57
|
+
:returns: HTML literal string
|
|
58
|
+
"""
|
|
59
|
+
context = kwargs
|
|
60
|
+
context["diff"] = self
|
|
61
|
+
html = render(template, context)
|
|
62
|
+
return HTML.literal(html)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class VersionDiff(WebDiff):
|
|
66
|
+
"""
|
|
67
|
+
Special diff class for use with version history views. While
|
|
68
|
+
based on :class:`WebDiff`, this class uses a different signature
|
|
69
|
+
for the constructor.
|
|
70
|
+
|
|
71
|
+
:param config: The app :term:`config object`.
|
|
72
|
+
|
|
73
|
+
:param version: Reference to a Continuum version record object.
|
|
74
|
+
|
|
75
|
+
:param \\**kwargs: Remaining kwargs are passed as-is to the
|
|
76
|
+
:class:`WebDiff` constructor.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(self, config, version, **kwargs):
|
|
80
|
+
import sqlalchemy_continuum as continuum # pylint: disable=import-outside-toplevel
|
|
81
|
+
from wutta_continuum.util import ( # pylint: disable=import-outside-toplevel
|
|
82
|
+
render_operation_type,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
self.version = version
|
|
86
|
+
self.model_class = continuum.parent_class(type(self.version))
|
|
87
|
+
self.mapper = sa.inspect(self.model_class)
|
|
88
|
+
self.version_mapper = sa.inspect(type(self.version))
|
|
89
|
+
self.title = kwargs.pop("title", self.model_class.__name__)
|
|
90
|
+
|
|
91
|
+
self.operation_title = render_operation_type(self.version.operation_type)
|
|
92
|
+
|
|
93
|
+
if "nature" not in kwargs:
|
|
94
|
+
if (
|
|
95
|
+
version.previous
|
|
96
|
+
and version.operation_type == continuum.Operation.DELETE
|
|
97
|
+
):
|
|
98
|
+
kwargs["nature"] = "delete"
|
|
99
|
+
elif version.previous:
|
|
100
|
+
kwargs["nature"] = "update"
|
|
101
|
+
else:
|
|
102
|
+
kwargs["nature"] = "create"
|
|
103
|
+
|
|
104
|
+
if "fields" not in kwargs:
|
|
105
|
+
kwargs["fields"] = self.get_default_fields()
|
|
106
|
+
|
|
107
|
+
old_data = {}
|
|
108
|
+
new_data = {}
|
|
109
|
+
for field in kwargs["fields"]:
|
|
110
|
+
if version.previous:
|
|
111
|
+
old_data[field] = getattr(version.previous, field)
|
|
112
|
+
new_data[field] = getattr(version, field)
|
|
113
|
+
|
|
114
|
+
super().__init__(config, old_data, new_data, **kwargs)
|
|
115
|
+
|
|
116
|
+
def get_default_fields(self): # pylint: disable=missing-function-docstring
|
|
117
|
+
fields = sorted(self.version_mapper.columns.keys())
|
|
118
|
+
|
|
119
|
+
unwanted = [
|
|
120
|
+
"transaction_id",
|
|
121
|
+
"end_transaction_id",
|
|
122
|
+
"operation_type",
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
return [field for field in fields if field not in unwanted]
|
|
126
|
+
|
|
127
|
+
def render_version_value(self, version, field, value):
|
|
128
|
+
"""
|
|
129
|
+
Render the cell value HTML for a given version + field.
|
|
130
|
+
|
|
131
|
+
This method is used to render both sides of the diff (old +
|
|
132
|
+
new values). It will just render the field value using a
|
|
133
|
+
monospace font by default. However:
|
|
134
|
+
|
|
135
|
+
If the field is involved in a mapper relationship (i.e. it is
|
|
136
|
+
the "foreign key" to a related table), the logic here will
|
|
137
|
+
also (try to) traverse that show display text for the related
|
|
138
|
+
object (if found).
|
|
139
|
+
|
|
140
|
+
:param version: Reference to the Continuum version object.
|
|
141
|
+
|
|
142
|
+
:param field: Name of the field, as string.
|
|
143
|
+
|
|
144
|
+
:param value: Raw value for the field, as obtained from the
|
|
145
|
+
version object.
|
|
146
|
+
|
|
147
|
+
:returns: Rendered cell value as HTML literal
|
|
148
|
+
"""
|
|
149
|
+
# first render normal span; this is our fallback but also may
|
|
150
|
+
# be embedded within a more complex result.
|
|
151
|
+
text = HTML.tag("span", c=[repr(value)], style="font-family: monospace;")
|
|
152
|
+
|
|
153
|
+
# loop thru all mapped relationship props
|
|
154
|
+
for prop in self.mapper.relationships:
|
|
155
|
+
|
|
156
|
+
# we only want singletons
|
|
157
|
+
if prop.uselist:
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
# loop thru columns for prop
|
|
161
|
+
# nb. there should always be just one colum for a
|
|
162
|
+
# singleton prop, but technically a list is used, so no
|
|
163
|
+
# harm in looping i assume..
|
|
164
|
+
for col in prop.local_columns:
|
|
165
|
+
|
|
166
|
+
# we only want the matching column
|
|
167
|
+
if col.name != field:
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
# grab "related version" reference via prop key. this
|
|
171
|
+
# would be like a UserVersion for instance.
|
|
172
|
+
if ref := getattr(version, prop.key):
|
|
173
|
+
|
|
174
|
+
# grab "related object" reference. this would be
|
|
175
|
+
# like a User for instance.
|
|
176
|
+
if ref := getattr(ref, "version_parent", None):
|
|
177
|
+
|
|
178
|
+
# render text w/ related object as bold string
|
|
179
|
+
style = (
|
|
180
|
+
"margin-left: 2rem; font-style: italic; font-weight: bold;"
|
|
181
|
+
)
|
|
182
|
+
return HTML.tag(
|
|
183
|
+
"span",
|
|
184
|
+
c=[text, HTML.tag("span", c=[str(ref)], style=style)],
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
return text
|
|
188
|
+
|
|
189
|
+
def render_old_value(self, field):
|
|
190
|
+
if self.nature == "create":
|
|
191
|
+
return ""
|
|
192
|
+
value = self.old_value(field)
|
|
193
|
+
return self.render_version_value(self.version.previous, field, value)
|
|
194
|
+
|
|
195
|
+
def render_new_value(self, field):
|
|
196
|
+
if self.nature == "delete":
|
|
197
|
+
return ""
|
|
198
|
+
value = self.new_value(field)
|
|
199
|
+
return self.render_version_value(self.version, field, value)
|
|
@@ -31,6 +31,7 @@ import colander
|
|
|
31
31
|
import sqlalchemy as sa
|
|
32
32
|
|
|
33
33
|
from wuttjamaican.conf import parse_list
|
|
34
|
+
from wuttjamaican.util import localtime
|
|
34
35
|
|
|
35
36
|
from wuttaweb.db import Session
|
|
36
37
|
from wuttaweb.forms import widgets
|
|
@@ -38,20 +39,42 @@ from wuttaweb.forms import widgets
|
|
|
38
39
|
|
|
39
40
|
class WuttaDateTime(colander.DateTime):
|
|
40
41
|
"""
|
|
41
|
-
Custom schema type for
|
|
42
|
+
Custom schema type for :class:`~python:datetime.datetime` fields.
|
|
42
43
|
|
|
43
44
|
This should be used automatically for
|
|
44
|
-
:class
|
|
45
|
-
register another default.
|
|
45
|
+
:class:`~sqlalchemy:sqlalchemy.types.DateTime` ORM columns unless
|
|
46
|
+
you register another default.
|
|
46
47
|
|
|
47
48
|
This schema type exists for sake of convenience, when working with
|
|
48
49
|
the Buefy datepicker + timepicker widgets.
|
|
50
|
+
|
|
51
|
+
It also follows the datetime handling "rules" as outlined in
|
|
52
|
+
:doc:`wuttjamaican:narr/datetime`. On the Python side, values
|
|
53
|
+
should be naive/UTC datetime objects. On the HTTP side, values
|
|
54
|
+
will be ISO-format strings representing aware/local time.
|
|
49
55
|
"""
|
|
50
56
|
|
|
51
|
-
def
|
|
57
|
+
def serialize(self, node, appstruct):
|
|
58
|
+
if not appstruct:
|
|
59
|
+
return colander.null
|
|
60
|
+
|
|
61
|
+
# nb. request should be present when it matters
|
|
62
|
+
if node.widget and node.widget.request:
|
|
63
|
+
request = node.widget.request
|
|
64
|
+
config = request.wutta_config
|
|
65
|
+
app = config.get_app()
|
|
66
|
+
appstruct = app.localtime(appstruct)
|
|
67
|
+
else:
|
|
68
|
+
# but if not, fallback to config-less logic
|
|
69
|
+
appstruct = localtime(appstruct)
|
|
70
|
+
|
|
71
|
+
if self.format:
|
|
72
|
+
return appstruct.strftime(self.format)
|
|
73
|
+
return appstruct.isoformat()
|
|
74
|
+
|
|
75
|
+
def deserialize( # pylint: disable=inconsistent-return-statements
|
|
52
76
|
self, node, cstruct
|
|
53
77
|
):
|
|
54
|
-
""" """
|
|
55
78
|
if not cstruct:
|
|
56
79
|
return colander.null
|
|
57
80
|
|
|
@@ -60,9 +83,17 @@ class WuttaDateTime(colander.DateTime):
|
|
|
60
83
|
"%Y-%m-%dT%I:%M %p",
|
|
61
84
|
]
|
|
62
85
|
|
|
86
|
+
# nb. request is always assumed to be present here
|
|
87
|
+
request = node.widget.request
|
|
88
|
+
config = request.wutta_config
|
|
89
|
+
app = config.get_app()
|
|
90
|
+
|
|
63
91
|
for fmt in formats:
|
|
64
92
|
try:
|
|
65
|
-
|
|
93
|
+
dt = datetime.datetime.strptime(cstruct, fmt)
|
|
94
|
+
if not dt.tzinfo:
|
|
95
|
+
dt = app.localtime(dt, from_utc=False)
|
|
96
|
+
return app.make_utc(dt)
|
|
66
97
|
except Exception: # pylint: disable=broad-exception-caught
|
|
67
98
|
pass
|
|
68
99
|
|
|
@@ -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
|
|
|
@@ -2390,6 +2390,9 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth
|
|
|
2390
2390
|
# convert record to new dict
|
|
2391
2391
|
record = self.object_to_dict(record)
|
|
2392
2392
|
|
|
2393
|
+
# discard non-declared fields
|
|
2394
|
+
record = {field: record[field] for field in record if field in self.columns}
|
|
2395
|
+
|
|
2393
2396
|
# make all values safe for json
|
|
2394
2397
|
record = make_json_safe(record, warn=False)
|
|
2395
2398
|
|
|
@@ -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>
|
|
@@ -63,6 +63,7 @@ class EmailSettingView(MasterView): # pylint: disable=abstract-method
|
|
|
63
63
|
|
|
64
64
|
form_fields = [
|
|
65
65
|
"key",
|
|
66
|
+
"fallback_key",
|
|
66
67
|
"description",
|
|
67
68
|
"subject",
|
|
68
69
|
"sender",
|
|
@@ -92,9 +93,11 @@ class EmailSettingView(MasterView): # pylint: disable=abstract-method
|
|
|
92
93
|
def normalize_setting(self, setting): # pylint: disable=empty-docstring
|
|
93
94
|
""" """
|
|
94
95
|
key = setting.__name__
|
|
96
|
+
setting = setting(self.config)
|
|
95
97
|
return {
|
|
96
98
|
"key": key,
|
|
97
|
-
"
|
|
99
|
+
"fallback_key": setting.fallback_key or "",
|
|
100
|
+
"description": setting.get_description() or "",
|
|
98
101
|
"subject": self.email_handler.get_auto_subject(
|
|
99
102
|
key, rendered=False, setting=setting
|
|
100
103
|
),
|
|
@@ -158,8 +161,12 @@ class EmailSettingView(MasterView): # pylint: disable=abstract-method
|
|
|
158
161
|
f = form
|
|
159
162
|
super().configure_form(f)
|
|
160
163
|
|
|
164
|
+
# fallback_key
|
|
165
|
+
f.set_readonly("fallback_key")
|
|
166
|
+
|
|
161
167
|
# description
|
|
162
168
|
f.set_readonly("description")
|
|
169
|
+
f.set_widget("description", "notes")
|
|
163
170
|
|
|
164
171
|
# replyto
|
|
165
172
|
f.set_required("replyto", False)
|
|
@@ -247,11 +254,12 @@ class EmailSettingView(MasterView): # pylint: disable=abstract-method
|
|
|
247
254
|
if self.viewing:
|
|
248
255
|
setting = context["instance"]
|
|
249
256
|
context["setting"] = setting
|
|
257
|
+
|
|
250
258
|
context["has_html_template"] = self.email_handler.get_auto_body_template(
|
|
251
|
-
setting["key"], "html"
|
|
259
|
+
setting["key"], "html", fallback_key=setting["fallback_key"]
|
|
252
260
|
)
|
|
253
261
|
context["has_txt_template"] = self.email_handler.get_auto_body_template(
|
|
254
|
-
setting["key"], "txt"
|
|
262
|
+
setting["key"], "txt", fallback_key=setting["fallback_key"]
|
|
255
263
|
)
|
|
256
264
|
|
|
257
265
|
return super().render_to_response(template, context)
|
|
@@ -269,11 +277,15 @@ class EmailSettingView(MasterView): # pylint: disable=abstract-method
|
|
|
269
277
|
mode = self.request.params.get("mode", "html")
|
|
270
278
|
|
|
271
279
|
if mode == "txt":
|
|
272
|
-
body = self.email_handler.get_auto_txt_body(
|
|
280
|
+
body = self.email_handler.get_auto_txt_body(
|
|
281
|
+
key, context, fallback_key=setting.fallback_key
|
|
282
|
+
)
|
|
273
283
|
self.request.response.content_type = "text/plain"
|
|
274
284
|
|
|
275
285
|
else: # html
|
|
276
|
-
body = self.email_handler.get_auto_html_body(
|
|
286
|
+
body = self.email_handler.get_auto_html_body(
|
|
287
|
+
key, context, fallback_key=setting.fallback_key
|
|
288
|
+
)
|
|
277
289
|
|
|
278
290
|
self.request.response.text = body
|
|
279
291
|
return self.request.response
|
|
@@ -1145,6 +1145,7 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
|
|
1145
1145
|
"issued_at",
|
|
1146
1146
|
"user",
|
|
1147
1147
|
"remote_addr",
|
|
1148
|
+
"comment",
|
|
1148
1149
|
]
|
|
1149
1150
|
|
|
1150
1151
|
def get_version_grid_data(self, instance):
|
|
@@ -1197,6 +1198,14 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
|
|
1197
1198
|
# remote_addr
|
|
1198
1199
|
g.set_label("remote_addr", "IP Address")
|
|
1199
1200
|
|
|
1201
|
+
# comment
|
|
1202
|
+
g.set_renderer("comment", self.render_version_comment)
|
|
1203
|
+
|
|
1204
|
+
def render_version_comment( # pylint: disable=missing-function-docstring,unused-argument
|
|
1205
|
+
self, txn, key, value
|
|
1206
|
+
):
|
|
1207
|
+
return txn.meta.get("comment", "")
|
|
1208
|
+
|
|
1200
1209
|
def view_version(self): # pylint: disable=too-many-locals
|
|
1201
1210
|
"""
|
|
1202
1211
|
View to show diff details for a particular object version.
|
|
@@ -1260,7 +1269,7 @@ class MasterView(View): # pylint: disable=too-many-public-methods
|
|
|
1260
1269
|
)
|
|
1261
1270
|
|
|
1262
1271
|
version_diffs = [
|
|
1263
|
-
VersionDiff(version)
|
|
1272
|
+
VersionDiff(self.config, version)
|
|
1264
1273
|
for version in self.get_relevant_versions(txn, instance)
|
|
1265
1274
|
]
|
|
1266
1275
|
|
|
@@ -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
|
"""
|