WuttaWeb 0.23.2__tar.gz → 0.24.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.23.2 → wuttaweb-0.24.0}/CHANGELOG.md +17 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/PKG-INFO +3 -3
- wuttaweb-0.24.0/docs/api/wuttaweb.diffs.rst +6 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/index.rst +1 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/pyproject.toml +9 -3
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/db/sess.py +2 -2
- wuttaweb-0.24.0/src/wuttaweb/diffs.py +224 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/grids/base.py +4 -2
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/handler.py +1 -1
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/subscribers.py +12 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/base.mako +32 -3
- wuttaweb-0.24.0/src/wuttaweb/templates/diff.mako +15 -0
- wuttaweb-0.24.0/src/wuttaweb/templates/master/create_row.mako +4 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/master/view.mako +12 -0
- wuttaweb-0.24.0/src/wuttaweb/templates/master/view_version.mako +35 -0
- wuttaweb-0.24.0/src/wuttaweb/templates/master/view_versions.mako +20 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/testing.py +67 -1
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/views/batch.py +87 -5
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/views/master.py +716 -17
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/views/upgrades.py +1 -2
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tasks.py +2 -1
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/forms/test_schema.py +2 -1
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/grids/test_filters.py +0 -4
- wuttaweb-0.24.0/tests/test_diffs.py +222 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/test_handler.py +3 -3
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/test_subscribers.py +13 -0
- wuttaweb-0.24.0/tests/util.py +15 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/views/test_batch.py +132 -39
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/views/test_master.py +453 -7
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tox.ini +7 -2
- wuttaweb-0.23.2/tests/util.py +0 -43
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/.gitignore +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/.hgignore +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/.pylintrc +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/COPYING.txt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/README.md +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/Makefile +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/_static/.keepme +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.app.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.auth.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.cli.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.cli.webapp.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.conf.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.db.continuum.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.db.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.db.sess.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.emails.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.forms.base.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.forms.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.forms.schema.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.forms.widgets.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.grids.base.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.grids.filters.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.grids.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.handler.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.helpers.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.menus.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.progress.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.static.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.subscribers.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.util.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.views.auth.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.views.base.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.views.batch.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.views.common.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.views.email.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.views.essential.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.views.master.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.views.people.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.views.progress.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.views.reports.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.views.roles.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.views.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.views.settings.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.views.upgrades.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/api/wuttaweb.views.users.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/conf.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/glossary.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/make.bat +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/narr/cli/builtin.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/narr/cli/index.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/narr/templates/base.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/narr/templates/index.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/narr/templates/lookup.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/docs/narr/templates/overview.rst +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/__init__.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/_version.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/app.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/auth.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/cli/__init__.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/cli/webapp.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/conf.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/db/__init__.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/db/continuum.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/email-templates/feedback.html.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/email-templates/feedback.txt.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/emails.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/forms/__init__.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/forms/base.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/forms/schema.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/forms/widgets.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/grids/__init__.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/grids/filters.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/helpers.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/menus.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/progress.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/static/__init__.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/static/img/favicon.ico +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/static/img/logo.png +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/static/img/testing.png +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/appinfo/index.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/auth/change_password.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/auth/login.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/base_meta.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/batch/view.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/configure.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/dateinput.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/datetimeinput.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/password.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/permissions.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/readonly/email_recips.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/select.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/textarea.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/textinput.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/deform/wutta_checked_password.pt +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/email/settings/view.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/forbidden.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/form.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/grids/table_element.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/grids/vue_template.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/home.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/master/configure.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/master/create.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/master/delete.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/master/edit.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/master/form.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/master/index.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/notfound.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/page.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/people/view_profile.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/progress.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/reports/view.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/setup.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/themes/butterfly/base.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/themes/butterfly/buefy-components.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/themes/butterfly/buefy-plugin.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/themes/butterfly/http-plugin.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/upgrade.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/upgrades/view.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/users/view.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/templates/wutta-components.mako +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/util.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/views/__init__.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/views/auth.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/views/base.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/views/common.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/views/email.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/views/essential.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/views/people.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/views/progress.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/views/reports.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/views/roles.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/views/settings.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/src/wuttaweb/views/users.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/__init__.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/cli/__init__.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/cli/test_webapp.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/db/__init__.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/db/test_continuum.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/forms/test_base.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/forms/test_widgets.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/grids/__init__.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/grids/test_base.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/libcache/bb_oruga.js +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/libcache/bb_oruga_bulma.css +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/libcache/bb_oruga_bulma.js +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/libcache/bb_vue.js +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/libcache/bb_vue_fontawesome.js +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/libcache/buefy.css +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/libcache/buefy.js +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/libcache/fontawesome.js +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/libcache/vue.js +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/libcache/vue_resource.js +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/test_app.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/test_auth.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/test_emails.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/test_helpers.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/test_menus.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/test_progress.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/test_static.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/test_util.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/views/__init__.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/views/test___init__.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/views/test_auth.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/views/test_base.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/views/test_common.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/views/test_email.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/views/test_essential.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/views/test_people.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/views/test_progress.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/views/test_reports.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/views/test_roles.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/views/test_settings.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/views/test_upgrades.py +0 -0
- {wuttaweb-0.23.2 → wuttaweb-0.24.0}/tests/views/test_users.py +0 -0
|
@@ -5,6 +5,23 @@ 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.24.0 (2025-12-15)
|
|
9
|
+
|
|
10
|
+
### Feat
|
|
11
|
+
|
|
12
|
+
- basic support for displaying version history
|
|
13
|
+
|
|
14
|
+
### Fix
|
|
15
|
+
|
|
16
|
+
- use UTC when updating timestamp in DB
|
|
17
|
+
- workaround error when 'fanstatic.needed' missing from environ
|
|
18
|
+
- workaround error when 'fanstatic.needed' missing from environ
|
|
19
|
+
- address pylint warnings
|
|
20
|
+
- add basic `create_row()` support, esp. for batch views
|
|
21
|
+
- update dependencies for wuttjamaican, wutta-continuum
|
|
22
|
+
- make master view auto-detect continuum versioning for model class
|
|
23
|
+
- fix 'invalid-name' for pylint
|
|
24
|
+
|
|
8
25
|
## v0.23.2 (2025-10-19)
|
|
9
26
|
|
|
10
27
|
### Fix
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: WuttaWeb
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.24.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,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.25.0
|
|
43
43
|
Requires-Dist: zope-sqlalchemy>=1.5
|
|
44
44
|
Provides-Extra: continuum
|
|
45
|
-
Requires-Dist: wutta-continuum; extra == 'continuum'
|
|
45
|
+
Requires-Dist: wutta-continuum>=0.2.2; 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.24.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,13 +44,13 @@ dependencies = [
|
|
|
44
44
|
"pyramid_tm",
|
|
45
45
|
"waitress",
|
|
46
46
|
"WebHelpers2",
|
|
47
|
-
"WuttJamaican[db]>=0.
|
|
47
|
+
"WuttJamaican[db]>=0.25.0",
|
|
48
48
|
"zope.sqlalchemy>=1.5",
|
|
49
49
|
]
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
[project.optional-dependencies]
|
|
53
|
-
continuum = ["Wutta-Continuum"]
|
|
53
|
+
continuum = ["Wutta-Continuum>=0.2.2"]
|
|
54
54
|
docs = ["Sphinx", "furo", "sphinxcontrib-programoutput"]
|
|
55
55
|
tests = ["pylint", "pytest", "pytest-cov", "tox"]
|
|
56
56
|
|
|
@@ -91,3 +91,9 @@ update_changelog_on_bump = true
|
|
|
91
91
|
exclude = [
|
|
92
92
|
"htmlcov/",
|
|
93
93
|
]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
[tool.pytest.ini_options]
|
|
97
|
+
markers = [
|
|
98
|
+
"versioned: tests with SQLAlchemy-Continuum versioning feature enabled",
|
|
99
|
+
]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
################################################################################
|
|
3
3
|
#
|
|
4
4
|
# wuttaweb -- Web App for Wutta Framework
|
|
5
|
-
# Copyright © 2024 Lance Edgar
|
|
5
|
+
# Copyright © 2024-2025 Lance Edgar
|
|
6
6
|
#
|
|
7
7
|
# This file is part of Wutta Framework.
|
|
8
8
|
#
|
|
@@ -61,6 +61,6 @@ from sqlalchemy import orm
|
|
|
61
61
|
from zope.sqlalchemy.datamanager import register
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
Session = orm.scoped_session(orm.sessionmaker())
|
|
64
|
+
Session = orm.scoped_session(orm.sessionmaker()) # pylint: disable=invalid-name
|
|
65
65
|
|
|
66
66
|
register(Session)
|
|
@@ -0,0 +1,224 @@
|
|
|
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
|
+
|
|
33
|
+
class Diff:
|
|
34
|
+
"""
|
|
35
|
+
Represent / display a basic "diff" between two data records.
|
|
36
|
+
|
|
37
|
+
You must provide both the "old" and "new" data records, when
|
|
38
|
+
constructing an instance of this class. Then call
|
|
39
|
+
:meth:`render_html()` to display the diff table.
|
|
40
|
+
|
|
41
|
+
:param old_data: Dict of "old" data record.
|
|
42
|
+
|
|
43
|
+
:param new_data: Dict of "new" data record.
|
|
44
|
+
|
|
45
|
+
:param fields: Optional list of field names. If not specified,
|
|
46
|
+
will be derived from the data records.
|
|
47
|
+
|
|
48
|
+
:param nature: What sort of diff is being represented; must be one
|
|
49
|
+
of: ``("create", "update", "delete")``
|
|
50
|
+
|
|
51
|
+
:param old_color: Background color to display for "old/deleted"
|
|
52
|
+
field data, when applicable.
|
|
53
|
+
|
|
54
|
+
:param new_color: Background color to display for "new/created"
|
|
55
|
+
field data, when applicable.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
59
|
+
self,
|
|
60
|
+
old_data: dict,
|
|
61
|
+
new_data: dict,
|
|
62
|
+
fields: list = None,
|
|
63
|
+
nature="update",
|
|
64
|
+
old_color="#ffebe9",
|
|
65
|
+
new_color="#dafbe1",
|
|
66
|
+
):
|
|
67
|
+
self.old_data = old_data
|
|
68
|
+
self.new_data = new_data
|
|
69
|
+
self.columns = ["field name", "old value", "new value"]
|
|
70
|
+
self.fields = fields or self.make_fields()
|
|
71
|
+
self.nature = nature
|
|
72
|
+
self.old_color = old_color
|
|
73
|
+
self.new_color = new_color
|
|
74
|
+
|
|
75
|
+
def make_fields(self): # pylint: disable=missing-function-docstring
|
|
76
|
+
return sorted(set(self.old_data) | set(self.new_data), key=lambda x: x.lower())
|
|
77
|
+
|
|
78
|
+
def old_value(self, field): # pylint: disable=missing-function-docstring
|
|
79
|
+
return self.old_data.get(field)
|
|
80
|
+
|
|
81
|
+
def new_value(self, field): # pylint: disable=missing-function-docstring
|
|
82
|
+
return self.new_data.get(field)
|
|
83
|
+
|
|
84
|
+
def values_differ(self, field): # pylint: disable=missing-function-docstring
|
|
85
|
+
return self.new_value(field) != self.old_value(field)
|
|
86
|
+
|
|
87
|
+
def render_html(self, template="/diff.mako", **kwargs):
|
|
88
|
+
"""
|
|
89
|
+
Render the diff as HTML table.
|
|
90
|
+
|
|
91
|
+
:param template: Name of template to render, if you need to
|
|
92
|
+
override the default.
|
|
93
|
+
|
|
94
|
+
:param \\**kwargs: Remaining kwargs are passed as context to
|
|
95
|
+
the template renderer.
|
|
96
|
+
|
|
97
|
+
:returns: HTML literal string
|
|
98
|
+
"""
|
|
99
|
+
context = kwargs
|
|
100
|
+
context["diff"] = self
|
|
101
|
+
return HTML.literal(render(template, context))
|
|
102
|
+
|
|
103
|
+
def render_field_row(self, field): # pylint: disable=missing-function-docstring
|
|
104
|
+
is_diff = self.values_differ(field)
|
|
105
|
+
|
|
106
|
+
td_field = HTML.tag("td", class_="field", c=field)
|
|
107
|
+
|
|
108
|
+
td_old_value = HTML.tag(
|
|
109
|
+
"td",
|
|
110
|
+
c=self.render_old_value(field),
|
|
111
|
+
**self.get_old_value_attrs(is_diff),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
td_new_value = HTML.tag(
|
|
115
|
+
"td",
|
|
116
|
+
c=self.render_new_value(field),
|
|
117
|
+
**self.get_new_value_attrs(is_diff),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return HTML.tag("tr", c=[td_field, td_old_value, td_new_value])
|
|
121
|
+
|
|
122
|
+
def render_old_value(self, field): # pylint: disable=missing-function-docstring
|
|
123
|
+
value = self.old_value(field)
|
|
124
|
+
return repr(value)
|
|
125
|
+
|
|
126
|
+
def render_new_value(self, field): # pylint: disable=missing-function-docstring
|
|
127
|
+
value = self.new_value(field)
|
|
128
|
+
return repr(value)
|
|
129
|
+
|
|
130
|
+
def get_old_value_attrs( # pylint: disable=missing-function-docstring
|
|
131
|
+
self, is_diff
|
|
132
|
+
):
|
|
133
|
+
attrs = {}
|
|
134
|
+
if self.nature == "update" and is_diff:
|
|
135
|
+
attrs["style"] = f"background-color: {self.old_color};"
|
|
136
|
+
elif self.nature == "delete":
|
|
137
|
+
attrs["style"] = f"background-color: {self.old_color};"
|
|
138
|
+
return attrs
|
|
139
|
+
|
|
140
|
+
def get_new_value_attrs( # pylint: disable=missing-function-docstring
|
|
141
|
+
self, is_diff
|
|
142
|
+
):
|
|
143
|
+
attrs = {}
|
|
144
|
+
if self.nature == "create":
|
|
145
|
+
attrs["style"] = f"background-color: {self.new_color};"
|
|
146
|
+
elif self.nature == "update" and is_diff:
|
|
147
|
+
attrs["style"] = f"background-color: {self.new_color};"
|
|
148
|
+
return attrs
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class VersionDiff(Diff):
|
|
152
|
+
"""
|
|
153
|
+
Special diff class, for use with version history views. Note that
|
|
154
|
+
while based on :class:`Diff`, this class uses a different
|
|
155
|
+
signature for the constructor.
|
|
156
|
+
|
|
157
|
+
:param version: Reference to a Continuum version record (object).
|
|
158
|
+
|
|
159
|
+
:param \\**kwargs: Remaining kwargs are passed as-is to the
|
|
160
|
+
:class:`Diff` constructor.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
def __init__(self, version, **kwargs):
|
|
164
|
+
import sqlalchemy_continuum as continuum # pylint: disable=import-outside-toplevel
|
|
165
|
+
from wutta_continuum.util import ( # pylint: disable=import-outside-toplevel
|
|
166
|
+
render_operation_type,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
self.version = version
|
|
170
|
+
self.model_class = continuum.parent_class(type(self.version))
|
|
171
|
+
self.mapper = sa.inspect(self.model_class)
|
|
172
|
+
self.version_mapper = sa.inspect(type(self.version))
|
|
173
|
+
self.title = kwargs.pop("title", self.model_class.__name__)
|
|
174
|
+
|
|
175
|
+
self.operation_title = render_operation_type(self.version.operation_type)
|
|
176
|
+
|
|
177
|
+
if "nature" not in kwargs:
|
|
178
|
+
if (
|
|
179
|
+
version.previous
|
|
180
|
+
and version.operation_type == continuum.Operation.DELETE
|
|
181
|
+
):
|
|
182
|
+
kwargs["nature"] = "delete"
|
|
183
|
+
elif version.previous:
|
|
184
|
+
kwargs["nature"] = "update"
|
|
185
|
+
else:
|
|
186
|
+
kwargs["nature"] = "create"
|
|
187
|
+
|
|
188
|
+
if "fields" not in kwargs:
|
|
189
|
+
kwargs["fields"] = self.get_default_fields()
|
|
190
|
+
|
|
191
|
+
old_data = {}
|
|
192
|
+
new_data = {}
|
|
193
|
+
for field in kwargs["fields"]:
|
|
194
|
+
if version.previous:
|
|
195
|
+
old_data[field] = getattr(version.previous, field)
|
|
196
|
+
new_data[field] = getattr(version, field)
|
|
197
|
+
|
|
198
|
+
super().__init__(old_data, new_data, **kwargs)
|
|
199
|
+
|
|
200
|
+
def get_default_fields(self): # pylint: disable=missing-function-docstring
|
|
201
|
+
fields = sorted(self.version_mapper.columns.keys())
|
|
202
|
+
|
|
203
|
+
unwanted = [
|
|
204
|
+
"transaction_id",
|
|
205
|
+
"end_transaction_id",
|
|
206
|
+
"operation_type",
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
return [field for field in fields if field not in unwanted]
|
|
210
|
+
|
|
211
|
+
def render_version_value(self, value): # pylint: disable=missing-function-docstring
|
|
212
|
+
return HTML.tag("span", c=[repr(value)], style="font-family: monospace;")
|
|
213
|
+
|
|
214
|
+
def render_old_value(self, field):
|
|
215
|
+
if self.nature == "create":
|
|
216
|
+
return ""
|
|
217
|
+
value = self.old_value(field)
|
|
218
|
+
return self.render_version_value(value)
|
|
219
|
+
|
|
220
|
+
def render_new_value(self, field):
|
|
221
|
+
if self.nature == "delete":
|
|
222
|
+
return ""
|
|
223
|
+
value = self.new_value(field)
|
|
224
|
+
return self.render_version_value(value)
|
|
@@ -84,7 +84,8 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth
|
|
|
84
84
|
.. attribute:: vue_tagname
|
|
85
85
|
|
|
86
86
|
String name for Vue component tag. By default this is
|
|
87
|
-
``'wutta-grid'``. See also :meth:`render_vue_tag()
|
|
87
|
+
``'wutta-grid'``. See also :meth:`render_vue_tag()`
|
|
88
|
+
and :attr:`vue_component`.
|
|
88
89
|
|
|
89
90
|
.. attribute:: model_class
|
|
90
91
|
|
|
@@ -841,8 +842,9 @@ class Grid: # pylint: disable=too-many-instance-attributes,too-many-public-meth
|
|
|
841
842
|
def set_tools(self, tools):
|
|
842
843
|
"""
|
|
843
844
|
Set the :attr:`tools` attribute using the given tools collection.
|
|
844
|
-
|
|
845
845
|
This will normalize the list/dict to desired internal format.
|
|
846
|
+
|
|
847
|
+
See also :meth:`add_tool()`.
|
|
846
848
|
"""
|
|
847
849
|
if tools and isinstance(tools, list):
|
|
848
850
|
if not any(isinstance(t, (tuple, list)) for t in tools):
|
|
@@ -41,6 +41,7 @@ import logging
|
|
|
41
41
|
from collections import OrderedDict
|
|
42
42
|
|
|
43
43
|
from pyramid import threadlocal
|
|
44
|
+
from pyramid.httpexceptions import HTTPFound
|
|
44
45
|
|
|
45
46
|
from wuttaweb import helpers
|
|
46
47
|
from wuttaweb.db import Session
|
|
@@ -123,6 +124,17 @@ def new_request(event):
|
|
|
123
124
|
config = request.registry.settings["wutta_config"]
|
|
124
125
|
app = config.get_app()
|
|
125
126
|
|
|
127
|
+
# nb. in rare circumstances i have received unhandled error email,
|
|
128
|
+
# which somehow was triggered by 'fanstatic.needed' being missing
|
|
129
|
+
# from the environ. not sure why that would happen, but it seems
|
|
130
|
+
# to when crawlers request a non-existent filename under the
|
|
131
|
+
# fanstatic path. there isn't a great way to handle it since
|
|
132
|
+
# e.g. can't show the normal error page if JS resources won't
|
|
133
|
+
# load, so we try a hail-mary redirect..
|
|
134
|
+
# (nb. also we skip this if environ is empty, i.e. for tests)
|
|
135
|
+
if request.environ and "fanstatic.needed" not in request.environ:
|
|
136
|
+
raise HTTPFound(location=request.route_url("home"))
|
|
137
|
+
|
|
126
138
|
request.wutta_config = config
|
|
127
139
|
|
|
128
140
|
def get_referrer(default=None):
|
|
@@ -152,6 +152,8 @@
|
|
|
152
152
|
font-size: 2rem;
|
|
153
153
|
font-weight: bold;
|
|
154
154
|
margin-bottom: 0 !important;
|
|
155
|
+
display: flex;
|
|
156
|
+
gap: 0.6rem;
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
#content-title h1 {
|
|
@@ -242,7 +244,9 @@
|
|
|
242
244
|
## nb. this is the index title proper
|
|
243
245
|
<div class="level-left">
|
|
244
246
|
<div id="header-index-title" class="level-item">
|
|
245
|
-
% if
|
|
247
|
+
% if index_title_rendered is not Undefined and index_title_rendered:
|
|
248
|
+
<h1 class="title">${index_title_rendered}</h1>
|
|
249
|
+
% elif index_title:
|
|
246
250
|
% if index_url:
|
|
247
251
|
<h1 class="title">${h.link_to(index_title, index_url)}</h1>
|
|
248
252
|
% else:
|
|
@@ -279,7 +283,7 @@
|
|
|
279
283
|
class="has-background-primary">
|
|
280
284
|
<div style="display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem;">
|
|
281
285
|
|
|
282
|
-
<div style="width: 60%; display: flex; gap:
|
|
286
|
+
<div style="width: 60%; display: flex; gap: 1rem;">
|
|
283
287
|
|
|
284
288
|
<h1 class="title has-text-white"
|
|
285
289
|
v-html="contentTitleHTML">
|
|
@@ -781,7 +785,32 @@
|
|
|
781
785
|
% endif
|
|
782
786
|
</%def>
|
|
783
787
|
|
|
784
|
-
<%def name="render_prevnext_header_buttons()"
|
|
788
|
+
<%def name="render_prevnext_header_buttons()">
|
|
789
|
+
% if show_prev_next is not Undefined and show_prev_next:
|
|
790
|
+
<b-button tag="a"
|
|
791
|
+
% if prev_url:
|
|
792
|
+
href="${prev_url}"
|
|
793
|
+
% else:
|
|
794
|
+
href="#"
|
|
795
|
+
disabled
|
|
796
|
+
% endif
|
|
797
|
+
icon-pack="fas"
|
|
798
|
+
icon-left="arrow-left">
|
|
799
|
+
Older
|
|
800
|
+
</b-button>
|
|
801
|
+
<b-button tag="a"
|
|
802
|
+
% if next_url:
|
|
803
|
+
href="${next_url}"
|
|
804
|
+
% else:
|
|
805
|
+
href="#"
|
|
806
|
+
disabled
|
|
807
|
+
% endif
|
|
808
|
+
icon-pack="fas"
|
|
809
|
+
icon-left="arrow-right">
|
|
810
|
+
Newer
|
|
811
|
+
</b-button>
|
|
812
|
+
% endif
|
|
813
|
+
</%def>
|
|
785
814
|
|
|
786
815
|
##############################
|
|
787
816
|
## vue components + app
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## -*- coding: utf-8; -*-
|
|
2
|
+
<table class="table is-fullwidth is-bordered is-narrow">
|
|
3
|
+
<thead>
|
|
4
|
+
<tr>
|
|
5
|
+
% for column in diff.columns:
|
|
6
|
+
<th>${column}</th>
|
|
7
|
+
% endfor
|
|
8
|
+
</tr>
|
|
9
|
+
</thead>
|
|
10
|
+
<tbody>
|
|
11
|
+
% for field in diff.fields:
|
|
12
|
+
${diff.render_field_row(field)}
|
|
13
|
+
% endfor
|
|
14
|
+
</tbody>
|
|
15
|
+
</table>
|
|
@@ -5,6 +5,18 @@
|
|
|
5
5
|
|
|
6
6
|
<%def name="content_title()">${instance_title}</%def>
|
|
7
7
|
|
|
8
|
+
<%def name="render_instance_header_title_extras()">
|
|
9
|
+
${parent.render_instance_header_title_extras()}
|
|
10
|
+
% if master.should_expose_versions():
|
|
11
|
+
<b-button tag="a"
|
|
12
|
+
href="${master.get_action_url('versions', instance)}"
|
|
13
|
+
icon-pack="fas"
|
|
14
|
+
icon-left="history">
|
|
15
|
+
View History
|
|
16
|
+
</b-button>
|
|
17
|
+
% endif
|
|
18
|
+
</%def>
|
|
19
|
+
|
|
8
20
|
<%def name="page_layout()">
|
|
9
21
|
|
|
10
22
|
% if master.has_rows:
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
## -*- coding: utf-8; -*-
|
|
2
|
+
<%inherit file="/master/form.mako" />
|
|
3
|
+
|
|
4
|
+
<%def name="title()">${index_title} » ${instance_title} » changes @ TXN ${transaction.id}</%def>
|
|
5
|
+
|
|
6
|
+
<%def name="content_title()">changes @ TXN ${transaction.id}</%def>
|
|
7
|
+
|
|
8
|
+
<%def name="page_content()">
|
|
9
|
+
<div class="wutta-form-wrapper">
|
|
10
|
+
|
|
11
|
+
<b-field label="Changed" horizontal>
|
|
12
|
+
<span>${changed}</span>
|
|
13
|
+
</b-field>
|
|
14
|
+
|
|
15
|
+
<b-field label="Changed by" horizontal>
|
|
16
|
+
<span>${transaction.user or ""}</span>
|
|
17
|
+
</b-field>
|
|
18
|
+
|
|
19
|
+
<b-field label="IP Address" horizontal>
|
|
20
|
+
<span>${transaction.remote_addr or ""}</span>
|
|
21
|
+
</b-field>
|
|
22
|
+
|
|
23
|
+
<b-field label="TXN ID" horizontal>
|
|
24
|
+
<span>${transaction.id}</span>
|
|
25
|
+
</b-field>
|
|
26
|
+
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div style="padding: 2rem;">
|
|
30
|
+
% for diff in version_diffs:
|
|
31
|
+
<h4 class="is-size-4 block">${diff.title} (${diff.operation_title})</h4>
|
|
32
|
+
${diff.render_html()}
|
|
33
|
+
% endfor
|
|
34
|
+
</div>
|
|
35
|
+
</%def>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
## -*- coding: utf-8; -*-
|
|
2
|
+
<%inherit file="/page.mako" />
|
|
3
|
+
|
|
4
|
+
<%def name="title()">${index_title} » ${instance_title} » history</%def>
|
|
5
|
+
|
|
6
|
+
<%def name="content_title()">Version History</%def>
|
|
7
|
+
|
|
8
|
+
<%def name="page_content()">
|
|
9
|
+
${grid.render_vue_tag()}
|
|
10
|
+
</%def>
|
|
11
|
+
|
|
12
|
+
<%def name="render_vue_templates()">
|
|
13
|
+
${parent.render_vue_templates()}
|
|
14
|
+
${grid.render_vue_template()}
|
|
15
|
+
</%def>
|
|
16
|
+
|
|
17
|
+
<%def name="make_vue_components()">
|
|
18
|
+
${parent.make_vue_components()}
|
|
19
|
+
${grid.render_vue_finalize()}
|
|
20
|
+
</%def>
|
|
@@ -24,14 +24,18 @@
|
|
|
24
24
|
WuttaWeb - test utilities
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
|
+
import sys
|
|
27
28
|
from unittest.mock import MagicMock
|
|
28
29
|
|
|
29
30
|
import fanstatic
|
|
31
|
+
import pytest
|
|
30
32
|
from pyramid import testing
|
|
31
33
|
|
|
32
34
|
from wuttjamaican.testing import DataTestCase
|
|
35
|
+
from wuttjamaican.db.model.base import metadata
|
|
33
36
|
|
|
34
37
|
from wuttaweb import subscribers
|
|
38
|
+
from wuttaweb.conf import WuttaWebConfigExtension
|
|
35
39
|
|
|
36
40
|
|
|
37
41
|
class WebTestCase(DataTestCase):
|
|
@@ -101,4 +105,66 @@ class WebTestCase(DataTestCase):
|
|
|
101
105
|
"""
|
|
102
106
|
Make and return a new dummy request object.
|
|
103
107
|
"""
|
|
104
|
-
return testing.DummyRequest()
|
|
108
|
+
return testing.DummyRequest(client_addr="127.0.0.1")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@pytest.mark.versioned
|
|
112
|
+
class VersionWebTestCase(WebTestCase):
|
|
113
|
+
"""
|
|
114
|
+
Base class for test suites requiring a full (typical) web app,
|
|
115
|
+
with Continuum versioning support.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def setUp(self):
|
|
119
|
+
self.setup_versioning()
|
|
120
|
+
|
|
121
|
+
def setup_versioning(self):
|
|
122
|
+
"""
|
|
123
|
+
Perform setup for the testing web app.
|
|
124
|
+
"""
|
|
125
|
+
self.setup_web()
|
|
126
|
+
|
|
127
|
+
def tearDown(self):
|
|
128
|
+
self.teardown_versioning()
|
|
129
|
+
|
|
130
|
+
def teardown_versioning(self):
|
|
131
|
+
"""
|
|
132
|
+
Perform teardown for the testing web app.
|
|
133
|
+
"""
|
|
134
|
+
import sqlalchemy_continuum as continuum # pylint: disable=import-outside-toplevel
|
|
135
|
+
|
|
136
|
+
continuum.remove_versioning()
|
|
137
|
+
continuum.versioning_manager.transaction_cls = continuum.TransactionFactory()
|
|
138
|
+
self.teardown_web()
|
|
139
|
+
|
|
140
|
+
def make_config(self, **kwargs):
|
|
141
|
+
"""
|
|
142
|
+
Make and customize the config object.
|
|
143
|
+
|
|
144
|
+
We override this to explicitly enable the versioning feature.
|
|
145
|
+
"""
|
|
146
|
+
from wutta_continuum.conf import ( # pylint: disable=import-outside-toplevel
|
|
147
|
+
WuttaContinuumConfigExtension,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
config = super().make_config(**kwargs)
|
|
151
|
+
config.setdefault("wutta_continuum.enable_versioning", "true")
|
|
152
|
+
|
|
153
|
+
# nb. must purge model classes from sys.modules, so they will
|
|
154
|
+
# be reloaded and sqlalchemy-continuum can reconfigure
|
|
155
|
+
if "wuttjamaican.db.model" in sys.modules:
|
|
156
|
+
del sys.modules["wuttjamaican.db.model.batch"]
|
|
157
|
+
del sys.modules["wuttjamaican.db.model.upgrades"]
|
|
158
|
+
del sys.modules["wuttjamaican.db.model.auth"]
|
|
159
|
+
del sys.modules["wuttjamaican.db.model.base"]
|
|
160
|
+
del sys.modules["wuttjamaican.db.model"]
|
|
161
|
+
|
|
162
|
+
self.assertNotIn("user_version", metadata.tables)
|
|
163
|
+
|
|
164
|
+
ext = WuttaWebConfigExtension()
|
|
165
|
+
ext.configure(config)
|
|
166
|
+
|
|
167
|
+
ext = WuttaContinuumConfigExtension()
|
|
168
|
+
ext.startup(config)
|
|
169
|
+
|
|
170
|
+
return config
|