WuttaWeb 0.20.6__tar.gz → 0.21.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.20.6 → wuttaweb-0.21.0}/CHANGELOG.md +10 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/PKG-INFO +2 -2
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/pyproject.toml +2 -2
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/forms/schema.py +0 -22
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/forms/widgets.py +0 -58
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/grids/base.py +18 -7
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/people.py +54 -13
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/users.py +52 -16
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/forms/test_schema.py +0 -14
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/forms/test_widgets.py +1 -46
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/grids/test_base.py +3 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_people.py +49 -15
- wuttaweb-0.21.0/tests/views/test_users.py +319 -0
- wuttaweb-0.20.6/tests/views/test_users.py +0 -219
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/.gitignore +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/COPYING.txt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/README.md +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/Makefile +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/_static/.keepme +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.app.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.auth.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.cli.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.cli.webapp.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.conf.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.db.continuum.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.db.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.db.sess.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.emails.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.forms.base.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.forms.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.forms.schema.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.forms.widgets.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.grids.base.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.grids.filters.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.grids.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.handler.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.helpers.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.menus.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.progress.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.static.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.subscribers.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.util.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.auth.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.base.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.batch.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.common.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.email.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.essential.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.master.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.people.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.progress.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.reports.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.roles.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.settings.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.upgrades.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.users.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/conf.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/glossary.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/index.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/make.bat +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/narr/cli/builtin.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/narr/cli/index.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/narr/templates/base.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/narr/templates/index.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/narr/templates/lookup.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/narr/templates/overview.rst +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/__init__.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/_version.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/app.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/auth.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/cli/__init__.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/cli/webapp.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/conf.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/db/__init__.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/db/continuum.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/db/sess.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/email-templates/feedback.html.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/email-templates/feedback.txt.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/emails.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/forms/__init__.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/forms/base.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/grids/__init__.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/grids/filters.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/handler.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/helpers.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/menus.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/progress.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/static/__init__.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/static/img/favicon.ico +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/static/img/logo.png +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/static/img/testing.png +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/subscribers.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/appinfo/index.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/auth/change_password.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/auth/login.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/base.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/base_meta.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/batch/view.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/configure.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/dateinput.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/datetimeinput.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/password.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/permissions.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/email_recips.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/select.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/textarea.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/textinput.pt +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/email/settings/view.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/forbidden.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/form.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/grids/table_element.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/grids/vue_template.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/home.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/configure.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/create.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/delete.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/edit.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/form.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/index.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/view.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/notfound.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/page.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/people/view_profile.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/progress.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/reports/view.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/setup.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/upgrade.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/upgrades/view.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/wutta-components.mako +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/testing.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/util.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/__init__.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/auth.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/base.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/batch.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/common.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/email.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/essential.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/master.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/progress.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/reports.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/roles.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/settings.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/upgrades.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tasks.py +0 -0
- {wuttaweb-0.20.6/tests/views → wuttaweb-0.21.0/tests}/__init__.py +0 -0
- {wuttaweb-0.20.6/tests/grids → wuttaweb-0.21.0/tests/cli}/__init__.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/cli/test_webapp.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/db/__init__.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/db/test_continuum.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/forms/test_base.py +0 -0
- {wuttaweb-0.20.6/tests/cli → wuttaweb-0.21.0/tests/grids}/__init__.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/grids/test_filters.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/bb_oruga.js +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/bb_oruga_bulma.css +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/bb_oruga_bulma.js +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/bb_vue.js +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/bb_vue_fontawesome.js +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/buefy.css +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/buefy.js +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/fontawesome.js +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/vue.js +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/vue_resource.js +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_app.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_auth.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_emails.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_handler.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_helpers.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_menus.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_progress.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_static.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_subscribers.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_util.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/util.py +0 -0
- {wuttaweb-0.20.6/tests → wuttaweb-0.21.0/tests/views}/__init__.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test___init__.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_auth.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_base.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_batch.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_common.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_email.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_essential.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_master.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_progress.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_reports.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_roles.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_settings.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_upgrades.py +0 -0
- {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tox.ini +0 -0
|
@@ -5,6 +5,16 @@ 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.21.0 (2025-02-01)
|
|
9
|
+
|
|
10
|
+
### Feat
|
|
11
|
+
|
|
12
|
+
- overhaul some User/Person form fields etc.
|
|
13
|
+
|
|
14
|
+
### Fix
|
|
15
|
+
|
|
16
|
+
- do not auto-create grid filters for uuid columns
|
|
17
|
+
|
|
8
18
|
## v0.20.6 (2025-01-26)
|
|
9
19
|
|
|
10
20
|
### Fix
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: WuttaWeb
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.21.0
|
|
4
4
|
Summary: Web App for Wutta Framework
|
|
5
5
|
Project-URL: Homepage, https://wuttaproject.org/
|
|
6
6
|
Project-URL: Repository, https://forgejo.wuttaproject.org/wutta/wuttaweb
|
|
@@ -39,7 +39,7 @@ Requires-Dist: pyramid-tm
|
|
|
39
39
|
Requires-Dist: pyramid>=2
|
|
40
40
|
Requires-Dist: waitress
|
|
41
41
|
Requires-Dist: webhelpers2
|
|
42
|
-
Requires-Dist: wuttjamaican[db]>=0.20.
|
|
42
|
+
Requires-Dist: wuttjamaican[db]>=0.20.4
|
|
43
43
|
Requires-Dist: zope-sqlalchemy>=1.5
|
|
44
44
|
Provides-Extra: continuum
|
|
45
45
|
Requires-Dist: wutta-continuum; extra == 'continuum'
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "WuttaWeb"
|
|
9
|
-
version = "0.
|
|
9
|
+
version = "0.21.0"
|
|
10
10
|
description = "Web App for Wutta Framework"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
|
|
@@ -44,7 +44,7 @@ dependencies = [
|
|
|
44
44
|
"pyramid_tm",
|
|
45
45
|
"waitress",
|
|
46
46
|
"WebHelpers2",
|
|
47
|
-
"WuttJamaican[db]>=0.20.
|
|
47
|
+
"WuttJamaican[db]>=0.20.4",
|
|
48
48
|
"zope.sqlalchemy>=1.5",
|
|
49
49
|
]
|
|
50
50
|
|
|
@@ -572,28 +572,6 @@ class RoleRefs(WuttaSet):
|
|
|
572
572
|
return widgets.RoleRefsWidget(self.request, **kwargs)
|
|
573
573
|
|
|
574
574
|
|
|
575
|
-
class UserRefs(WuttaSet):
|
|
576
|
-
"""
|
|
577
|
-
Form schema type for the Role
|
|
578
|
-
:attr:`~wuttjamaican:wuttjamaican.db.model.auth.Role.users`
|
|
579
|
-
association proxy field.
|
|
580
|
-
|
|
581
|
-
This is a subclass of :class:`WuttaSet`. It uses a ``set`` of
|
|
582
|
-
:class:`~wuttjamaican:wuttjamaican.db.model.auth.User` ``uuid``
|
|
583
|
-
values for underlying data format.
|
|
584
|
-
"""
|
|
585
|
-
|
|
586
|
-
def widget_maker(self, **kwargs):
|
|
587
|
-
"""
|
|
588
|
-
Constructs a default widget for the field.
|
|
589
|
-
|
|
590
|
-
:returns: Instance of
|
|
591
|
-
:class:`~wuttaweb.forms.widgets.UserRefsWidget`.
|
|
592
|
-
"""
|
|
593
|
-
kwargs.setdefault('session', Session())
|
|
594
|
-
return widgets.UserRefsWidget(self.request, **kwargs)
|
|
595
|
-
|
|
596
|
-
|
|
597
575
|
class Permissions(WuttaSet):
|
|
598
576
|
"""
|
|
599
577
|
Form schema type for the Role
|
|
@@ -56,7 +56,6 @@ from webhelpers2.html import HTML
|
|
|
56
56
|
from wuttjamaican.conf import parse_list
|
|
57
57
|
|
|
58
58
|
from wuttaweb.db import Session
|
|
59
|
-
from wuttaweb.grids import Grid
|
|
60
59
|
|
|
61
60
|
|
|
62
61
|
class ObjectRefWidget(SelectWidget):
|
|
@@ -414,63 +413,6 @@ class RoleRefsWidget(WuttaCheckboxChoiceWidget):
|
|
|
414
413
|
return super().serialize(field, cstruct, **kw)
|
|
415
414
|
|
|
416
415
|
|
|
417
|
-
class UserRefsWidget(WuttaCheckboxChoiceWidget):
|
|
418
|
-
"""
|
|
419
|
-
Widget for use with Role
|
|
420
|
-
:attr:`~wuttjamaican:wuttjamaican.db.model.auth.Role.users` field.
|
|
421
|
-
This is the default widget for the
|
|
422
|
-
:class:`~wuttaweb.forms.schema.UserRefs` type.
|
|
423
|
-
|
|
424
|
-
This is a subclass of :class:`WuttaCheckboxChoiceWidget`; however
|
|
425
|
-
it only supports readonly mode and does not use a template.
|
|
426
|
-
Rather, it generates and renders a
|
|
427
|
-
:class:`~wuttaweb.grids.base.Grid` showing the users list.
|
|
428
|
-
"""
|
|
429
|
-
|
|
430
|
-
def serialize(self, field, cstruct, **kw):
|
|
431
|
-
""" """
|
|
432
|
-
readonly = kw.get('readonly', self.readonly)
|
|
433
|
-
if not readonly:
|
|
434
|
-
raise NotImplementedError("edit not allowed for this widget")
|
|
435
|
-
|
|
436
|
-
model = self.app.model
|
|
437
|
-
columns = ['username', 'active']
|
|
438
|
-
|
|
439
|
-
# generate data set for users
|
|
440
|
-
users = []
|
|
441
|
-
if cstruct:
|
|
442
|
-
for uuid in cstruct:
|
|
443
|
-
user = self.session.get(model.User, uuid)
|
|
444
|
-
if user:
|
|
445
|
-
users.append(dict([(key, getattr(user, key))
|
|
446
|
-
for key in columns + ['uuid']]))
|
|
447
|
-
|
|
448
|
-
# do not render if no data
|
|
449
|
-
if not users:
|
|
450
|
-
return HTML.tag('span')
|
|
451
|
-
|
|
452
|
-
# grid
|
|
453
|
-
grid = Grid(self.request, key='roles.view.users',
|
|
454
|
-
columns=columns, data=users)
|
|
455
|
-
|
|
456
|
-
# view action
|
|
457
|
-
if self.request.has_perm('users.view'):
|
|
458
|
-
url = lambda user, i: self.request.route_url('users.view', uuid=user['uuid'])
|
|
459
|
-
grid.add_action('view', icon='eye', url=url)
|
|
460
|
-
grid.set_link('person')
|
|
461
|
-
grid.set_link('username')
|
|
462
|
-
|
|
463
|
-
# edit action
|
|
464
|
-
if self.request.has_perm('users.edit'):
|
|
465
|
-
url = lambda user, i: self.request.route_url('users.edit', uuid=user['uuid'])
|
|
466
|
-
grid.add_action('edit', url=url)
|
|
467
|
-
|
|
468
|
-
# render as simple <b-table>
|
|
469
|
-
# nb. must indicate we are a part of this form
|
|
470
|
-
form = getattr(field.parent, 'wutta_form', None)
|
|
471
|
-
return grid.render_table_element(form)
|
|
472
|
-
|
|
473
|
-
|
|
474
416
|
class PermissionsWidget(WuttaCheckboxChoiceWidget):
|
|
475
417
|
"""
|
|
476
418
|
Widget for use with Role
|
|
@@ -41,6 +41,7 @@ from webhelpers2.html import HTML
|
|
|
41
41
|
from wuttaweb.db import Session
|
|
42
42
|
from wuttaweb.util import FieldList, get_model_fields, make_json_safe
|
|
43
43
|
from wuttjamaican.util import UNSPECIFIED
|
|
44
|
+
from wuttjamaican.db.util import UUID
|
|
44
45
|
from wuttaweb.grids.filters import default_sqlalchemy_filters, VerbNotSupported
|
|
45
46
|
|
|
46
47
|
|
|
@@ -1136,14 +1137,15 @@ class Grid:
|
|
|
1136
1137
|
|
|
1137
1138
|
def make_backend_filters(self, filters=None):
|
|
1138
1139
|
"""
|
|
1139
|
-
Make backend filters for
|
|
1140
|
+
Make "automatic" backend filters for the grid.
|
|
1140
1141
|
|
|
1141
1142
|
This is called by the constructor, if :attr:`filterable` is
|
|
1142
1143
|
true.
|
|
1143
1144
|
|
|
1144
|
-
For each column in the
|
|
1145
|
-
|
|
1146
|
-
|
|
1145
|
+
For each "column" in the model class, this will call
|
|
1146
|
+
:meth:`make_filter()` to add an automatic filter. However it
|
|
1147
|
+
first checks the provided ``filters`` and will not override
|
|
1148
|
+
any of those.
|
|
1147
1149
|
|
|
1148
1150
|
.. note::
|
|
1149
1151
|
|
|
@@ -1181,9 +1183,18 @@ class Grid:
|
|
|
1181
1183
|
|
|
1182
1184
|
inspector = sa.inspect(self.model_class)
|
|
1183
1185
|
for prop in inspector.column_attrs:
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1186
|
+
|
|
1187
|
+
# do not overwrite existing filters
|
|
1188
|
+
if prop.key in filters:
|
|
1189
|
+
continue
|
|
1190
|
+
|
|
1191
|
+
# do not create filter for UUID field
|
|
1192
|
+
if (len(prop.columns) == 1
|
|
1193
|
+
and isinstance(prop.columns[0].type, UUID)):
|
|
1194
|
+
continue
|
|
1195
|
+
|
|
1196
|
+
attr = getattr(self.model_class, prop.key)
|
|
1197
|
+
filters[prop.key] = self.make_filter(attr)
|
|
1187
1198
|
|
|
1188
1199
|
return filters
|
|
1189
1200
|
|
|
@@ -28,7 +28,6 @@ import sqlalchemy as sa
|
|
|
28
28
|
|
|
29
29
|
from wuttjamaican.db.model import Person
|
|
30
30
|
from wuttaweb.views import MasterView
|
|
31
|
-
from wuttaweb.forms.schema import UserRefs
|
|
32
31
|
|
|
33
32
|
|
|
34
33
|
class PersonView(MasterView):
|
|
@@ -62,6 +61,14 @@ class PersonView(MasterView):
|
|
|
62
61
|
'full_name': {'active': True},
|
|
63
62
|
}
|
|
64
63
|
|
|
64
|
+
form_fields = [
|
|
65
|
+
'full_name',
|
|
66
|
+
'first_name',
|
|
67
|
+
'middle_name',
|
|
68
|
+
'last_name',
|
|
69
|
+
'users',
|
|
70
|
+
]
|
|
71
|
+
|
|
65
72
|
def configure_grid(self, g):
|
|
66
73
|
""" """
|
|
67
74
|
super().configure_grid(g)
|
|
@@ -80,20 +87,54 @@ class PersonView(MasterView):
|
|
|
80
87
|
super().configure_form(f)
|
|
81
88
|
person = f.model_instance
|
|
82
89
|
|
|
83
|
-
#
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
f.set_required('last_name', False)
|
|
90
|
+
# full_name
|
|
91
|
+
if self.creating or self.editing:
|
|
92
|
+
f.remove('full_name')
|
|
87
93
|
|
|
88
94
|
# users
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
if self.viewing:
|
|
96
|
+
f.set_grid('users', self.make_users_grid(person))
|
|
97
|
+
|
|
98
|
+
def make_users_grid(self, person):
|
|
99
|
+
"""
|
|
100
|
+
Make and return the grid for the Users field.
|
|
101
|
+
|
|
102
|
+
This grid is shown for the Users field when viewing a Person.
|
|
103
|
+
|
|
104
|
+
:returns: Fully configured :class:`~wuttaweb.grids.base.Grid`
|
|
105
|
+
instance.
|
|
106
|
+
"""
|
|
107
|
+
model = self.app.model
|
|
108
|
+
route_prefix = self.get_route_prefix()
|
|
109
|
+
|
|
110
|
+
grid = self.make_grid(key=f'{route_prefix}.view.users',
|
|
111
|
+
model_class=model.User,
|
|
112
|
+
data=person.users,
|
|
113
|
+
columns=[
|
|
114
|
+
'username',
|
|
115
|
+
'active',
|
|
116
|
+
])
|
|
117
|
+
|
|
118
|
+
if self.request.has_perm('users.view'):
|
|
119
|
+
url = lambda user, i: self.request.route_url('users.view', uuid=user.uuid)
|
|
120
|
+
grid.add_action('view', icon='eye', url=url)
|
|
121
|
+
grid.set_link('username')
|
|
122
|
+
|
|
123
|
+
if self.request.has_perm('users.edit'):
|
|
124
|
+
url = lambda user, i: self.request.route_url('users.edit', uuid=user.uuid)
|
|
125
|
+
grid.add_action('edit', url=url)
|
|
126
|
+
|
|
127
|
+
return grid
|
|
128
|
+
|
|
129
|
+
def objectify(self, form):
|
|
130
|
+
""" """
|
|
131
|
+
person = super().objectify(form)
|
|
132
|
+
|
|
133
|
+
# full_name
|
|
134
|
+
person.full_name = self.app.make_full_name(person.first_name,
|
|
135
|
+
person.last_name)
|
|
136
|
+
|
|
137
|
+
return person
|
|
97
138
|
|
|
98
139
|
def autocomplete_query(self, term):
|
|
99
140
|
""" """
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
################################################################################
|
|
3
3
|
#
|
|
4
4
|
# wuttaweb -- Web App for Wutta Framework
|
|
5
|
-
# Copyright © 2024 Lance Edgar
|
|
5
|
+
# Copyright © 2024-2025 Lance Edgar
|
|
6
6
|
#
|
|
7
7
|
# This file is part of Wutta Framework.
|
|
8
8
|
#
|
|
@@ -30,7 +30,6 @@ from wuttjamaican.db.model import User
|
|
|
30
30
|
from wuttaweb.views import MasterView
|
|
31
31
|
from wuttaweb.forms import widgets
|
|
32
32
|
from wuttaweb.forms.schema import PersonRef, RoleRefs
|
|
33
|
-
from wuttaweb.db import Session
|
|
34
33
|
|
|
35
34
|
|
|
36
35
|
class UserView(MasterView):
|
|
@@ -61,6 +60,14 @@ class UserView(MasterView):
|
|
|
61
60
|
}
|
|
62
61
|
sort_defaults = 'username'
|
|
63
62
|
|
|
63
|
+
form_fields = [
|
|
64
|
+
'username',
|
|
65
|
+
'person',
|
|
66
|
+
'active',
|
|
67
|
+
'prevent_edit',
|
|
68
|
+
'roles',
|
|
69
|
+
]
|
|
70
|
+
|
|
64
71
|
def get_query(self, session=None):
|
|
65
72
|
""" """
|
|
66
73
|
query = super().get_query(session=session)
|
|
@@ -109,17 +116,24 @@ class UserView(MasterView):
|
|
|
109
116
|
super().configure_form(f)
|
|
110
117
|
user = f.model_instance
|
|
111
118
|
|
|
112
|
-
# never show these
|
|
113
|
-
f.remove('person_uuid',
|
|
114
|
-
'role_refs')
|
|
115
|
-
|
|
116
|
-
# person
|
|
117
|
-
f.set_node('person', PersonRef(self.request, empty_option=True))
|
|
118
|
-
f.set_required('person', False)
|
|
119
|
-
|
|
120
119
|
# username
|
|
121
120
|
f.set_validator('username', self.unique_username)
|
|
122
121
|
|
|
122
|
+
# person
|
|
123
|
+
if self.creating or self.editing:
|
|
124
|
+
f.fields.insert_after('person', 'first_name')
|
|
125
|
+
f.set_required('first_name', False)
|
|
126
|
+
f.fields.insert_after('first_name', 'last_name')
|
|
127
|
+
f.set_required('last_name', False)
|
|
128
|
+
f.remove('person')
|
|
129
|
+
if self.editing:
|
|
130
|
+
person = user.person
|
|
131
|
+
if person:
|
|
132
|
+
f.set_default('first_name', person.first_name)
|
|
133
|
+
f.set_default('last_name', person.last_name)
|
|
134
|
+
else:
|
|
135
|
+
f.set_node('person', PersonRef(self.request))
|
|
136
|
+
|
|
123
137
|
# password
|
|
124
138
|
# nb. we must avoid 'password' as field name since
|
|
125
139
|
# ColanderAlchemy wants to handle the raw/hashed value
|
|
@@ -140,7 +154,7 @@ class UserView(MasterView):
|
|
|
140
154
|
def unique_username(self, node, value):
|
|
141
155
|
""" """
|
|
142
156
|
model = self.app.model
|
|
143
|
-
session = Session()
|
|
157
|
+
session = self.Session()
|
|
144
158
|
|
|
145
159
|
query = session.query(model.User)\
|
|
146
160
|
.filter(model.User.username == value)
|
|
@@ -152,26 +166,48 @@ class UserView(MasterView):
|
|
|
152
166
|
if query.count():
|
|
153
167
|
node.raise_invalid("Username must be unique")
|
|
154
168
|
|
|
155
|
-
def objectify(self, form
|
|
169
|
+
def objectify(self, form):
|
|
156
170
|
""" """
|
|
171
|
+
model = self.app.model
|
|
172
|
+
auth = self.app.get_auth_handler()
|
|
157
173
|
data = form.validated
|
|
158
174
|
|
|
159
175
|
# normal logic first
|
|
160
176
|
user = super().objectify(form)
|
|
161
177
|
|
|
178
|
+
# maybe update person name
|
|
179
|
+
if 'first_name' in form or 'last_name' in form:
|
|
180
|
+
first_name = data.get('first_name')
|
|
181
|
+
last_name = data.get('last_name')
|
|
182
|
+
if self.creating and (first_name or last_name):
|
|
183
|
+
user.person = auth.make_person(first_name=first_name, last_name=last_name)
|
|
184
|
+
elif self.editing:
|
|
185
|
+
if first_name or last_name:
|
|
186
|
+
if user.person:
|
|
187
|
+
person = user.person
|
|
188
|
+
if 'first_name' in form:
|
|
189
|
+
person.first_name = first_name
|
|
190
|
+
if 'last_name' in form:
|
|
191
|
+
person.last_name = last_name
|
|
192
|
+
person.full_name = self.app.make_full_name(person.first_name,
|
|
193
|
+
person.last_name)
|
|
194
|
+
else:
|
|
195
|
+
user.person = auth.make_person(first_name=first_name, last_name=last_name)
|
|
196
|
+
elif user.person:
|
|
197
|
+
user.person = None
|
|
198
|
+
|
|
162
199
|
# maybe set user password
|
|
163
200
|
if 'set_password' in form and data.get('set_password'):
|
|
164
|
-
auth = self.app.get_auth_handler()
|
|
165
201
|
auth.set_user_password(user, data['set_password'])
|
|
166
202
|
|
|
167
203
|
# update roles for user
|
|
168
204
|
# TODO
|
|
169
205
|
# if self.has_perm('edit_roles'):
|
|
170
|
-
self.update_roles(user, form
|
|
206
|
+
self.update_roles(user, form)
|
|
171
207
|
|
|
172
208
|
return user
|
|
173
209
|
|
|
174
|
-
def update_roles(self, user, form
|
|
210
|
+
def update_roles(self, user, form):
|
|
175
211
|
""" """
|
|
176
212
|
# TODO
|
|
177
213
|
# if not self.has_perm('edit_roles'):
|
|
@@ -181,7 +217,7 @@ class UserView(MasterView):
|
|
|
181
217
|
return
|
|
182
218
|
|
|
183
219
|
model = self.app.model
|
|
184
|
-
session =
|
|
220
|
+
session = self.Session()
|
|
185
221
|
auth = self.app.get_auth_handler()
|
|
186
222
|
|
|
187
223
|
old_roles = set([role.uuid for role in user.roles])
|
|
@@ -377,20 +377,6 @@ class TestUserRef(WebTestCase):
|
|
|
377
377
|
self.assertIn(f'/users/{user.uuid}', url)
|
|
378
378
|
|
|
379
379
|
|
|
380
|
-
class TestUserRefs(DataTestCase):
|
|
381
|
-
|
|
382
|
-
def setUp(self):
|
|
383
|
-
self.setup_db()
|
|
384
|
-
self.request = testing.DummyRequest(wutta_config=self.config)
|
|
385
|
-
|
|
386
|
-
def test_widget_maker(self):
|
|
387
|
-
model = self.app.model
|
|
388
|
-
with patch.object(mod, 'Session', return_value=self.session):
|
|
389
|
-
typ = mod.UserRefs(self.request)
|
|
390
|
-
widget = typ.widget_maker()
|
|
391
|
-
self.assertIsInstance(widget, widgets.UserRefsWidget)
|
|
392
|
-
|
|
393
|
-
|
|
394
380
|
class TestRoleRefs(DataTestCase):
|
|
395
381
|
|
|
396
382
|
def setUp(self):
|
|
@@ -11,7 +11,7 @@ from pyramid import testing
|
|
|
11
11
|
from wuttaweb import grids
|
|
12
12
|
from wuttaweb.forms import widgets as mod
|
|
13
13
|
from wuttaweb.forms import schema
|
|
14
|
-
from wuttaweb.forms.schema import (FileDownload, PersonRef, RoleRefs,
|
|
14
|
+
from wuttaweb.forms.schema import (FileDownload, PersonRef, RoleRefs, Permissions,
|
|
15
15
|
WuttaDateTime, EmailRecipients)
|
|
16
16
|
from wuttaweb.testing import WebTestCase
|
|
17
17
|
|
|
@@ -300,51 +300,6 @@ class TestRoleRefsWidget(WebTestCase):
|
|
|
300
300
|
self.assertIn(str(blokes.uuid.hex), html)
|
|
301
301
|
|
|
302
302
|
|
|
303
|
-
class TestUserRefsWidget(WebTestCase):
|
|
304
|
-
|
|
305
|
-
def make_field(self, node, **kwargs):
|
|
306
|
-
# TODO: not sure why default renderer is in use even though
|
|
307
|
-
# pyramid_deform was included in setup? but this works..
|
|
308
|
-
kwargs.setdefault('renderer', deform.Form.default_renderer)
|
|
309
|
-
return deform.Field(node, **kwargs)
|
|
310
|
-
|
|
311
|
-
def test_serialize(self):
|
|
312
|
-
model = self.app.model
|
|
313
|
-
|
|
314
|
-
# nb. we let the field construct the widget via our type
|
|
315
|
-
# node = colander.SchemaNode(UserRefs(self.request, session=self.session))
|
|
316
|
-
with patch.object(schema, 'Session', return_value=self.session):
|
|
317
|
-
node = colander.SchemaNode(UserRefs(self.request))
|
|
318
|
-
field = self.make_field(node)
|
|
319
|
-
widget = field.widget
|
|
320
|
-
|
|
321
|
-
# readonly is required
|
|
322
|
-
self.assertRaises(NotImplementedError, widget.serialize, field, set())
|
|
323
|
-
self.assertRaises(NotImplementedError, widget.serialize, field, set(), readonly=False)
|
|
324
|
-
|
|
325
|
-
# empty
|
|
326
|
-
html = widget.serialize(field, set(), readonly=True)
|
|
327
|
-
self.assertEqual(html, '<span></span>')
|
|
328
|
-
|
|
329
|
-
# with data, no actions
|
|
330
|
-
user = model.User(username='barney')
|
|
331
|
-
self.session.add(user)
|
|
332
|
-
self.session.commit()
|
|
333
|
-
html = widget.serialize(field, {user.uuid}, readonly=True)
|
|
334
|
-
self.assertIn('<b-table ', html)
|
|
335
|
-
self.assertNotIn('Actions', html)
|
|
336
|
-
self.assertNotIn('View', html)
|
|
337
|
-
self.assertNotIn('Edit', html)
|
|
338
|
-
|
|
339
|
-
# with view/edit actions
|
|
340
|
-
with patch.object(self.request, 'is_root', new=True):
|
|
341
|
-
html = widget.serialize(field, {user.uuid}, readonly=True)
|
|
342
|
-
self.assertIn('<b-table ', html)
|
|
343
|
-
self.assertIn('Actions', html)
|
|
344
|
-
self.assertIn('View', html)
|
|
345
|
-
self.assertIn('Edit', html)
|
|
346
|
-
|
|
347
|
-
|
|
348
303
|
class TestPermissionsWidget(WebTestCase):
|
|
349
304
|
|
|
350
305
|
def make_field(self, node, **kwargs):
|
|
@@ -1021,6 +1021,9 @@ class TestGrid(WebTestCase):
|
|
|
1021
1021
|
self.assertIn('active', filters)
|
|
1022
1022
|
# nb. relationship not included by default
|
|
1023
1023
|
self.assertNotIn('person', filters)
|
|
1024
|
+
# nb. uuid fields not included by default
|
|
1025
|
+
self.assertNotIn('uuid', filters)
|
|
1026
|
+
self.assertNotIn('person_uuid', filters)
|
|
1024
1027
|
|
|
1025
1028
|
def test_make_filter(self):
|
|
1026
1029
|
model = self.app.model
|
|
@@ -8,6 +8,8 @@ from pyramid.httpexceptions import HTTPNotFound
|
|
|
8
8
|
|
|
9
9
|
from wuttaweb.views import people
|
|
10
10
|
from wuttaweb.testing import WebTestCase
|
|
11
|
+
from wuttaweb.forms.widgets import GridWidget
|
|
12
|
+
from wuttaweb.grids import Grid
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
class TestPersonView(WebTestCase):
|
|
@@ -34,27 +36,59 @@ class TestPersonView(WebTestCase):
|
|
|
34
36
|
def test_configure_form(self):
|
|
35
37
|
model = self.app.model
|
|
36
38
|
view = self.make_view()
|
|
37
|
-
form = view.make_form(model_class=model.Person)
|
|
38
39
|
|
|
39
|
-
#
|
|
40
|
+
# full_name
|
|
41
|
+
form = view.make_form(model_class=model.Person)
|
|
42
|
+
self.assertIn('full_name', form)
|
|
40
43
|
with patch.object(view, 'creating', new=True):
|
|
41
|
-
form.set_fields(form.get_model_fields())
|
|
42
|
-
self.assertEqual(form.required_fields, {})
|
|
43
44
|
view.configure_form(form)
|
|
44
|
-
self.
|
|
45
|
-
self.assertFalse(form.required_fields['middle_name'])
|
|
46
|
-
|
|
47
|
-
person = model.Person(full_name="Barney Rubble")
|
|
48
|
-
user = model.User(username='barney', person=person)
|
|
49
|
-
self.session.add(user)
|
|
50
|
-
self.session.commit()
|
|
45
|
+
self.assertNotIn('full_name', form)
|
|
51
46
|
|
|
52
|
-
# users
|
|
47
|
+
# users
|
|
48
|
+
person = model.Person()
|
|
49
|
+
form = view.make_form(model_instance=person)
|
|
50
|
+
self.assertNotIn('users', form.widgets)
|
|
53
51
|
with patch.object(view, 'viewing', new=True):
|
|
54
|
-
form = view.make_form(model_instance=person)
|
|
55
|
-
self.assertEqual(form.defaults, {})
|
|
56
52
|
view.configure_form(form)
|
|
57
|
-
self.assertIn('
|
|
53
|
+
self.assertIn('users', form.widgets)
|
|
54
|
+
self.assertIsInstance(form.widgets['users'], GridWidget)
|
|
55
|
+
|
|
56
|
+
def test_make_users_grid(self):
|
|
57
|
+
model = self.app.model
|
|
58
|
+
view = self.make_view()
|
|
59
|
+
person = model.Person(full_name="John Doe")
|
|
60
|
+
|
|
61
|
+
# basic
|
|
62
|
+
grid = view.make_users_grid(person)
|
|
63
|
+
self.assertIsInstance(grid, Grid)
|
|
64
|
+
self.assertFalse(grid.linked_columns)
|
|
65
|
+
self.assertFalse(grid.actions)
|
|
66
|
+
|
|
67
|
+
# view + edit actions
|
|
68
|
+
with patch.object(self.request, 'is_root', new=True):
|
|
69
|
+
grid = view.make_users_grid(person)
|
|
70
|
+
self.assertIsInstance(grid, Grid)
|
|
71
|
+
self.assertIn('username', grid.linked_columns)
|
|
72
|
+
self.assertEqual(len(grid.actions), 2)
|
|
73
|
+
self.assertEqual(grid.actions[0].key, 'view')
|
|
74
|
+
self.assertEqual(grid.actions[1].key, 'edit')
|
|
75
|
+
|
|
76
|
+
def test_objectify(self):
|
|
77
|
+
model = self.app.model
|
|
78
|
+
view = self.make_view()
|
|
79
|
+
|
|
80
|
+
# creating
|
|
81
|
+
form = view.make_model_form()
|
|
82
|
+
form.validated = {'first_name': 'Barney', 'last_name': 'Rubble'}
|
|
83
|
+
person = view.objectify(form)
|
|
84
|
+
self.assertEqual(person.full_name, 'Barney Rubble')
|
|
85
|
+
|
|
86
|
+
# editing
|
|
87
|
+
form = view.make_model_form(model_instance=person)
|
|
88
|
+
form.validated = {'first_name': 'Betty', 'last_name': 'Rubble'}
|
|
89
|
+
person2 = view.objectify(form)
|
|
90
|
+
self.assertEqual(person2.full_name, 'Betty Rubble')
|
|
91
|
+
self.assertIs(person2, person)
|
|
58
92
|
|
|
59
93
|
def test_autocomplete_query(self):
|
|
60
94
|
model = self.app.model
|