WuttaWeb 0.16.1__tar.gz → 0.17.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.16.1 → wuttaweb-0.17.0}/CHANGELOG.md +32 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/PKG-INFO +3 -2
- wuttaweb-0.17.0/docs/api/wuttaweb.views.batch.rst +6 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/conf.py +2 -0
- wuttaweb-0.17.0/docs/glossary.rst +30 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/index.rst +1 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/pyproject.toml +7 -2
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/forms/base.py +132 -14
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/forms/schema.py +77 -3
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/forms/widgets.py +99 -3
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/grids/base.py +48 -1
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/grids/filters.py +35 -4
- wuttaweb-0.17.0/src/wuttaweb/handler.py +144 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/menus.py +6 -1
- wuttaweb-0.17.0/src/wuttaweb/static/__init__.py +74 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/subscribers.py +8 -3
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/base.mako +10 -0
- wuttaweb-0.17.0/src/wuttaweb/templates/base_meta.mako +23 -0
- wuttaweb-0.17.0/src/wuttaweb/templates/batch/view.mako +124 -0
- wuttaweb-0.17.0/src/wuttaweb/templates/deform/dateinput.pt +6 -0
- wuttaweb-0.17.0/src/wuttaweb/templates/deform/datetimeinput.pt +10 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/objectref.pt +1 -1
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/form.mako +21 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/forms/vue_template.mako +8 -0
- wuttaweb-0.17.0/src/wuttaweb/templates/master/view.mako +52 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/page.mako +5 -1
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/wutta-components.mako +163 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/util.py +19 -2
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/views/base.py +5 -3
- wuttaweb-0.17.0/src/wuttaweb/views/batch.py +404 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/views/master.py +278 -12
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/views/roles.py +64 -2
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/views/upgrades.py +1 -2
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/views/users.py +1 -2
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/forms/test_base.py +88 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/forms/test_schema.py +63 -2
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/forms/test_widgets.py +92 -5
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/grids/test_base.py +38 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/grids/test_filters.py +47 -3
- wuttaweb-0.17.0/tests/test_handler.py +76 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/test_util.py +31 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/util.py +5 -0
- wuttaweb-0.17.0/tests/views/test_batch.py +373 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/views/test_master.py +159 -2
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/views/test_roles.py +31 -0
- wuttaweb-0.16.1/docs/glossary.rst +0 -10
- wuttaweb-0.16.1/src/wuttaweb/handler.py +0 -57
- wuttaweb-0.16.1/src/wuttaweb/static/__init__.py +0 -37
- wuttaweb-0.16.1/src/wuttaweb/templates/base_meta.mako +0 -23
- wuttaweb-0.16.1/src/wuttaweb/templates/master/view.mako +0 -9
- wuttaweb-0.16.1/tests/test_handler.py +0 -20
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/.gitignore +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/COPYING.txt +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/README.md +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/Makefile +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/_static/.keepme +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.app.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.auth.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.conf.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.db.continuum.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.db.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.db.sess.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.forms.base.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.forms.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.forms.schema.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.forms.widgets.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.grids.base.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.grids.filters.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.grids.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.handler.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.helpers.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.menus.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.progress.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.static.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.subscribers.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.util.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.auth.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.base.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.common.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.essential.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.master.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.people.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.progress.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.roles.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.settings.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.upgrades.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.users.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/make.bat +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/narr/templates/base.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/narr/templates/index.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/narr/templates/lookup.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/docs/narr/templates/overview.rst +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/__init__.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/_version.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/app.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/auth.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/conf.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/db/__init__.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/db/continuum.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/db/sess.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/email/templates/feedback.html.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/email/templates/feedback.txt.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/forms/__init__.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/grids/__init__.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/helpers.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/progress.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/static/img/favicon.ico +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/static/img/logo.png +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/static/img/testing.png +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/appinfo/index.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/auth/change_password.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/auth/login.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/configure.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/password.pt +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/permissions.pt +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/select.pt +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/textarea.pt +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/textinput.pt +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/forbidden.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/grids/table_element.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/grids/vue_template.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/home.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/configure.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/create.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/delete.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/edit.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/form.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/index.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/notfound.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/people/view_profile.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/progress.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/setup.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/upgrade.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/templates/upgrades/view.mako +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/views/__init__.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/views/auth.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/views/common.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/views/essential.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/views/people.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/views/progress.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/src/wuttaweb/views/settings.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tasks.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/__init__.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/db/__init__.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/db/test_continuum.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/forms/__init__.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/grids/__init__.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/libcache/bb_oruga.js +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/libcache/bb_oruga_bulma.css +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/libcache/bb_oruga_bulma.js +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/libcache/bb_vue.js +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/libcache/bb_vue_fontawesome.js +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/libcache/buefy.css +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/libcache/buefy.js +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/libcache/fontawesome.js +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/libcache/vue.js +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/libcache/vue_resource.js +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/test_app.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/test_auth.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/test_helpers.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/test_menus.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/test_progress.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/test_static.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/test_subscribers.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/views/__init__.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/views/test___init__.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/views/test_auth.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/views/test_base.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/views/test_common.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/views/test_people.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/views/test_progress.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/views/test_settings.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/views/test_upgrades.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tests/views/test_users.py +0 -0
- {wuttaweb-0.16.1 → wuttaweb-0.17.0}/tox.ini +0 -0
|
@@ -5,6 +5,38 @@ 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.17.0 (2024-12-15)
|
|
9
|
+
|
|
10
|
+
### Feat
|
|
11
|
+
|
|
12
|
+
- add basic support for batch execution
|
|
13
|
+
- add basic support for rows grid for master, batch views
|
|
14
|
+
- add basic master view class for batches
|
|
15
|
+
|
|
16
|
+
### Fix
|
|
17
|
+
|
|
18
|
+
- add handling for decimal values and lists, in `make_json_safe()`
|
|
19
|
+
- fix behavior when editing Roles for a User
|
|
20
|
+
- add basic views for raw Permissions
|
|
21
|
+
- improve support for date, datetime fields in grids, forms
|
|
22
|
+
- add way to set field widgets using pseudo-type
|
|
23
|
+
- add support for date, datetime form fields
|
|
24
|
+
- make dropdown widgets as wide as other text fields in main form
|
|
25
|
+
- add fallback instance title
|
|
26
|
+
- display "global" errors at top of form, if present
|
|
27
|
+
- add `make_form()` and `make_grid()` methods on web handler
|
|
28
|
+
- correct "empty option" behavior for `ObjectRef` schema type
|
|
29
|
+
- use fanstatic to serve built-in images by default
|
|
30
|
+
|
|
31
|
+
## v0.16.2 (2024-12-10)
|
|
32
|
+
|
|
33
|
+
### Fix
|
|
34
|
+
|
|
35
|
+
- add `GridWidget` and `form.set_grid()` for convenience
|
|
36
|
+
- add "is false or null" grid filter, for nullable bool columns
|
|
37
|
+
- remove Person column for `Person.users` grid display
|
|
38
|
+
- flatten UUID to str for `make_json_safe()`
|
|
39
|
+
|
|
8
40
|
## v0.16.1 (2024-12-08)
|
|
9
41
|
|
|
10
42
|
### Fix
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: WuttaWeb
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.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
|
|
@@ -26,6 +26,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
26
26
|
Requires-Python: >=3.8
|
|
27
27
|
Requires-Dist: colanderalchemy
|
|
28
28
|
Requires-Dist: humanize
|
|
29
|
+
Requires-Dist: markdown
|
|
29
30
|
Requires-Dist: paginate
|
|
30
31
|
Requires-Dist: paginate-sqlalchemy
|
|
31
32
|
Requires-Dist: pyramid-beaker
|
|
@@ -36,7 +37,7 @@ Requires-Dist: pyramid-tm
|
|
|
36
37
|
Requires-Dist: pyramid>=2
|
|
37
38
|
Requires-Dist: waitress
|
|
38
39
|
Requires-Dist: webhelpers2
|
|
39
|
-
Requires-Dist: wuttjamaican[db]>=0.
|
|
40
|
+
Requires-Dist: wuttjamaican[db]>=0.18.0
|
|
40
41
|
Requires-Dist: zope-sqlalchemy>=1.5
|
|
41
42
|
Provides-Extra: continuum
|
|
42
43
|
Requires-Dist: wutta-continuum; extra == 'continuum'
|
|
@@ -29,9 +29,11 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
|
|
29
29
|
intersphinx_mapping = {
|
|
30
30
|
'colander': ('https://docs.pylonsproject.org/projects/colander/en/latest/', None),
|
|
31
31
|
'deform': ('https://docs.pylonsproject.org/projects/deform/en/latest/', None),
|
|
32
|
+
'fanstatic': ('https://www.fanstatic.org/en/latest/', None),
|
|
32
33
|
'pyramid': ('https://docs.pylonsproject.org/projects/pyramid/en/latest/', None),
|
|
33
34
|
'python': ('https://docs.python.org/3/', None),
|
|
34
35
|
'rattail-manual': ('https://rattailproject.org/docs/rattail-manual/', None),
|
|
36
|
+
'sqlalchemy': ('http://docs.sqlalchemy.org/en/latest/', None),
|
|
35
37
|
'webhelpers2': ('https://webhelpers2.readthedocs.io/en/latest/', None),
|
|
36
38
|
'wuttjamaican': ('https://rattailproject.org/docs/wuttjamaican/', None),
|
|
37
39
|
'wutta-continuum': ('https://rattailproject.org/docs/wutta-continuum/', None),
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
.. _glossary:
|
|
2
|
+
|
|
3
|
+
Glossary
|
|
4
|
+
========
|
|
5
|
+
|
|
6
|
+
.. glossary::
|
|
7
|
+
:sorted:
|
|
8
|
+
|
|
9
|
+
grid
|
|
10
|
+
This refers to a "table of data, with features" essentially.
|
|
11
|
+
Sometimes it may be displayed as a simple table with no features,
|
|
12
|
+
or sometimes it has sortable columns, search filters and other
|
|
13
|
+
tools.
|
|
14
|
+
|
|
15
|
+
See also the :class:`~wuttaweb.grids.base.Grid` base class.
|
|
16
|
+
|
|
17
|
+
menu handler
|
|
18
|
+
This is the :term:`handler` responsible for constructing the main
|
|
19
|
+
app menu at top of page.
|
|
20
|
+
|
|
21
|
+
The menu handler is accessed by way of the :term:`web handler`.
|
|
22
|
+
|
|
23
|
+
See also the :class:`~wuttaweb.menus.MenuHandler` base class.
|
|
24
|
+
|
|
25
|
+
web handler
|
|
26
|
+
This is the :term:`handler` responsible for overall web layer
|
|
27
|
+
customizations, e.g. logo image and menu overrides. Although
|
|
28
|
+
the latter it delegates to the :term:`menu handler`.
|
|
29
|
+
|
|
30
|
+
See also the :class:`~wuttaweb.handler.WebHandler` base class.
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "WuttaWeb"
|
|
9
|
-
version = "0.
|
|
9
|
+
version = "0.17.0"
|
|
10
10
|
description = "Web App for Wutta Framework"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
|
|
@@ -32,6 +32,7 @@ requires-python = ">= 3.8"
|
|
|
32
32
|
dependencies = [
|
|
33
33
|
"ColanderAlchemy",
|
|
34
34
|
"humanize",
|
|
35
|
+
"markdown",
|
|
35
36
|
"paginate",
|
|
36
37
|
"paginate_sqlalchemy",
|
|
37
38
|
"pyramid>=2",
|
|
@@ -42,7 +43,7 @@ dependencies = [
|
|
|
42
43
|
"pyramid_tm",
|
|
43
44
|
"waitress",
|
|
44
45
|
"WebHelpers2",
|
|
45
|
-
"WuttJamaican[db]>=0.
|
|
46
|
+
"WuttJamaican[db]>=0.18.0",
|
|
46
47
|
"zope.sqlalchemy>=1.5",
|
|
47
48
|
]
|
|
48
49
|
|
|
@@ -53,6 +54,10 @@ docs = ["Sphinx", "furo"]
|
|
|
53
54
|
tests = ["pytest-cov", "tox"]
|
|
54
55
|
|
|
55
56
|
|
|
57
|
+
[project.entry-points."fanstatic.libraries"]
|
|
58
|
+
wuttaweb_img = "wuttaweb.static:img"
|
|
59
|
+
|
|
60
|
+
|
|
56
61
|
[project.entry-points."paste.app_factory"]
|
|
57
62
|
main = "wuttaweb.app:main"
|
|
58
63
|
|
|
@@ -27,6 +27,9 @@ Base form classes
|
|
|
27
27
|
import logging
|
|
28
28
|
from collections import OrderedDict
|
|
29
29
|
|
|
30
|
+
import sqlalchemy as sa
|
|
31
|
+
from sqlalchemy import orm
|
|
32
|
+
|
|
30
33
|
import colander
|
|
31
34
|
import deform
|
|
32
35
|
from colanderalchemy import SQLAlchemySchemaNode
|
|
@@ -311,6 +314,7 @@ class Form:
|
|
|
311
314
|
self.model_class = type(self.model_instance)
|
|
312
315
|
|
|
313
316
|
self.set_fields(fields or self.get_fields())
|
|
317
|
+
self.set_default_widgets()
|
|
314
318
|
|
|
315
319
|
# nb. this tracks grid JSON data for inclusion in page template
|
|
316
320
|
self.grid_vue_context = OrderedDict()
|
|
@@ -457,23 +461,122 @@ class Form:
|
|
|
457
461
|
if self.schema:
|
|
458
462
|
self.schema[key] = node
|
|
459
463
|
|
|
460
|
-
def set_widget(self, key, widget):
|
|
464
|
+
def set_widget(self, key, widget, **kwargs):
|
|
461
465
|
"""
|
|
462
466
|
Set/override the widget for a field.
|
|
463
467
|
|
|
468
|
+
You can specify a widget instance or else a named "type" of
|
|
469
|
+
widget, in which case that is passed along to
|
|
470
|
+
:meth:`make_widget()`.
|
|
471
|
+
|
|
464
472
|
:param key: Name of field.
|
|
465
473
|
|
|
466
|
-
:param widget:
|
|
467
|
-
|
|
474
|
+
:param widget: Either a :class:`deform:deform.widget.Widget`
|
|
475
|
+
instance, or else a widget "type" name.
|
|
476
|
+
|
|
477
|
+
:param \**kwargs: Any remaining kwargs are passed along to
|
|
478
|
+
:meth:`make_widget()` - if applicable.
|
|
468
479
|
|
|
469
480
|
Widget overrides are tracked via :attr:`widgets`.
|
|
470
481
|
"""
|
|
482
|
+
if not isinstance(widget, deform.widget.Widget):
|
|
483
|
+
widget_obj = self.make_widget(widget, **kwargs)
|
|
484
|
+
if not widget_obj:
|
|
485
|
+
raise ValueError(f"widget type not supported: {widget}")
|
|
486
|
+
widget = widget_obj
|
|
487
|
+
|
|
471
488
|
self.widgets[key] = widget
|
|
472
489
|
|
|
473
490
|
# update schema if necessary
|
|
474
491
|
if self.schema and key in self.schema:
|
|
475
492
|
self.schema[key].widget = widget
|
|
476
493
|
|
|
494
|
+
def make_widget(self, widget_type, **kwargs):
|
|
495
|
+
"""
|
|
496
|
+
Make and return a new field widget of the given type.
|
|
497
|
+
|
|
498
|
+
This has built-in support for the following types (although
|
|
499
|
+
subclass can override as needed):
|
|
500
|
+
|
|
501
|
+
* ``'notes'`` => :class:`~wuttaweb.forms.widgets.NotesWidget`
|
|
502
|
+
|
|
503
|
+
See also :meth:`set_widget()` which may call this method
|
|
504
|
+
automatically.
|
|
505
|
+
|
|
506
|
+
:param widget_type: Which of the above (or custom) widget
|
|
507
|
+
type to create.
|
|
508
|
+
|
|
509
|
+
:param \**kwargs: Remaining kwargs are passed as-is to the
|
|
510
|
+
widget factory.
|
|
511
|
+
|
|
512
|
+
:returns: New widget instance, or ``None`` if e.g. it could
|
|
513
|
+
not determine how to create the widget.
|
|
514
|
+
"""
|
|
515
|
+
from wuttaweb.forms import widgets
|
|
516
|
+
|
|
517
|
+
if widget_type == 'notes':
|
|
518
|
+
return widgets.NotesWidget(**kwargs)
|
|
519
|
+
|
|
520
|
+
def set_default_widgets(self):
|
|
521
|
+
"""
|
|
522
|
+
Set default field widgets, where applicable.
|
|
523
|
+
|
|
524
|
+
This will add new entries to :attr:`widgets` for columns
|
|
525
|
+
whose data type implies a default widget should be used.
|
|
526
|
+
This is generally only possible if :attr:`model_class` is set
|
|
527
|
+
to a valid SQLAlchemy mapped class.
|
|
528
|
+
|
|
529
|
+
As of writing this only looks for
|
|
530
|
+
:class:`sqlalchemy:sqlalchemy.types.DateTime` fields and if
|
|
531
|
+
any are found, they are configured to use
|
|
532
|
+
:class:`~wuttaweb.forms.widgets.WuttaDateTimeWidget()`.
|
|
533
|
+
"""
|
|
534
|
+
from wuttaweb.forms import widgets
|
|
535
|
+
|
|
536
|
+
if not self.model_class:
|
|
537
|
+
return
|
|
538
|
+
|
|
539
|
+
for key in self.fields:
|
|
540
|
+
if key in self.widgets:
|
|
541
|
+
continue
|
|
542
|
+
|
|
543
|
+
attr = getattr(self.model_class, key, None)
|
|
544
|
+
if attr:
|
|
545
|
+
prop = getattr(attr, 'prop', None)
|
|
546
|
+
if prop and isinstance(prop, orm.ColumnProperty):
|
|
547
|
+
column = prop.columns[0]
|
|
548
|
+
if isinstance(column.type, sa.DateTime):
|
|
549
|
+
# self.set_renderer(key, self.render_datetime)
|
|
550
|
+
self.set_widget(key, widgets.WuttaDateTimeWidget(self.request))
|
|
551
|
+
|
|
552
|
+
def set_grid(self, key, grid):
|
|
553
|
+
"""
|
|
554
|
+
Establish a :term:`grid` to be displayed for a field. This
|
|
555
|
+
uses a :class:`~wuttaweb.forms.widgets.GridWidget` to wrap the
|
|
556
|
+
rendered grid.
|
|
557
|
+
|
|
558
|
+
:param key: Name of field.
|
|
559
|
+
|
|
560
|
+
:param widget: :class:`~wuttaweb.grids.base.Grid` instance,
|
|
561
|
+
pre-configured and (usually) with data.
|
|
562
|
+
"""
|
|
563
|
+
from wuttaweb.forms.widgets import GridWidget
|
|
564
|
+
|
|
565
|
+
widget = GridWidget(self.request, grid)
|
|
566
|
+
self.set_widget(key, widget)
|
|
567
|
+
self.add_grid_vue_context(grid)
|
|
568
|
+
|
|
569
|
+
def add_grid_vue_context(self, grid):
|
|
570
|
+
""" """
|
|
571
|
+
if not grid.key:
|
|
572
|
+
raise ValueError("grid must have a key!")
|
|
573
|
+
|
|
574
|
+
if grid.key in self.grid_vue_context:
|
|
575
|
+
log.warning("grid data with key '%s' already registered, "
|
|
576
|
+
"but will be replaced", grid.key)
|
|
577
|
+
|
|
578
|
+
self.grid_vue_context[grid.key] = grid.get_vue_context()
|
|
579
|
+
|
|
477
580
|
def set_validator(self, key, validator):
|
|
478
581
|
"""
|
|
479
582
|
Set/override the validator for a field, or the form.
|
|
@@ -848,17 +951,6 @@ class Form:
|
|
|
848
951
|
output = render(template, context)
|
|
849
952
|
return HTML.literal(output)
|
|
850
953
|
|
|
851
|
-
def add_grid_vue_context(self, grid):
|
|
852
|
-
""" """
|
|
853
|
-
if not grid.key:
|
|
854
|
-
raise ValueError("grid must have a key!")
|
|
855
|
-
|
|
856
|
-
if grid.key in self.grid_vue_context:
|
|
857
|
-
log.warning("grid data with key '%s' already registered, "
|
|
858
|
-
"but will be replaced", grid.key)
|
|
859
|
-
|
|
860
|
-
self.grid_vue_context[grid.key] = grid.get_vue_context()
|
|
861
|
-
|
|
862
954
|
def render_vue_field(
|
|
863
955
|
self,
|
|
864
956
|
fieldname,
|
|
@@ -1094,6 +1186,32 @@ class Form:
|
|
|
1094
1186
|
|
|
1095
1187
|
return self.validated
|
|
1096
1188
|
|
|
1189
|
+
def has_global_errors(self):
|
|
1190
|
+
"""
|
|
1191
|
+
Convenience function to check if the form has any "global"
|
|
1192
|
+
(not field-level) errors.
|
|
1193
|
+
|
|
1194
|
+
See also :meth:`get_global_errors()`.
|
|
1195
|
+
|
|
1196
|
+
:returns: ``True`` if global errors present, else ``False``.
|
|
1197
|
+
"""
|
|
1198
|
+
dform = self.get_deform()
|
|
1199
|
+
return bool(dform.error)
|
|
1200
|
+
|
|
1201
|
+
def get_global_errors(self):
|
|
1202
|
+
"""
|
|
1203
|
+
Returns a list of "global" (not field-level) error messages
|
|
1204
|
+
for the form.
|
|
1205
|
+
|
|
1206
|
+
See also :meth:`has_global_errors()`.
|
|
1207
|
+
|
|
1208
|
+
:returns: List of error messages (possibly empty).
|
|
1209
|
+
"""
|
|
1210
|
+
dform = self.get_deform()
|
|
1211
|
+
if dform.error is None:
|
|
1212
|
+
return []
|
|
1213
|
+
return dform.error.messages()
|
|
1214
|
+
|
|
1097
1215
|
def get_field_errors(self, field):
|
|
1098
1216
|
"""
|
|
1099
1217
|
Return a list of error messages for the given field.
|
|
@@ -24,15 +24,48 @@
|
|
|
24
24
|
Form schema types
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
|
+
import datetime
|
|
27
28
|
import uuid as _uuid
|
|
28
29
|
|
|
29
30
|
import colander
|
|
31
|
+
import sqlalchemy as sa
|
|
30
32
|
|
|
31
33
|
from wuttaweb.db import Session
|
|
32
34
|
from wuttaweb.forms import widgets
|
|
33
35
|
from wuttjamaican.db.model import Person
|
|
34
36
|
|
|
35
37
|
|
|
38
|
+
class WuttaDateTime(colander.DateTime):
|
|
39
|
+
"""
|
|
40
|
+
Custom schema type for ``datetime`` fields.
|
|
41
|
+
|
|
42
|
+
This should be used automatically for
|
|
43
|
+
:class:`sqlalchemy:sqlalchemy.types.DateTime` columns unless you
|
|
44
|
+
register another default.
|
|
45
|
+
|
|
46
|
+
This schema type exists for sake of convenience, when working with
|
|
47
|
+
the Buefy datepicker + timepicker widgets.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def deserialize(self, node, cstruct):
|
|
51
|
+
""" """
|
|
52
|
+
if not cstruct:
|
|
53
|
+
return colander.null
|
|
54
|
+
|
|
55
|
+
formats = [
|
|
56
|
+
'%Y-%m-%dT%H:%M:%S',
|
|
57
|
+
'%Y-%m-%dT%I:%M %p',
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
for fmt in formats:
|
|
61
|
+
try:
|
|
62
|
+
return datetime.datetime.strptime(cstruct, fmt)
|
|
63
|
+
except:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
node.raise_invalid("Invalid date and/or time")
|
|
67
|
+
|
|
68
|
+
|
|
36
69
|
class ObjectNode(colander.SchemaNode):
|
|
37
70
|
"""
|
|
38
71
|
Custom schema node class which adds methods for compatibility with
|
|
@@ -207,6 +240,11 @@ class ObjectRef(colander.SchemaType):
|
|
|
207
240
|
|
|
208
241
|
def serialize(self, node, appstruct):
|
|
209
242
|
""" """
|
|
243
|
+
# nb. normalize to empty option if no object ref, so that
|
|
244
|
+
# works as expected
|
|
245
|
+
if self.empty_option and not appstruct:
|
|
246
|
+
return self.empty_option[0]
|
|
247
|
+
|
|
210
248
|
if appstruct is colander.null:
|
|
211
249
|
return colander.null
|
|
212
250
|
|
|
@@ -214,7 +252,7 @@ class ObjectRef(colander.SchemaType):
|
|
|
214
252
|
node.model_instance = appstruct
|
|
215
253
|
|
|
216
254
|
# serialize to uuid
|
|
217
|
-
return appstruct.uuid
|
|
255
|
+
return appstruct.uuid.hex
|
|
218
256
|
|
|
219
257
|
def deserialize(self, node, cstruct):
|
|
220
258
|
""" """
|
|
@@ -296,7 +334,7 @@ class ObjectRef(colander.SchemaType):
|
|
|
296
334
|
if 'values' not in kwargs:
|
|
297
335
|
query = self.get_query()
|
|
298
336
|
objects = query.all()
|
|
299
|
-
values = [(obj.uuid, str(obj))
|
|
337
|
+
values = [(obj.uuid.hex, str(obj))
|
|
300
338
|
for obj in objects]
|
|
301
339
|
if self.empty_option:
|
|
302
340
|
values.insert(0, self.empty_option)
|
|
@@ -344,6 +382,30 @@ class PersonRef(ObjectRef):
|
|
|
344
382
|
return self.request.route_url('people.view', uuid=person.uuid)
|
|
345
383
|
|
|
346
384
|
|
|
385
|
+
class RoleRef(ObjectRef):
|
|
386
|
+
"""
|
|
387
|
+
Custom schema type for a
|
|
388
|
+
:class:`~wuttjamaican:wuttjamaican.db.model.auth.Role` reference
|
|
389
|
+
field.
|
|
390
|
+
|
|
391
|
+
This is a subclass of :class:`ObjectRef`.
|
|
392
|
+
"""
|
|
393
|
+
|
|
394
|
+
@property
|
|
395
|
+
def model_class(self):
|
|
396
|
+
""" """
|
|
397
|
+
model = self.app.model
|
|
398
|
+
return model.Role
|
|
399
|
+
|
|
400
|
+
def sort_query(self, query):
|
|
401
|
+
""" """
|
|
402
|
+
return query.order_by(self.model_class.name)
|
|
403
|
+
|
|
404
|
+
def get_object_url(self, role):
|
|
405
|
+
""" """
|
|
406
|
+
return self.request.route_url('roles.view', uuid=role.uuid)
|
|
407
|
+
|
|
408
|
+
|
|
347
409
|
class UserRef(ObjectRef):
|
|
348
410
|
"""
|
|
349
411
|
Custom schema type for a
|
|
@@ -391,16 +453,24 @@ class RoleRefs(WuttaSet):
|
|
|
391
453
|
if 'values' not in kwargs:
|
|
392
454
|
model = self.app.model
|
|
393
455
|
auth = self.app.get_auth_handler()
|
|
456
|
+
|
|
457
|
+
# avoid built-ins which cannot be assigned to users
|
|
394
458
|
avoid = {
|
|
395
459
|
auth.get_role_authenticated(self.session),
|
|
396
460
|
auth.get_role_anonymous(self.session),
|
|
397
461
|
}
|
|
398
462
|
avoid = set([role.uuid for role in avoid])
|
|
463
|
+
|
|
464
|
+
# also avoid admin unless current user is root
|
|
465
|
+
if not self.request.is_root:
|
|
466
|
+
avoid.add(auth.get_role_administrator(self.session).uuid)
|
|
467
|
+
|
|
468
|
+
# everything else can be (un)assigned for users
|
|
399
469
|
roles = self.session.query(model.Role)\
|
|
400
470
|
.filter(~model.Role.uuid.in_(avoid))\
|
|
401
471
|
.order_by(model.Role.name)\
|
|
402
472
|
.all()
|
|
403
|
-
values = [(role.uuid, role.name) for role in roles]
|
|
473
|
+
values = [(role.uuid.hex, role.name) for role in roles]
|
|
404
474
|
kwargs['values'] = values
|
|
405
475
|
|
|
406
476
|
return widgets.RoleRefsWidget(self.request, **kwargs)
|
|
@@ -497,3 +567,7 @@ class FileDownload(colander.String):
|
|
|
497
567
|
""" """
|
|
498
568
|
kwargs.setdefault('url', self.url)
|
|
499
569
|
return widgets.FileDownloadWidget(self.request, **kwargs)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
# nb. colanderalchemy schema overrides
|
|
573
|
+
sa.DateTime.__colanderalchemy_config__ = {'typ': WuttaDateTime}
|
|
@@ -36,9 +36,11 @@ in the namespace:
|
|
|
36
36
|
* :class:`deform:deform.widget.CheckboxWidget`
|
|
37
37
|
* :class:`deform:deform.widget.SelectWidget`
|
|
38
38
|
* :class:`deform:deform.widget.CheckboxChoiceWidget`
|
|
39
|
+
* :class:`deform:deform.widget.DateTimeInputWidget`
|
|
39
40
|
* :class:`deform:deform.widget.MoneyInputWidget`
|
|
40
41
|
"""
|
|
41
42
|
|
|
43
|
+
import datetime
|
|
42
44
|
import os
|
|
43
45
|
|
|
44
46
|
import colander
|
|
@@ -46,7 +48,7 @@ import humanize
|
|
|
46
48
|
from deform.widget import (Widget, TextInputWidget, TextAreaWidget,
|
|
47
49
|
PasswordWidget, CheckedPasswordWidget,
|
|
48
50
|
CheckboxWidget, SelectWidget, CheckboxChoiceWidget,
|
|
49
|
-
MoneyInputWidget)
|
|
51
|
+
DateTimeInputWidget, MoneyInputWidget)
|
|
50
52
|
from webhelpers2.html import HTML
|
|
51
53
|
|
|
52
54
|
from wuttaweb.db import Session
|
|
@@ -102,7 +104,7 @@ class ObjectRefWidget(SelectWidget):
|
|
|
102
104
|
# add url, only if rendering readonly
|
|
103
105
|
readonly = kw.get('readonly', self.readonly)
|
|
104
106
|
if readonly:
|
|
105
|
-
if 'url' not in values and self.url and field.schema
|
|
107
|
+
if 'url' not in values and self.url and getattr(field.schema, 'model_instance', None):
|
|
106
108
|
values['url'] = self.url(field.schema.model_instance)
|
|
107
109
|
|
|
108
110
|
return values
|
|
@@ -153,6 +155,43 @@ class WuttaCheckboxChoiceWidget(CheckboxChoiceWidget):
|
|
|
153
155
|
self.session = session or Session()
|
|
154
156
|
|
|
155
157
|
|
|
158
|
+
class WuttaDateTimeWidget(DateTimeInputWidget):
|
|
159
|
+
"""
|
|
160
|
+
Custom widget for :class:`python:datetime.datetime` fields.
|
|
161
|
+
|
|
162
|
+
The main purpose of this widget is to leverage
|
|
163
|
+
:meth:`~wuttjamaican:wuttjamaican.app.AppHandler.render_datetime()`
|
|
164
|
+
for the readonly display.
|
|
165
|
+
|
|
166
|
+
It is automatically used for SQLAlchemy mapped classes where the
|
|
167
|
+
field maps to a :class:`sqlalchemy:sqlalchemy.types.DateTime`
|
|
168
|
+
column. For other (non-mapped) datetime fields, you may have to
|
|
169
|
+
use it explicitly via
|
|
170
|
+
:meth:`~wuttaweb.forms.base.Form.set_widget()`.
|
|
171
|
+
|
|
172
|
+
This is a subclass of
|
|
173
|
+
:class:`deform:deform.widget.DateTimeInputWidget` and uses these
|
|
174
|
+
Deform templates:
|
|
175
|
+
|
|
176
|
+
* ``datetimeinput``
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
def __init__(self, request, *args, **kwargs):
|
|
180
|
+
super().__init__(*args, **kwargs)
|
|
181
|
+
self.request = request
|
|
182
|
+
self.config = self.request.wutta_config
|
|
183
|
+
self.app = self.config.get_app()
|
|
184
|
+
|
|
185
|
+
def serialize(self, field, cstruct, **kw):
|
|
186
|
+
""" """
|
|
187
|
+
readonly = kw.get('readonly', self.readonly)
|
|
188
|
+
if readonly and cstruct:
|
|
189
|
+
dt = datetime.datetime.fromisoformat(cstruct)
|
|
190
|
+
return self.app.render_datetime(dt)
|
|
191
|
+
|
|
192
|
+
return super().serialize(field, cstruct, **kw)
|
|
193
|
+
|
|
194
|
+
|
|
156
195
|
class FileDownloadWidget(Widget):
|
|
157
196
|
"""
|
|
158
197
|
Widget for use with :class:`~wuttaweb.forms.schema.FileDownload`
|
|
@@ -210,6 +249,44 @@ class FileDownloadWidget(Widget):
|
|
|
210
249
|
return humanize.naturalsize(size)
|
|
211
250
|
|
|
212
251
|
|
|
252
|
+
class GridWidget(Widget):
|
|
253
|
+
"""
|
|
254
|
+
Widget for fields whose data is represented by a :term:`grid`.
|
|
255
|
+
|
|
256
|
+
This is a subclass of :class:`deform:deform.widget.Widget` but
|
|
257
|
+
does not use any Deform templates.
|
|
258
|
+
|
|
259
|
+
This widget only supports "readonly" mode, is not editable. It is
|
|
260
|
+
merely a convenience around the grid itself, which does the heavy
|
|
261
|
+
lifting.
|
|
262
|
+
|
|
263
|
+
Instead of creating this widget directly you probably should call
|
|
264
|
+
:meth:`~wuttaweb.forms.base.Form.set_grid()` on your form.
|
|
265
|
+
|
|
266
|
+
:param request: Current :term:`request` object.
|
|
267
|
+
|
|
268
|
+
:param grid: :class:`~wuttaweb.grids.base.Grid` instance, used to
|
|
269
|
+
display the field data.
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
def __init__(self, request, grid, *args, **kwargs):
|
|
273
|
+
super().__init__(*args, **kwargs)
|
|
274
|
+
self.request = request
|
|
275
|
+
self.grid = grid
|
|
276
|
+
|
|
277
|
+
def serialize(self, field, cstruct, **kw):
|
|
278
|
+
"""
|
|
279
|
+
This widget simply calls
|
|
280
|
+
:meth:`~wuttaweb.grids.base.Grid.render_table_element()` on
|
|
281
|
+
the ``grid`` to serialize.
|
|
282
|
+
"""
|
|
283
|
+
readonly = kw.get('readonly', self.readonly)
|
|
284
|
+
if not readonly:
|
|
285
|
+
raise NotImplementedError("edit not allowed for this widget")
|
|
286
|
+
|
|
287
|
+
return self.grid.render_table_element()
|
|
288
|
+
|
|
289
|
+
|
|
213
290
|
class RoleRefsWidget(WuttaCheckboxChoiceWidget):
|
|
214
291
|
"""
|
|
215
292
|
Widget for use with User
|
|
@@ -280,7 +357,7 @@ class UserRefsWidget(WuttaCheckboxChoiceWidget):
|
|
|
280
357
|
raise NotImplementedError("edit not allowed for this widget")
|
|
281
358
|
|
|
282
359
|
model = self.app.model
|
|
283
|
-
columns = ['
|
|
360
|
+
columns = ['username', 'active']
|
|
284
361
|
|
|
285
362
|
# generate data set for users
|
|
286
363
|
users = []
|
|
@@ -344,3 +421,22 @@ class PermissionsWidget(WuttaCheckboxChoiceWidget):
|
|
|
344
421
|
kw['values'] = values
|
|
345
422
|
|
|
346
423
|
return super().serialize(field, cstruct, **kw)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
class BatchIdWidget(Widget):
|
|
427
|
+
"""
|
|
428
|
+
Widget for use with the
|
|
429
|
+
:attr:`~wuttjamaican:wuttjamaican.db.model.batch.BatchMixin.id`
|
|
430
|
+
field of a :term:`batch` model.
|
|
431
|
+
|
|
432
|
+
This widget is "always" read-only and renders the Batch ID as
|
|
433
|
+
zero-padded 8-char string
|
|
434
|
+
"""
|
|
435
|
+
|
|
436
|
+
def serialize(self, field, cstruct, **kw):
|
|
437
|
+
""" """
|
|
438
|
+
if cstruct is colander.null:
|
|
439
|
+
return colander.null
|
|
440
|
+
|
|
441
|
+
batch_id = int(cstruct)
|
|
442
|
+
return f'{batch_id:08d}'
|