WuttaWeb 0.20.5__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.5 → wuttaweb-0.21.0}/CHANGELOG.md +22 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/PKG-INFO +2 -2
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/pyproject.toml +2 -2
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/cli/webapp.py +1 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/forms/schema.py +0 -22
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/forms/widgets.py +0 -58
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/grids/base.py +34 -7
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/menus.py +3 -1
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/base.mako +10 -6
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/views/common.py +13 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/views/people.py +54 -13
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/views/roles.py +35 -4
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/views/users.py +52 -16
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/cli/test_webapp.py +4 -2
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/forms/test_schema.py +0 -14
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/forms/test_widgets.py +1 -46
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/grids/test_base.py +21 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/views/test_people.py +49 -15
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/views/test_roles.py +22 -0
- wuttaweb-0.21.0/tests/views/test_users.py +319 -0
- wuttaweb-0.20.5/tests/views/test_users.py +0 -219
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/.gitignore +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/COPYING.txt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/README.md +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/Makefile +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/_static/.keepme +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.app.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.auth.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.cli.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.cli.webapp.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.conf.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.db.continuum.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.db.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.db.sess.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.emails.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.forms.base.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.forms.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.forms.schema.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.forms.widgets.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.grids.base.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.grids.filters.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.grids.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.handler.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.helpers.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.menus.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.progress.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.static.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.subscribers.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.util.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.auth.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.base.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.batch.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.common.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.email.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.essential.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.master.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.people.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.progress.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.reports.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.roles.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.settings.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.upgrades.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.users.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/conf.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/glossary.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/index.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/make.bat +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/narr/cli/builtin.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/narr/cli/index.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/narr/templates/base.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/narr/templates/index.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/narr/templates/lookup.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/docs/narr/templates/overview.rst +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/__init__.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/_version.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/app.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/auth.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/cli/__init__.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/conf.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/db/__init__.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/db/continuum.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/db/sess.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/email-templates/feedback.html.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/email-templates/feedback.txt.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/emails.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/forms/__init__.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/forms/base.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/grids/__init__.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/grids/filters.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/handler.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/helpers.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/progress.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/static/__init__.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/static/img/favicon.ico +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/static/img/logo.png +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/static/img/testing.png +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/subscribers.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/appinfo/index.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/auth/change_password.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/auth/login.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/base_meta.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/batch/view.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/configure.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/dateinput.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/datetimeinput.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/password.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/permissions.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/email_recips.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/select.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/textarea.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/textinput.pt +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/email/settings/view.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/forbidden.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/form.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/grids/table_element.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/grids/vue_template.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/home.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/configure.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/create.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/delete.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/edit.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/form.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/index.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/view.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/notfound.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/page.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/people/view_profile.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/progress.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/reports/view.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/setup.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/upgrade.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/upgrades/view.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/templates/wutta-components.mako +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/testing.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/util.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/views/__init__.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/views/auth.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/views/base.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/views/batch.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/views/email.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/views/essential.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/views/master.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/views/progress.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/views/reports.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/views/settings.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/src/wuttaweb/views/upgrades.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tasks.py +0 -0
- {wuttaweb-0.20.5/tests/views → wuttaweb-0.21.0/tests}/__init__.py +0 -0
- {wuttaweb-0.20.5/tests/grids → wuttaweb-0.21.0/tests/cli}/__init__.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/db/__init__.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/db/test_continuum.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/forms/test_base.py +0 -0
- {wuttaweb-0.20.5/tests/cli → wuttaweb-0.21.0/tests/grids}/__init__.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/grids/test_filters.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/libcache/bb_oruga.js +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/libcache/bb_oruga_bulma.css +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/libcache/bb_oruga_bulma.js +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/libcache/bb_vue.js +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/libcache/bb_vue_fontawesome.js +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/libcache/buefy.css +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/libcache/buefy.js +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/libcache/fontawesome.js +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/libcache/vue.js +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/libcache/vue_resource.js +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/test_app.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/test_auth.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/test_emails.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/test_handler.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/test_helpers.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/test_menus.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/test_progress.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/test_static.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/test_subscribers.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/test_util.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/util.py +0 -0
- {wuttaweb-0.20.5/tests → wuttaweb-0.21.0/tests/views}/__init__.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/views/test___init__.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/views/test_auth.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/views/test_base.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/views/test_batch.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/views/test_common.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/views/test_email.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/views/test_essential.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/views/test_master.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/views/test_progress.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/views/test_reports.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/views/test_settings.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tests/views/test_upgrades.py +0 -0
- {wuttaweb-0.20.5 → wuttaweb-0.21.0}/tox.ini +0 -0
|
@@ -5,6 +5,28 @@ 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
|
+
|
|
18
|
+
## v0.20.6 (2025-01-26)
|
|
19
|
+
|
|
20
|
+
### Fix
|
|
21
|
+
|
|
22
|
+
- add `setup_enhance_admin_user()` method for initial setup
|
|
23
|
+
- add `render_percent()` method for Grid
|
|
24
|
+
- allow override for Admin menu title
|
|
25
|
+
- add `index_title_controls()` def block for base template
|
|
26
|
+
- add `make_users_grid()` method for RoleView
|
|
27
|
+
- fallback to empty string for uvicorn `root_path`
|
|
28
|
+
- add `root_path` config setting for running webapp via uvicorn
|
|
29
|
+
|
|
8
30
|
## v0.20.5 (2025-01-23)
|
|
9
31
|
|
|
10
32
|
### 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
|
|
|
@@ -74,6 +74,7 @@ def webapp(
|
|
|
74
74
|
'reload_dirs': config.get_list(f'{config.appname}.web.app.reload_dirs'),
|
|
75
75
|
'factory': config.get_bool(f'{config.appname}.web.app.factory', default=False),
|
|
76
76
|
'interface': config.get(f'{config.appname}.web.app.interface', default='auto'),
|
|
77
|
+
'root_path': config.get(f'{config.appname}.web.app.root_path', default=''),
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
# also must inject our config files to env, since there is no
|
|
@@ -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
|
|
|
@@ -612,6 +613,7 @@ class Grid:
|
|
|
612
613
|
* ``'date'`` -> :meth:`render_date()`
|
|
613
614
|
* ``'datetime'`` -> :meth:`render_datetime()`
|
|
614
615
|
* ``'quantity'`` -> :meth:`render_quantity()`
|
|
616
|
+
* ``'percent'`` -> :meth:`render_percent()`
|
|
615
617
|
|
|
616
618
|
Renderer overrides are tracked via :attr:`renderers`.
|
|
617
619
|
"""
|
|
@@ -622,6 +624,7 @@ class Grid:
|
|
|
622
624
|
'date': self.render_date,
|
|
623
625
|
'datetime': self.render_datetime,
|
|
624
626
|
'quantity': self.render_quantity,
|
|
627
|
+
'percent': self.render_percent,
|
|
625
628
|
}
|
|
626
629
|
|
|
627
630
|
if renderer in builtins:
|
|
@@ -1134,14 +1137,15 @@ class Grid:
|
|
|
1134
1137
|
|
|
1135
1138
|
def make_backend_filters(self, filters=None):
|
|
1136
1139
|
"""
|
|
1137
|
-
Make backend filters for
|
|
1140
|
+
Make "automatic" backend filters for the grid.
|
|
1138
1141
|
|
|
1139
1142
|
This is called by the constructor, if :attr:`filterable` is
|
|
1140
1143
|
true.
|
|
1141
1144
|
|
|
1142
|
-
For each column in the
|
|
1143
|
-
|
|
1144
|
-
|
|
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.
|
|
1145
1149
|
|
|
1146
1150
|
.. note::
|
|
1147
1151
|
|
|
@@ -1179,9 +1183,18 @@ class Grid:
|
|
|
1179
1183
|
|
|
1180
1184
|
inspector = sa.inspect(self.model_class)
|
|
1181
1185
|
for prop in inspector.column_attrs:
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
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)
|
|
1185
1198
|
|
|
1186
1199
|
return filters
|
|
1187
1200
|
|
|
@@ -1884,6 +1897,20 @@ class Grid:
|
|
|
1884
1897
|
dt = getattr(obj, key)
|
|
1885
1898
|
return self.app.render_datetime(dt)
|
|
1886
1899
|
|
|
1900
|
+
def render_percent(self, obj, key, value, **kwargs):
|
|
1901
|
+
"""
|
|
1902
|
+
Column renderer for percentage values.
|
|
1903
|
+
|
|
1904
|
+
This calls
|
|
1905
|
+
:meth:`~wuttjamaican:wuttjamaican.app.AppHandler.render_percent()`
|
|
1906
|
+
for the return value.
|
|
1907
|
+
|
|
1908
|
+
This is not used automatically but you can use it explicitly::
|
|
1909
|
+
|
|
1910
|
+
grid.set_renderer('foo', 'percent')
|
|
1911
|
+
"""
|
|
1912
|
+
return self.app.render_percent(value, **kwargs)
|
|
1913
|
+
|
|
1887
1914
|
def render_quantity(self, obj, key, value):
|
|
1888
1915
|
"""
|
|
1889
1916
|
Column renderer for quantity values.
|
|
@@ -143,6 +143,8 @@ class MenuHandler(GenericHandler):
|
|
|
143
143
|
which will ultimately be one element of the final list of
|
|
144
144
|
dicts as described in :class:`MenuHandler`.
|
|
145
145
|
|
|
146
|
+
:param title: Override the menu title; default is "Admin".
|
|
147
|
+
|
|
146
148
|
:param include_people: You can pass this flag to indicate the
|
|
147
149
|
admin menu should contain an entry for the "People" view.
|
|
148
150
|
"""
|
|
@@ -198,7 +200,7 @@ class MenuHandler(GenericHandler):
|
|
|
198
200
|
])
|
|
199
201
|
|
|
200
202
|
return {
|
|
201
|
-
'title': "Admin",
|
|
203
|
+
'title': kwargs.get('title', "Admin"),
|
|
202
204
|
'type': 'menu',
|
|
203
205
|
'items': items,
|
|
204
206
|
}
|
|
@@ -248,12 +248,7 @@
|
|
|
248
248
|
% else:
|
|
249
249
|
<h1 class="title">${index_title}</h1>
|
|
250
250
|
% endif
|
|
251
|
-
|
|
252
|
-
<wutta-button once type="is-primary"
|
|
253
|
-
tag="a" href="${url(f'{route_prefix}.create')}"
|
|
254
|
-
icon-left="plus"
|
|
255
|
-
label="Create New" />
|
|
256
|
-
% endif
|
|
251
|
+
${self.index_title_controls()}
|
|
257
252
|
% endif
|
|
258
253
|
</div>
|
|
259
254
|
</div>
|
|
@@ -349,6 +344,15 @@
|
|
|
349
344
|
</footer>
|
|
350
345
|
</%def>
|
|
351
346
|
|
|
347
|
+
<%def name="index_title_controls()">
|
|
348
|
+
% if master and master.creatable and not master.creating and master.has_perm('create'):
|
|
349
|
+
<wutta-button once type="is-primary"
|
|
350
|
+
tag="a" href="${url(f'{route_prefix}.create')}"
|
|
351
|
+
icon-left="plus"
|
|
352
|
+
label="Create New" />
|
|
353
|
+
% endif
|
|
354
|
+
</%def>
|
|
355
|
+
|
|
352
356
|
<%def name="render_vue_template_whole_page()">
|
|
353
357
|
<script type="text/x-template" id="whole-page-template">
|
|
354
358
|
|
|
@@ -245,6 +245,8 @@ class CommonView(View):
|
|
|
245
245
|
session.add(person)
|
|
246
246
|
user.person = person
|
|
247
247
|
|
|
248
|
+
self.setup_enhance_admin_user(user)
|
|
249
|
+
|
|
248
250
|
# send user to /login
|
|
249
251
|
self.request.session.flash("Account created! Please login below.")
|
|
250
252
|
return self.redirect(self.request.route_url('login'))
|
|
@@ -254,6 +256,17 @@ class CommonView(View):
|
|
|
254
256
|
'form': form,
|
|
255
257
|
}
|
|
256
258
|
|
|
259
|
+
def setup_enhance_admin_user(self, user):
|
|
260
|
+
"""
|
|
261
|
+
Further "enhance" the initial admin user when it is first created.
|
|
262
|
+
|
|
263
|
+
This does nothing by default; subclass can override if needed.
|
|
264
|
+
|
|
265
|
+
:param user: New admin
|
|
266
|
+
:class:`~wuttjamaican:wuttjamaican.db.model.auth.User`
|
|
267
|
+
which was just created as part of initial setup.
|
|
268
|
+
"""
|
|
269
|
+
|
|
257
270
|
@classmethod
|
|
258
271
|
def defaults(cls, config):
|
|
259
272
|
cls._defaults(config)
|
|
@@ -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
|
""" """
|
|
@@ -28,7 +28,7 @@ from wuttjamaican.db.model import Role, Permission
|
|
|
28
28
|
from wuttaweb.views import MasterView
|
|
29
29
|
from wuttaweb.db import Session
|
|
30
30
|
from wuttaweb.forms import widgets
|
|
31
|
-
from wuttaweb.forms.schema import
|
|
31
|
+
from wuttaweb.forms.schema import Permissions, RoleRef
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
class RoleView(MasterView):
|
|
@@ -123,9 +123,7 @@ class RoleView(MasterView):
|
|
|
123
123
|
# users
|
|
124
124
|
if not (self.creating or self.editing):
|
|
125
125
|
f.append('users')
|
|
126
|
-
f.
|
|
127
|
-
f.set_node('users', UserRefs(self.request))
|
|
128
|
-
f.set_default('users', [u.uuid for u in role.users])
|
|
126
|
+
f.set_grid('users', self.make_users_grid(role))
|
|
129
127
|
|
|
130
128
|
# permissions
|
|
131
129
|
f.append('permissions')
|
|
@@ -134,6 +132,39 @@ class RoleView(MasterView):
|
|
|
134
132
|
if not self.creating:
|
|
135
133
|
f.set_default('permissions', list(role.permissions))
|
|
136
134
|
|
|
135
|
+
def make_users_grid(self, role):
|
|
136
|
+
"""
|
|
137
|
+
Make and return the grid for the Users field.
|
|
138
|
+
|
|
139
|
+
This grid is shown for the Users field when viewing a Role.
|
|
140
|
+
|
|
141
|
+
:returns: Fully configured :class:`~wuttaweb.grids.base.Grid`
|
|
142
|
+
instance.
|
|
143
|
+
"""
|
|
144
|
+
model = self.app.model
|
|
145
|
+
route_prefix = self.get_route_prefix()
|
|
146
|
+
|
|
147
|
+
grid = self.make_grid(key=f'{route_prefix}.view.users',
|
|
148
|
+
model_class=model.User,
|
|
149
|
+
data=role.users,
|
|
150
|
+
columns=[
|
|
151
|
+
'username',
|
|
152
|
+
'person',
|
|
153
|
+
'active',
|
|
154
|
+
])
|
|
155
|
+
|
|
156
|
+
if self.request.has_perm('users.view'):
|
|
157
|
+
url = lambda user, i: self.request.route_url('users.view', uuid=user.uuid)
|
|
158
|
+
grid.add_action('view', icon='eye', url=url)
|
|
159
|
+
grid.set_link('person')
|
|
160
|
+
grid.set_link('username')
|
|
161
|
+
|
|
162
|
+
if self.request.has_perm('users.edit'):
|
|
163
|
+
url = lambda user, i: self.request.route_url('users.edit', uuid=user.uuid)
|
|
164
|
+
grid.add_action('edit', url=url)
|
|
165
|
+
|
|
166
|
+
return grid
|
|
167
|
+
|
|
137
168
|
def unique_name(self, node, value):
|
|
138
169
|
""" """
|
|
139
170
|
model = self.app.model
|
|
@@ -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])
|
|
@@ -88,7 +88,8 @@ app.spec = wuttaweb.app:make_wsgi_app
|
|
|
88
88
|
reload=False,
|
|
89
89
|
reload_dirs=None,
|
|
90
90
|
factory=False,
|
|
91
|
-
interface='auto'
|
|
91
|
+
interface='auto',
|
|
92
|
+
root_path='')
|
|
92
93
|
|
|
93
94
|
# with reload
|
|
94
95
|
uvicorn.run.reset_mock()
|
|
@@ -101,4 +102,5 @@ app.spec = wuttaweb.app:make_wsgi_app
|
|
|
101
102
|
reload=True,
|
|
102
103
|
reload_dirs=None,
|
|
103
104
|
factory=False,
|
|
104
|
-
interface='auto'
|
|
105
|
+
interface='auto',
|
|
106
|
+
root_path='')
|
|
@@ -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):
|