3
0
Fork 0

feat: overhaul some User/Person form fields etc.

hoping this is more intuitive to use..
This commit is contained in:
Lance Edgar 2025-01-27 17:07:42 -06:00
parent 70ed2dc78c
commit 5b2d1dad53
8 changed files with 289 additions and 217 deletions

View file

@ -572,28 +572,6 @@ class RoleRefs(WuttaSet):
return widgets.RoleRefsWidget(self.request, **kwargs)
class UserRefs(WuttaSet):
"""
Form schema type for the Role
:attr:`~wuttjamaican:wuttjamaican.db.model.auth.Role.users`
association proxy field.
This is a subclass of :class:`WuttaSet`. It uses a ``set`` of
:class:`~wuttjamaican:wuttjamaican.db.model.auth.User` ``uuid``
values for underlying data format.
"""
def widget_maker(self, **kwargs):
"""
Constructs a default widget for the field.
:returns: Instance of
:class:`~wuttaweb.forms.widgets.UserRefsWidget`.
"""
kwargs.setdefault('session', Session())
return widgets.UserRefsWidget(self.request, **kwargs)
class Permissions(WuttaSet):
"""
Form schema type for the Role

View file

@ -56,7 +56,6 @@ from webhelpers2.html import HTML
from wuttjamaican.conf import parse_list
from wuttaweb.db import Session
from wuttaweb.grids import Grid
class ObjectRefWidget(SelectWidget):
@ -414,63 +413,6 @@ class RoleRefsWidget(WuttaCheckboxChoiceWidget):
return super().serialize(field, cstruct, **kw)
class UserRefsWidget(WuttaCheckboxChoiceWidget):
"""
Widget for use with Role
:attr:`~wuttjamaican:wuttjamaican.db.model.auth.Role.users` field.
This is the default widget for the
:class:`~wuttaweb.forms.schema.UserRefs` type.
This is a subclass of :class:`WuttaCheckboxChoiceWidget`; however
it only supports readonly mode and does not use a template.
Rather, it generates and renders a
:class:`~wuttaweb.grids.base.Grid` showing the users list.
"""
def serialize(self, field, cstruct, **kw):
""" """
readonly = kw.get('readonly', self.readonly)
if not readonly:
raise NotImplementedError("edit not allowed for this widget")
model = self.app.model
columns = ['username', 'active']
# generate data set for users
users = []
if cstruct:
for uuid in cstruct:
user = self.session.get(model.User, uuid)
if user:
users.append(dict([(key, getattr(user, key))
for key in columns + ['uuid']]))
# do not render if no data
if not users:
return HTML.tag('span')
# grid
grid = Grid(self.request, key='roles.view.users',
columns=columns, data=users)
# view action
if self.request.has_perm('users.view'):
url = lambda user, i: self.request.route_url('users.view', uuid=user['uuid'])
grid.add_action('view', icon='eye', url=url)
grid.set_link('person')
grid.set_link('username')
# edit action
if self.request.has_perm('users.edit'):
url = lambda user, i: self.request.route_url('users.edit', uuid=user['uuid'])
grid.add_action('edit', url=url)
# render as simple <b-table>
# nb. must indicate we are a part of this form
form = getattr(field.parent, 'wutta_form', None)
return grid.render_table_element(form)
class PermissionsWidget(WuttaCheckboxChoiceWidget):
"""
Widget for use with Role

View file

@ -28,7 +28,6 @@ import sqlalchemy as sa
from wuttjamaican.db.model import Person
from wuttaweb.views import MasterView
from wuttaweb.forms.schema import UserRefs
class PersonView(MasterView):
@ -62,6 +61,14 @@ class PersonView(MasterView):
'full_name': {'active': True},
}
form_fields = [
'full_name',
'first_name',
'middle_name',
'last_name',
'users',
]
def configure_grid(self, g):
""" """
super().configure_grid(g)
@ -80,20 +87,54 @@ class PersonView(MasterView):
super().configure_form(f)
person = f.model_instance
# TODO: master should handle these? (nullable column)
f.set_required('first_name', False)
f.set_required('middle_name', False)
f.set_required('last_name', False)
# full_name
if self.creating or self.editing:
f.remove('full_name')
# users
# nb. colanderalchemy wants to do some magic for the true
# 'users' relationship, so we use a different field name
f.remove('users')
if not (self.creating or self.editing):
f.append('_users')
f.set_readonly('_users')
f.set_node('_users', UserRefs(self.request))
f.set_default('_users', [u.uuid for u in person.users])
if self.viewing:
f.set_grid('users', self.make_users_grid(person))
def make_users_grid(self, person):
"""
Make and return the grid for the Users field.
This grid is shown for the Users field when viewing a Person.
:returns: Fully configured :class:`~wuttaweb.grids.base.Grid`
instance.
"""
model = self.app.model
route_prefix = self.get_route_prefix()
grid = self.make_grid(key=f'{route_prefix}.view.users',
model_class=model.User,
data=person.users,
columns=[
'username',
'active',
])
if self.request.has_perm('users.view'):
url = lambda user, i: self.request.route_url('users.view', uuid=user.uuid)
grid.add_action('view', icon='eye', url=url)
grid.set_link('username')
if self.request.has_perm('users.edit'):
url = lambda user, i: self.request.route_url('users.edit', uuid=user.uuid)
grid.add_action('edit', url=url)
return grid
def objectify(self, form):
""" """
person = super().objectify(form)
# full_name
person.full_name = self.app.make_full_name(person.first_name,
person.last_name)
return person
def autocomplete_query(self, term):
""" """

View file

@ -2,7 +2,7 @@
################################################################################
#
# wuttaweb -- Web App for Wutta Framework
# Copyright © 2024 Lance Edgar
# Copyright © 2024-2025 Lance Edgar
#
# This file is part of Wutta Framework.
#
@ -30,7 +30,6 @@ from wuttjamaican.db.model import User
from wuttaweb.views import MasterView
from wuttaweb.forms import widgets
from wuttaweb.forms.schema import PersonRef, RoleRefs
from wuttaweb.db import Session
class UserView(MasterView):
@ -61,6 +60,14 @@ class UserView(MasterView):
}
sort_defaults = 'username'
form_fields = [
'username',
'person',
'active',
'prevent_edit',
'roles',
]
def get_query(self, session=None):
""" """
query = super().get_query(session=session)
@ -109,17 +116,24 @@ class UserView(MasterView):
super().configure_form(f)
user = f.model_instance
# never show these
f.remove('person_uuid',
'role_refs')
# person
f.set_node('person', PersonRef(self.request, empty_option=True))
f.set_required('person', False)
# username
f.set_validator('username', self.unique_username)
# person
if self.creating or self.editing:
f.fields.insert_after('person', 'first_name')
f.set_required('first_name', False)
f.fields.insert_after('first_name', 'last_name')
f.set_required('last_name', False)
f.remove('person')
if self.editing:
person = user.person
if person:
f.set_default('first_name', person.first_name)
f.set_default('last_name', person.last_name)
else:
f.set_node('person', PersonRef(self.request))
# password
# nb. we must avoid 'password' as field name since
# ColanderAlchemy wants to handle the raw/hashed value
@ -140,7 +154,7 @@ class UserView(MasterView):
def unique_username(self, node, value):
""" """
model = self.app.model
session = Session()
session = self.Session()
query = session.query(model.User)\
.filter(model.User.username == value)
@ -152,26 +166,48 @@ class UserView(MasterView):
if query.count():
node.raise_invalid("Username must be unique")
def objectify(self, form, session=None):
def objectify(self, form):
""" """
model = self.app.model
auth = self.app.get_auth_handler()
data = form.validated
# normal logic first
user = super().objectify(form)
# maybe update person name
if 'first_name' in form or 'last_name' in form:
first_name = data.get('first_name')
last_name = data.get('last_name')
if self.creating and (first_name or last_name):
user.person = auth.make_person(first_name=first_name, last_name=last_name)
elif self.editing:
if first_name or last_name:
if user.person:
person = user.person
if 'first_name' in form:
person.first_name = first_name
if 'last_name' in form:
person.last_name = last_name
person.full_name = self.app.make_full_name(person.first_name,
person.last_name)
else:
user.person = auth.make_person(first_name=first_name, last_name=last_name)
elif user.person:
user.person = None
# maybe set user password
if 'set_password' in form and data.get('set_password'):
auth = self.app.get_auth_handler()
auth.set_user_password(user, data['set_password'])
# update roles for user
# TODO
# if self.has_perm('edit_roles'):
self.update_roles(user, form, session=session)
self.update_roles(user, form)
return user
def update_roles(self, user, form, session=None):
def update_roles(self, user, form):
""" """
# TODO
# if not self.has_perm('edit_roles'):
@ -181,7 +217,7 @@ class UserView(MasterView):
return
model = self.app.model
session = session or Session()
session = self.Session()
auth = self.app.get_auth_handler()
old_roles = set([role.uuid for role in user.roles])