feat: overhaul some User/Person form fields etc.
hoping this is more intuitive to use..
This commit is contained in:
parent
70ed2dc78c
commit
5b2d1dad53
8 changed files with 289 additions and 217 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
""" """
|
||||
|
|
|
@ -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])
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue