WuttaWeb 0.16.0__tar.gz → 0.16.2__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.0 → wuttaweb-0.16.2}/CHANGELOG.md +15 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/PKG-INFO +2 -2
- wuttaweb-0.16.2/docs/glossary.rst +15 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/pyproject.toml +2 -2
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/forms/base.py +28 -11
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/forms/schema.py +12 -3
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/forms/widgets.py +39 -1
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/grids/base.py +1 -1
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/grids/filters.py +35 -4
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/base.mako +1 -1
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/util.py +5 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/forms/test_base.py +14 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/forms/test_schema.py +1 -1
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/forms/test_widgets.py +30 -4
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/grids/test_filters.py +47 -3
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/test_auth.py +2 -1
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/test_util.py +6 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/views/test_common.py +1 -1
- wuttaweb-0.16.0/docs/glossary.rst +0 -10
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/.gitignore +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/COPYING.txt +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/README.md +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/Makefile +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/_static/.keepme +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.app.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.auth.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.conf.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.db.continuum.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.db.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.db.sess.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.forms.base.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.forms.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.forms.schema.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.forms.widgets.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.grids.base.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.grids.filters.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.grids.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.handler.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.helpers.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.menus.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.progress.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.static.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.subscribers.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.util.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.views.auth.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.views.base.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.views.common.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.views.essential.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.views.master.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.views.people.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.views.progress.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.views.roles.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.views.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.views.settings.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.views.upgrades.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/api/wuttaweb.views.users.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/conf.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/index.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/make.bat +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/narr/templates/base.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/narr/templates/index.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/narr/templates/lookup.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/docs/narr/templates/overview.rst +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/__init__.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/_version.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/app.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/auth.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/conf.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/db/__init__.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/db/continuum.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/db/sess.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/email/templates/feedback.html.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/email/templates/feedback.txt.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/forms/__init__.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/grids/__init__.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/handler.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/helpers.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/menus.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/progress.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/static/__init__.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/static/img/favicon.ico +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/static/img/logo.png +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/static/img/testing.png +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/subscribers.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/appinfo/index.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/auth/change_password.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/auth/login.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/base_meta.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/configure.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/deform/password.pt +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/deform/permissions.pt +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/deform/select.pt +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/deform/textarea.pt +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/deform/textinput.pt +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/forbidden.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/form.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/grids/table_element.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/grids/vue_template.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/home.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/master/configure.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/master/create.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/master/delete.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/master/edit.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/master/form.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/master/index.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/master/view.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/notfound.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/page.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/people/view_profile.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/progress.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/setup.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/upgrade.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/upgrades/view.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/templates/wutta-components.mako +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/views/__init__.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/views/auth.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/views/base.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/views/common.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/views/essential.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/views/master.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/views/people.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/views/progress.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/views/roles.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/views/settings.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/views/upgrades.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/src/wuttaweb/views/users.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tasks.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/__init__.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/db/__init__.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/db/test_continuum.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/forms/__init__.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/grids/__init__.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/grids/test_base.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/libcache/bb_oruga.js +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/libcache/bb_oruga_bulma.css +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/libcache/bb_oruga_bulma.js +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/libcache/bb_vue.js +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/libcache/bb_vue_fontawesome.js +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/libcache/buefy.css +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/libcache/buefy.js +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/libcache/fontawesome.js +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/libcache/vue.js +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/libcache/vue_resource.js +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/test_app.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/test_handler.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/test_helpers.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/test_menus.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/test_progress.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/test_static.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/test_subscribers.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/util.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/views/__init__.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/views/test___init__.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/views/test_auth.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/views/test_base.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/views/test_master.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/views/test_people.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/views/test_progress.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/views/test_roles.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/views/test_settings.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/views/test_upgrades.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tests/views/test_users.py +0 -0
- {wuttaweb-0.16.0 → wuttaweb-0.16.2}/tox.ini +0 -0
|
@@ -5,6 +5,21 @@ 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.16.2 (2024-12-10)
|
|
9
|
+
|
|
10
|
+
### Fix
|
|
11
|
+
|
|
12
|
+
- add `GridWidget` and `form.set_grid()` for convenience
|
|
13
|
+
- add "is false or null" grid filter, for nullable bool columns
|
|
14
|
+
- remove Person column for `Person.users` grid display
|
|
15
|
+
- flatten UUID to str for `make_json_safe()`
|
|
16
|
+
|
|
17
|
+
## v0.16.1 (2024-12-08)
|
|
18
|
+
|
|
19
|
+
### Fix
|
|
20
|
+
|
|
21
|
+
- refactor to reflect usage of proper UUID values
|
|
22
|
+
|
|
8
23
|
## v0.16.0 (2024-12-05)
|
|
9
24
|
|
|
10
25
|
### Feat
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: WuttaWeb
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.2
|
|
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
|
|
@@ -36,7 +36,7 @@ Requires-Dist: pyramid-tm
|
|
|
36
36
|
Requires-Dist: pyramid>=2
|
|
37
37
|
Requires-Dist: waitress
|
|
38
38
|
Requires-Dist: webhelpers2
|
|
39
|
-
Requires-Dist: wuttjamaican[db]>=0.
|
|
39
|
+
Requires-Dist: wuttjamaican[db]>=0.17.1
|
|
40
40
|
Requires-Dist: zope-sqlalchemy>=1.5
|
|
41
41
|
Provides-Extra: continuum
|
|
42
42
|
Requires-Dist: wutta-continuum; extra == 'continuum'
|
|
@@ -0,0 +1,15 @@
|
|
|
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.
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "WuttaWeb"
|
|
9
|
-
version = "0.16.
|
|
9
|
+
version = "0.16.2"
|
|
10
10
|
description = "Web App for Wutta Framework"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
|
|
@@ -42,7 +42,7 @@ dependencies = [
|
|
|
42
42
|
"pyramid_tm",
|
|
43
43
|
"waitress",
|
|
44
44
|
"WebHelpers2",
|
|
45
|
-
"WuttJamaican[db]>=0.
|
|
45
|
+
"WuttJamaican[db]>=0.17.1",
|
|
46
46
|
"zope.sqlalchemy>=1.5",
|
|
47
47
|
]
|
|
48
48
|
|
|
@@ -474,6 +474,34 @@ class Form:
|
|
|
474
474
|
if self.schema and key in self.schema:
|
|
475
475
|
self.schema[key].widget = widget
|
|
476
476
|
|
|
477
|
+
def set_grid(self, key, grid):
|
|
478
|
+
"""
|
|
479
|
+
Establish a :term:`grid` to be displayed for a field. This
|
|
480
|
+
uses a :class:`~wuttaweb.forms.widgets.GridWidget` to wrap the
|
|
481
|
+
rendered grid.
|
|
482
|
+
|
|
483
|
+
:param key: Name of field.
|
|
484
|
+
|
|
485
|
+
:param widget: :class:`~wuttaweb.grids.base.Grid` instance,
|
|
486
|
+
pre-configured and (usually) with data.
|
|
487
|
+
"""
|
|
488
|
+
from wuttaweb.forms.widgets import GridWidget
|
|
489
|
+
|
|
490
|
+
widget = GridWidget(self.request, grid)
|
|
491
|
+
self.set_widget(key, widget)
|
|
492
|
+
self.add_grid_vue_context(grid)
|
|
493
|
+
|
|
494
|
+
def add_grid_vue_context(self, grid):
|
|
495
|
+
""" """
|
|
496
|
+
if not grid.key:
|
|
497
|
+
raise ValueError("grid must have a key!")
|
|
498
|
+
|
|
499
|
+
if grid.key in self.grid_vue_context:
|
|
500
|
+
log.warning("grid data with key '%s' already registered, "
|
|
501
|
+
"but will be replaced", grid.key)
|
|
502
|
+
|
|
503
|
+
self.grid_vue_context[grid.key] = grid.get_vue_context()
|
|
504
|
+
|
|
477
505
|
def set_validator(self, key, validator):
|
|
478
506
|
"""
|
|
479
507
|
Set/override the validator for a field, or the form.
|
|
@@ -848,17 +876,6 @@ class Form:
|
|
|
848
876
|
output = render(template, context)
|
|
849
877
|
return HTML.literal(output)
|
|
850
878
|
|
|
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
879
|
def render_vue_field(
|
|
863
880
|
self,
|
|
864
881
|
fieldname,
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
Form schema types
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
|
+
import uuid as _uuid
|
|
28
|
+
|
|
27
29
|
import colander
|
|
28
30
|
|
|
29
31
|
from wuttaweb.db import Session
|
|
@@ -212,7 +214,7 @@ class ObjectRef(colander.SchemaType):
|
|
|
212
214
|
node.model_instance = appstruct
|
|
213
215
|
|
|
214
216
|
# serialize to uuid
|
|
215
|
-
return appstruct.uuid
|
|
217
|
+
return appstruct.uuid.hex
|
|
216
218
|
|
|
217
219
|
def deserialize(self, node, cstruct):
|
|
218
220
|
""" """
|
|
@@ -246,7 +248,14 @@ class ObjectRef(colander.SchemaType):
|
|
|
246
248
|
|
|
247
249
|
# fetch object from DB
|
|
248
250
|
model = self.app.model
|
|
249
|
-
obj =
|
|
251
|
+
obj = None
|
|
252
|
+
if isinstance(value, _uuid.UUID):
|
|
253
|
+
obj = self.session.get(self.model_class, value)
|
|
254
|
+
else:
|
|
255
|
+
try:
|
|
256
|
+
obj = self.session.get(self.model_class, _uuid.UUID(value))
|
|
257
|
+
except ValueError:
|
|
258
|
+
pass
|
|
250
259
|
|
|
251
260
|
# raise error if not found
|
|
252
261
|
if not obj:
|
|
@@ -287,7 +296,7 @@ class ObjectRef(colander.SchemaType):
|
|
|
287
296
|
if 'values' not in kwargs:
|
|
288
297
|
query = self.get_query()
|
|
289
298
|
objects = query.all()
|
|
290
|
-
values = [(obj.uuid, str(obj))
|
|
299
|
+
values = [(obj.uuid.hex, str(obj))
|
|
291
300
|
for obj in objects]
|
|
292
301
|
if self.empty_option:
|
|
293
302
|
values.insert(0, self.empty_option)
|
|
@@ -210,6 +210,44 @@ class FileDownloadWidget(Widget):
|
|
|
210
210
|
return humanize.naturalsize(size)
|
|
211
211
|
|
|
212
212
|
|
|
213
|
+
class GridWidget(Widget):
|
|
214
|
+
"""
|
|
215
|
+
Widget for fields whose data is represented by a :term:`grid`.
|
|
216
|
+
|
|
217
|
+
This is a subclass of :class:`deform:deform.widget.Widget` but
|
|
218
|
+
does not use any Deform templates.
|
|
219
|
+
|
|
220
|
+
This widget only supports "readonly" mode, is not editable. It is
|
|
221
|
+
merely a convenience around the grid itself, which does the heavy
|
|
222
|
+
lifting.
|
|
223
|
+
|
|
224
|
+
Instead of creating this widget directly you probably should call
|
|
225
|
+
:meth:`~wuttaweb.forms.base.Form.set_grid()` on your form.
|
|
226
|
+
|
|
227
|
+
:param request: Current :term:`request` object.
|
|
228
|
+
|
|
229
|
+
:param grid: :class:`~wuttaweb.grids.base.Grid` instance, used to
|
|
230
|
+
display the field data.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
def __init__(self, request, grid, *args, **kwargs):
|
|
234
|
+
super().__init__(*args, **kwargs)
|
|
235
|
+
self.request = request
|
|
236
|
+
self.grid = grid
|
|
237
|
+
|
|
238
|
+
def serialize(self, field, cstruct, **kw):
|
|
239
|
+
"""
|
|
240
|
+
This widget simply calls
|
|
241
|
+
:meth:`~wuttaweb.grids.base.Grid.render_table_element()` on
|
|
242
|
+
the ``grid`` to serialize.
|
|
243
|
+
"""
|
|
244
|
+
readonly = kw.get('readonly', self.readonly)
|
|
245
|
+
if not readonly:
|
|
246
|
+
raise NotImplementedError("edit not allowed for this widget")
|
|
247
|
+
|
|
248
|
+
return self.grid.render_table_element()
|
|
249
|
+
|
|
250
|
+
|
|
213
251
|
class RoleRefsWidget(WuttaCheckboxChoiceWidget):
|
|
214
252
|
"""
|
|
215
253
|
Widget for use with User
|
|
@@ -280,7 +318,7 @@ class UserRefsWidget(WuttaCheckboxChoiceWidget):
|
|
|
280
318
|
raise NotImplementedError("edit not allowed for this widget")
|
|
281
319
|
|
|
282
320
|
model = self.app.model
|
|
283
|
-
columns = ['
|
|
321
|
+
columns = ['username', 'active']
|
|
284
322
|
|
|
285
323
|
# generate data set for users
|
|
286
324
|
users = []
|
|
@@ -116,20 +116,22 @@ class GridFilter:
|
|
|
116
116
|
'is_any': "is any",
|
|
117
117
|
'equal': "equal to",
|
|
118
118
|
'not_equal': "not equal to",
|
|
119
|
-
'is_null': "is null",
|
|
120
|
-
'is_not_null': "is not null",
|
|
121
119
|
'is_true': "is true",
|
|
122
120
|
'is_false': "is false",
|
|
121
|
+
'is_false_null': "is false or null",
|
|
122
|
+
'is_null': "is null",
|
|
123
|
+
'is_not_null': "is not null",
|
|
123
124
|
'contains': "contains",
|
|
124
125
|
'does_not_contain': "does not contain",
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
valueless_verbs = [
|
|
128
129
|
'is_any',
|
|
129
|
-
'is_null',
|
|
130
|
-
'is_not_null',
|
|
131
130
|
'is_true',
|
|
132
131
|
'is_false',
|
|
132
|
+
'is_false_null',
|
|
133
|
+
'is_null',
|
|
134
|
+
'is_not_null',
|
|
133
135
|
]
|
|
134
136
|
|
|
135
137
|
def __init__(
|
|
@@ -416,6 +418,27 @@ class BooleanAlchemyFilter(AlchemyFilter):
|
|
|
416
418
|
"""
|
|
417
419
|
default_verbs = ['is_true', 'is_false']
|
|
418
420
|
|
|
421
|
+
def get_verbs(self):
|
|
422
|
+
""" """
|
|
423
|
+
|
|
424
|
+
# get basic verbs from caller, or default list
|
|
425
|
+
verbs = getattr(self, 'verbs', self.default_verbs)
|
|
426
|
+
if callable(verbs):
|
|
427
|
+
verbs = verbs()
|
|
428
|
+
verbs = list(verbs)
|
|
429
|
+
|
|
430
|
+
# add some more if column is nullable
|
|
431
|
+
if self.nullable:
|
|
432
|
+
for verb in ('is_false_null', 'is_null', 'is_not_null'):
|
|
433
|
+
if verb not in verbs:
|
|
434
|
+
verbs.append(verb)
|
|
435
|
+
|
|
436
|
+
# add wildcard
|
|
437
|
+
if 'is_any' not in verbs:
|
|
438
|
+
verbs.append('is_any')
|
|
439
|
+
|
|
440
|
+
return verbs
|
|
441
|
+
|
|
419
442
|
def coerce_value(self, value):
|
|
420
443
|
""" """
|
|
421
444
|
if value is not None:
|
|
@@ -435,6 +458,14 @@ class BooleanAlchemyFilter(AlchemyFilter):
|
|
|
435
458
|
"""
|
|
436
459
|
return query.filter(self.model_property == False)
|
|
437
460
|
|
|
461
|
+
def filter_is_false_null(self, query, value):
|
|
462
|
+
"""
|
|
463
|
+
Filter data with "is false or null" condition. The value is
|
|
464
|
+
ignored.
|
|
465
|
+
"""
|
|
466
|
+
return query.filter(sa.or_(self.model_property == False,
|
|
467
|
+
self.model_property == None))
|
|
468
|
+
|
|
438
469
|
|
|
439
470
|
default_sqlalchemy_filters = {
|
|
440
471
|
None: AlchemyFilter,
|
|
@@ -563,7 +563,7 @@
|
|
|
563
563
|
|
|
564
564
|
const WuttaFeedbackFormData = {
|
|
565
565
|
referrer: null,
|
|
566
|
-
userUUID: ${json.dumps(request.user.uuid if request.user else None)|n},
|
|
566
|
+
userUUID: ${json.dumps(request.user.uuid.hex if request.user else None)|n},
|
|
567
567
|
userName: ${json.dumps(str(request.user) if request.user else None)|n},
|
|
568
568
|
showDialog: false,
|
|
569
569
|
sendingFeedback: false,
|
|
@@ -27,6 +27,7 @@ Web Utilities
|
|
|
27
27
|
import importlib
|
|
28
28
|
import json
|
|
29
29
|
import logging
|
|
30
|
+
import uuid as _uuid
|
|
30
31
|
import warnings
|
|
31
32
|
|
|
32
33
|
import sqlalchemy as sa
|
|
@@ -531,6 +532,10 @@ def make_json_safe(value, key=None, warn=True):
|
|
|
531
532
|
parent[key] = make_json_safe(value, key=key, warn=warn)
|
|
532
533
|
value = parent
|
|
533
534
|
|
|
535
|
+
# convert UUID to str
|
|
536
|
+
if isinstance(value, _uuid.UUID):
|
|
537
|
+
value = value.hex
|
|
538
|
+
|
|
534
539
|
# ensure JSON-compatibility, warn if problems
|
|
535
540
|
try:
|
|
536
541
|
json.dumps(value)
|
|
@@ -142,6 +142,20 @@ class TestForm(TestCase):
|
|
|
142
142
|
self.assertIs(form.widgets['foo'], new_widget)
|
|
143
143
|
self.assertIs(schema['foo'].widget, new_widget)
|
|
144
144
|
|
|
145
|
+
def test_set_grid(self):
|
|
146
|
+
form = self.make_form(fields=['foo', 'bar'])
|
|
147
|
+
self.assertNotIn('foo', form.widgets)
|
|
148
|
+
self.assertNotIn('foogrid', form.grid_vue_context)
|
|
149
|
+
|
|
150
|
+
grid = Grid(self.request, key='foogrid',
|
|
151
|
+
columns=['a', 'b'],
|
|
152
|
+
data=[{'a': 1, 'b': 2}, {'a': 3, 'b': 4}])
|
|
153
|
+
|
|
154
|
+
form.set_grid('foo', grid)
|
|
155
|
+
self.assertIn('foo', form.widgets)
|
|
156
|
+
self.assertIsInstance(form.widgets['foo'], widgets.GridWidget)
|
|
157
|
+
self.assertIn('foogrid', form.grid_vue_context)
|
|
158
|
+
|
|
145
159
|
def test_set_validator(self):
|
|
146
160
|
form = self.make_form(fields=['foo', 'bar'])
|
|
147
161
|
self.assertEqual(form.validators, {})
|
|
@@ -100,7 +100,7 @@ class TestObjectRef(DataTestCase):
|
|
|
100
100
|
self.assertIsNotNone(person.uuid)
|
|
101
101
|
typ = mod.ObjectRef(self.request)
|
|
102
102
|
value = typ.serialize(node, person)
|
|
103
|
-
self.assertEqual(value, person.uuid)
|
|
103
|
+
self.assertEqual(value, person.uuid.hex)
|
|
104
104
|
|
|
105
105
|
def test_deserialize(self):
|
|
106
106
|
model = self.app.model
|
|
@@ -6,6 +6,7 @@ import colander
|
|
|
6
6
|
import deform
|
|
7
7
|
from pyramid import testing
|
|
8
8
|
|
|
9
|
+
from wuttaweb import grids
|
|
9
10
|
from wuttaweb.forms import widgets as mod
|
|
10
11
|
from wuttaweb.forms.schema import FileDownload, PersonRef, RoleRefs, UserRefs, Permissions
|
|
11
12
|
from tests.util import WebTestCase
|
|
@@ -117,6 +118,31 @@ class TestFileDownloadWidget(WebTestCase):
|
|
|
117
118
|
self.assertEqual(html2, html)
|
|
118
119
|
|
|
119
120
|
|
|
121
|
+
class TestGridWidget(WebTestCase):
|
|
122
|
+
|
|
123
|
+
def make_field(self, node, **kwargs):
|
|
124
|
+
# TODO: not sure why default renderer is in use even though
|
|
125
|
+
# pyramid_deform was included in setup? but this works..
|
|
126
|
+
kwargs.setdefault('renderer', deform.Form.default_renderer)
|
|
127
|
+
return deform.Field(node, **kwargs)
|
|
128
|
+
|
|
129
|
+
def test_serialize(self):
|
|
130
|
+
grid = grids.Grid(self.request,
|
|
131
|
+
columns=['foo', 'bar'],
|
|
132
|
+
data=[{'foo': 1, 'bar': 2}, {'foo': 3, 'bar': 4}])
|
|
133
|
+
|
|
134
|
+
node = colander.SchemaNode(colander.String())
|
|
135
|
+
widget = mod.GridWidget(self.request, grid)
|
|
136
|
+
field = self.make_field(node)
|
|
137
|
+
|
|
138
|
+
# readonly works okay
|
|
139
|
+
html = widget.serialize(field, None, readonly=True)
|
|
140
|
+
self.assertIn('<b-table ', html)
|
|
141
|
+
|
|
142
|
+
# but otherwise, error
|
|
143
|
+
self.assertRaises(NotImplementedError, widget.serialize, field, None)
|
|
144
|
+
|
|
145
|
+
|
|
120
146
|
class TestRoleRefsWidget(WebTestCase):
|
|
121
147
|
|
|
122
148
|
def make_field(self, node, **kwargs):
|
|
@@ -146,14 +172,14 @@ class TestRoleRefsWidget(WebTestCase):
|
|
|
146
172
|
|
|
147
173
|
# editable values list *excludes* admin (by default)
|
|
148
174
|
html = widget.serialize(field, {admin.uuid, blokes.uuid})
|
|
149
|
-
self.assertNotIn(admin.uuid, html)
|
|
150
|
-
self.assertIn(blokes.uuid, html)
|
|
175
|
+
self.assertNotIn(str(admin.uuid), html)
|
|
176
|
+
self.assertIn(str(blokes.uuid), html)
|
|
151
177
|
|
|
152
178
|
# but admin is included for root user
|
|
153
179
|
self.request.is_root = True
|
|
154
180
|
html = widget.serialize(field, {admin.uuid, blokes.uuid})
|
|
155
|
-
self.assertIn(admin.uuid, html)
|
|
156
|
-
self.assertIn(blokes.uuid, html)
|
|
181
|
+
self.assertIn(str(admin.uuid), html)
|
|
182
|
+
self.assertIn(str(blokes.uuid), html)
|
|
157
183
|
|
|
158
184
|
|
|
159
185
|
class TestUserRefsWidget(WebTestCase):
|
|
@@ -328,9 +328,15 @@ class TestBooleanAlchemyFilter(WebTestCase):
|
|
|
328
328
|
|
|
329
329
|
model = self.app.model
|
|
330
330
|
self.sample_data = [
|
|
331
|
-
{'username': 'alice',
|
|
332
|
-
|
|
333
|
-
|
|
331
|
+
{'username': 'alice',
|
|
332
|
+
'prevent_edit': False,
|
|
333
|
+
'active': True},
|
|
334
|
+
{'username': 'bob',
|
|
335
|
+
'prevent_edit': True,
|
|
336
|
+
'active': True},
|
|
337
|
+
{'username': 'charlie',
|
|
338
|
+
'active': False,
|
|
339
|
+
'prevent_edit': None},
|
|
334
340
|
]
|
|
335
341
|
for user in self.sample_data:
|
|
336
342
|
user = model.User(**user)
|
|
@@ -343,6 +349,34 @@ class TestBooleanAlchemyFilter(WebTestCase):
|
|
|
343
349
|
kwargs['model_property'] = model_property
|
|
344
350
|
return factory(self.request, model_property.key, **kwargs)
|
|
345
351
|
|
|
352
|
+
def test_get_verbs(self):
|
|
353
|
+
model = self.app.model
|
|
354
|
+
|
|
355
|
+
# bool field, not nullable
|
|
356
|
+
filtr = self.make_filter(model.User.active,
|
|
357
|
+
factory=mod.BooleanAlchemyFilter,
|
|
358
|
+
nullable=False)
|
|
359
|
+
self.assertFalse(hasattr(filtr, 'verbs'))
|
|
360
|
+
self.assertEqual(filtr.default_verbs, ['is_true', 'is_false'])
|
|
361
|
+
|
|
362
|
+
# by default, returns default verbs (plus 'is_any')
|
|
363
|
+
self.assertEqual(filtr.get_verbs(), ['is_true', 'is_false', 'is_any'])
|
|
364
|
+
|
|
365
|
+
# default verbs can be a callable
|
|
366
|
+
filtr.default_verbs = lambda: ['foo', 'bar']
|
|
367
|
+
self.assertEqual(filtr.get_verbs(), ['foo', 'bar', 'is_any'])
|
|
368
|
+
|
|
369
|
+
# bool field, *nullable*
|
|
370
|
+
filtr = self.make_filter(model.User.active,
|
|
371
|
+
factory=mod.BooleanAlchemyFilter,
|
|
372
|
+
nullable=True)
|
|
373
|
+
self.assertFalse(hasattr(filtr, 'verbs'))
|
|
374
|
+
self.assertEqual(filtr.default_verbs, ['is_true', 'is_false'])
|
|
375
|
+
|
|
376
|
+
# effective verbs also include is_false_null
|
|
377
|
+
self.assertEqual(filtr.get_verbs(), ['is_true', 'is_false', 'is_false_null',
|
|
378
|
+
'is_null', 'is_not_null', 'is_any'])
|
|
379
|
+
|
|
346
380
|
def test_coerce_value(self):
|
|
347
381
|
model = self.app.model
|
|
348
382
|
filtr = self.make_filter(model.User.active)
|
|
@@ -377,6 +411,16 @@ class TestBooleanAlchemyFilter(WebTestCase):
|
|
|
377
411
|
self.assertIsNot(filtered_query, self.sample_query)
|
|
378
412
|
self.assertEqual(filtered_query.count(), 1)
|
|
379
413
|
|
|
414
|
+
def test_filter_is_false_null(self):
|
|
415
|
+
model = self.app.model
|
|
416
|
+
filtr = self.make_filter(model.User.prevent_edit)
|
|
417
|
+
self.assertEqual(self.sample_query.count(), 3)
|
|
418
|
+
|
|
419
|
+
# nb. only one account is marked with "prevent edit"
|
|
420
|
+
filtered_query = filtr.filter_is_false_null(self.sample_query, None)
|
|
421
|
+
self.assertIsNot(filtered_query, self.sample_query)
|
|
422
|
+
self.assertEqual(filtered_query.count(), 2)
|
|
423
|
+
|
|
380
424
|
|
|
381
425
|
class TestVerbNotSupported(TestCase):
|
|
382
426
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8; -*-
|
|
2
2
|
|
|
3
|
+
import uuid as _uuid
|
|
3
4
|
from unittest import TestCase
|
|
4
5
|
from unittest.mock import MagicMock
|
|
5
6
|
|
|
@@ -90,7 +91,7 @@ class TestWuttaSecurityPolicy(TestCase):
|
|
|
90
91
|
|
|
91
92
|
# invalid identity yields no user
|
|
92
93
|
self.policy = self.make_policy()
|
|
93
|
-
self.policy.remember(self.request,
|
|
94
|
+
self.policy.remember(self.request, _uuid.uuid4()) # random uuid
|
|
94
95
|
user = self.policy.identity(self.request)
|
|
95
96
|
self.assertIsNone(user)
|
|
96
97
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8; -*-
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import uuid as _uuid
|
|
4
5
|
from unittest import TestCase
|
|
5
6
|
from unittest.mock import patch, MagicMock
|
|
6
7
|
|
|
@@ -564,6 +565,11 @@ class TestMakeJsonSafe(TestCase):
|
|
|
564
565
|
value = mod.make_json_safe(person, key='person')
|
|
565
566
|
self.assertEqual(value, "Betty Boop")
|
|
566
567
|
|
|
568
|
+
def test_uuid(self):
|
|
569
|
+
uuid = _uuid.uuid4()
|
|
570
|
+
value = mod.make_json_safe(uuid)
|
|
571
|
+
self.assertEqual(value, uuid.hex)
|
|
572
|
+
|
|
567
573
|
def test_dict(self):
|
|
568
574
|
model = self.app.model
|
|
569
575
|
person = model.Person(full_name="Betty Boop")
|
|
@@ -88,7 +88,7 @@ class TestCommonView(WebTestCase):
|
|
|
88
88
|
|
|
89
89
|
# basic send, with user
|
|
90
90
|
self.request.user = user
|
|
91
|
-
self.request.POST['user_uuid'] = user.uuid
|
|
91
|
+
self.request.POST['user_uuid'] = str(user.uuid)
|
|
92
92
|
with patch.object(mod, 'Session', return_value=self.session):
|
|
93
93
|
context = view.feedback()
|
|
94
94
|
self.assertEqual(context, {'ok': True})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|