WuttaWeb 0.16.2__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.2 → wuttaweb-0.17.0}/CHANGELOG.md +23 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/PKG-INFO +3 -2
- wuttaweb-0.17.0/docs/api/wuttaweb.views.batch.rst +6 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/conf.py +2 -0
- wuttaweb-0.17.0/docs/glossary.rst +30 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/index.rst +1 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/pyproject.toml +7 -2
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/forms/base.py +104 -3
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/forms/schema.py +75 -1
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/forms/widgets.py +60 -2
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/grids/base.py +47 -0
- wuttaweb-0.17.0/src/wuttaweb/handler.py +144 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/menus.py +6 -1
- wuttaweb-0.17.0/src/wuttaweb/static/__init__.py +74 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/subscribers.py +8 -3
- {wuttaweb-0.16.2 → 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.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/objectref.pt +1 -1
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/form.mako +21 -0
- {wuttaweb-0.16.2 → 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.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/page.mako +5 -1
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/wutta-components.mako +163 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/util.py +16 -4
- {wuttaweb-0.16.2 → 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.2 → wuttaweb-0.17.0}/src/wuttaweb/views/master.py +278 -12
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/roles.py +64 -2
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/upgrades.py +1 -2
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/users.py +1 -2
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/forms/test_base.py +74 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/forms/test_schema.py +62 -1
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/forms/test_widgets.py +66 -5
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/grids/test_base.py +38 -0
- wuttaweb-0.17.0/tests/test_handler.py +76 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/test_util.py +25 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/util.py +5 -0
- wuttaweb-0.17.0/tests/views/test_batch.py +373 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_master.py +159 -2
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_roles.py +31 -0
- wuttaweb-0.16.2/docs/glossary.rst +0 -15
- wuttaweb-0.16.2/src/wuttaweb/handler.py +0 -57
- wuttaweb-0.16.2/src/wuttaweb/static/__init__.py +0 -37
- wuttaweb-0.16.2/src/wuttaweb/templates/base_meta.mako +0 -23
- wuttaweb-0.16.2/src/wuttaweb/templates/master/view.mako +0 -9
- wuttaweb-0.16.2/tests/test_handler.py +0 -20
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/.gitignore +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/COPYING.txt +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/README.md +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/Makefile +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/_static/.keepme +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.app.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.auth.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.conf.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.db.continuum.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.db.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.db.sess.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.forms.base.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.forms.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.forms.schema.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.forms.widgets.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.grids.base.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.grids.filters.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.grids.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.handler.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.helpers.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.menus.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.progress.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.static.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.subscribers.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.util.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.auth.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.base.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.common.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.essential.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.master.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.people.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.progress.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.roles.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.settings.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.upgrades.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.users.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/make.bat +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/narr/templates/base.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/narr/templates/index.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/narr/templates/lookup.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/narr/templates/overview.rst +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/__init__.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/_version.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/app.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/auth.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/conf.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/db/__init__.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/db/continuum.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/db/sess.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/email/templates/feedback.html.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/email/templates/feedback.txt.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/forms/__init__.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/grids/__init__.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/grids/filters.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/helpers.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/progress.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/static/img/favicon.ico +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/static/img/logo.png +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/static/img/testing.png +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/appinfo/index.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/auth/change_password.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/auth/login.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/configure.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/password.pt +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/permissions.pt +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/select.pt +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/textarea.pt +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/textinput.pt +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/forbidden.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/grids/table_element.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/grids/vue_template.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/home.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/configure.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/create.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/delete.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/edit.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/form.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/index.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/notfound.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/people/view_profile.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/progress.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/setup.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/upgrade.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/upgrades/view.mako +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/__init__.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/auth.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/common.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/essential.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/people.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/progress.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/settings.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tasks.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/__init__.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/db/__init__.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/db/test_continuum.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/forms/__init__.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/grids/__init__.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/grids/test_filters.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/bb_oruga.js +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/bb_oruga_bulma.css +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/bb_oruga_bulma.js +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/bb_vue.js +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/bb_vue_fontawesome.js +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/buefy.css +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/buefy.js +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/fontawesome.js +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/vue.js +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/vue_resource.js +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/test_app.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/test_auth.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/test_helpers.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/test_menus.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/test_progress.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/test_static.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/test_subscribers.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/__init__.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test___init__.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_auth.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_base.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_common.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_people.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_progress.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_settings.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_upgrades.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_users.py +0 -0
- {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tox.ini +0 -0
|
@@ -5,6 +5,29 @@ 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
|
+
|
|
8
31
|
## v0.16.2 (2024-12-10)
|
|
9
32
|
|
|
10
33
|
### 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,94 @@ 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
|
+
|
|
477
552
|
def set_grid(self, key, grid):
|
|
478
553
|
"""
|
|
479
554
|
Establish a :term:`grid` to be displayed for a field. This
|
|
@@ -1111,6 +1186,32 @@ class Form:
|
|
|
1111
1186
|
|
|
1112
1187
|
return self.validated
|
|
1113
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
|
+
|
|
1114
1215
|
def get_field_errors(self, field):
|
|
1115
1216
|
"""
|
|
1116
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
|
|
|
@@ -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`
|
|
@@ -382,3 +421,22 @@ class PermissionsWidget(WuttaCheckboxChoiceWidget):
|
|
|
382
421
|
kw['values'] = values
|
|
383
422
|
|
|
384
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}'
|
|
@@ -397,6 +397,7 @@ class Grid:
|
|
|
397
397
|
self.app = self.config.get_app()
|
|
398
398
|
|
|
399
399
|
self.set_columns(columns or self.get_columns())
|
|
400
|
+
self.set_default_renderers()
|
|
400
401
|
self.set_tools(tools)
|
|
401
402
|
|
|
402
403
|
# sorting
|
|
@@ -596,6 +597,35 @@ class Grid:
|
|
|
596
597
|
renderer = functools.partial(renderer, **kwargs)
|
|
597
598
|
self.renderers[key] = renderer
|
|
598
599
|
|
|
600
|
+
def set_default_renderers(self):
|
|
601
|
+
"""
|
|
602
|
+
Set default column value renderers, where applicable.
|
|
603
|
+
|
|
604
|
+
This will add new entries to :attr:`renderers` for columns
|
|
605
|
+
whose data type implies a default renderer should be used.
|
|
606
|
+
This is generally only possible if :attr:`model_class` is set
|
|
607
|
+
to a valid SQLAlchemy mapped class.
|
|
608
|
+
|
|
609
|
+
This (for now?) only looks for
|
|
610
|
+
:class:`sqlalchemy:sqlalchemy.types.DateTime` columns and if
|
|
611
|
+
any are found, they are configured to use
|
|
612
|
+
:meth:`render_datetime()`.
|
|
613
|
+
"""
|
|
614
|
+
if not self.model_class:
|
|
615
|
+
return
|
|
616
|
+
|
|
617
|
+
for key in self.columns:
|
|
618
|
+
if key in self.renderers:
|
|
619
|
+
continue
|
|
620
|
+
|
|
621
|
+
attr = getattr(self.model_class, key, None)
|
|
622
|
+
if attr:
|
|
623
|
+
prop = getattr(attr, 'prop', None)
|
|
624
|
+
if prop and isinstance(prop, orm.ColumnProperty):
|
|
625
|
+
column = prop.columns[0]
|
|
626
|
+
if isinstance(column.type, sa.DateTime):
|
|
627
|
+
self.set_renderer(key, self.render_datetime)
|
|
628
|
+
|
|
599
629
|
def set_link(self, key, link=True):
|
|
600
630
|
"""
|
|
601
631
|
Explicitly enable or disable auto-link behavior for a given
|
|
@@ -1725,6 +1755,23 @@ class Grid:
|
|
|
1725
1755
|
# rendering methods
|
|
1726
1756
|
##############################
|
|
1727
1757
|
|
|
1758
|
+
def render_datetime(self, obj, key, value):
|
|
1759
|
+
"""
|
|
1760
|
+
Default cell value renderer for
|
|
1761
|
+
:class:`sqlalchemy:sqlalchemy.types.DateTime` columns, which
|
|
1762
|
+
calls
|
|
1763
|
+
:meth:`~wuttjamaican:wuttjamaican.app.AppHandler.render_datetime()`
|
|
1764
|
+
for the return value.
|
|
1765
|
+
|
|
1766
|
+
This may be used automatically per
|
|
1767
|
+
:meth:`set_default_renderers()` or you can use it explicitly
|
|
1768
|
+
for any :class:`python:datetime.datetime` column with::
|
|
1769
|
+
|
|
1770
|
+
grid.set_renderer('foo', grid.render_datetime)
|
|
1771
|
+
"""
|
|
1772
|
+
dt = getattr(obj, key)
|
|
1773
|
+
return self.app.render_datetime(dt)
|
|
1774
|
+
|
|
1728
1775
|
def render_table_element(
|
|
1729
1776
|
self,
|
|
1730
1777
|
form=None,
|