1
0
Fork 0

feat: expose User password for editing in master views

This commit is contained in:
Lance Edgar 2024-08-14 15:55:10 -05:00
parent 230e2fd1ab
commit 330ee324ba
6 changed files with 56 additions and 7 deletions

View file

@ -674,6 +674,13 @@ class Form:
schema = SQLAlchemySchemaNode(self.model_class, schema = SQLAlchemySchemaNode(self.model_class,
includes=includes) includes=includes)
# fill in the blanks if anything got missed
for key in fields:
if key not in schema:
log.warning("key '%s' not in schema", key)
node = colander.SchemaNode(colander.String(), name=key)
schema.add(node)
else: else:
# make basic schema # make basic schema

View file

@ -31,12 +31,15 @@ in the namespace:
* :class:`deform:deform.widget.Widget` (base class) * :class:`deform:deform.widget.Widget` (base class)
* :class:`deform:deform.widget.TextInputWidget` * :class:`deform:deform.widget.TextInputWidget`
* :class:`deform:deform.widget.TextAreaWidget` * :class:`deform:deform.widget.TextAreaWidget`
* :class:`deform:deform.widget.PasswordWidget`
* :class:`deform:deform.widget.CheckedPasswordWidget`
* :class:`deform:deform.widget.SelectWidget` * :class:`deform:deform.widget.SelectWidget`
* :class:`deform:deform.widget.CheckboxChoiceWidget` * :class:`deform:deform.widget.CheckboxChoiceWidget`
""" """
import colander import colander
from deform.widget import (Widget, TextInputWidget, TextAreaWidget, from deform.widget import (Widget, TextInputWidget, TextAreaWidget,
PasswordWidget, CheckedPasswordWidget,
SelectWidget, CheckboxChoiceWidget) SelectWidget, CheckboxChoiceWidget)
from webhelpers2.html import HTML from webhelpers2.html import HTML

View file

@ -25,11 +25,11 @@ Auth Views
""" """
import colander import colander
from deform.widget import TextInputWidget, PasswordWidget, CheckedPasswordWidget
from wuttaweb.views import View from wuttaweb.views import View
from wuttaweb.db import Session from wuttaweb.db import Session
from wuttaweb.auth import login_user, logout_user from wuttaweb.auth import login_user, logout_user
from wuttaweb.forms import widgets
class AuthView(View): class AuthView(View):
@ -97,14 +97,14 @@ class AuthView(View):
schema.add(colander.SchemaNode( schema.add(colander.SchemaNode(
colander.String(), colander.String(),
name='username', name='username',
widget=TextInputWidget(attributes={ widget=widgets.TextInputWidget(attributes={
'ref': 'username', 'ref': 'username',
}))) })))
schema.add(colander.SchemaNode( schema.add(colander.SchemaNode(
colander.String(), colander.String(),
name='password', name='password',
widget=PasswordWidget(attributes={ widget=widgets.PasswordWidget(attributes={
'ref': 'password', 'ref': 'password',
}))) })))
@ -174,13 +174,13 @@ class AuthView(View):
schema.add(colander.SchemaNode( schema.add(colander.SchemaNode(
colander.String(), colander.String(),
name='current_password', name='current_password',
widget=PasswordWidget(), widget=widgets.PasswordWidget(),
validator=self.change_password_validate_current_password)) validator=self.change_password_validate_current_password))
schema.add(colander.SchemaNode( schema.add(colander.SchemaNode(
colander.String(), colander.String(),
name='new_password', name='new_password',
widget=CheckedPasswordWidget(), widget=widgets.CheckedPasswordWidget(),
validator=self.change_password_validate_new_password)) validator=self.change_password_validate_new_password))
return schema return schema

View file

@ -28,6 +28,7 @@ import colander
from wuttjamaican.db.model import User from wuttjamaican.db.model import User
from wuttaweb.views import MasterView from wuttaweb.views import MasterView
from wuttaweb.forms import widgets
from wuttaweb.forms.schema import PersonRef, RoleRefs from wuttaweb.forms.schema import PersonRef, RoleRefs
from wuttaweb.db import Session from wuttaweb.db import Session
@ -93,9 +94,15 @@ class UserView(MasterView):
f.set_validator('username', self.unique_username) f.set_validator('username', self.unique_username)
# password # password
# if not (self.creating or self.editing): # nb. we must avoid 'password' as field name since
# f.remove('password') # ColanderAlchemy wants to handle the raw/hashed value
f.remove('password') f.remove('password')
# nb. no need for password field if readonly
if self.creating or self.editing:
# nb. use 'set_password' as field name
f.append('set_password')
f.set_required('set_password', False)
f.set_widget('set_password', widgets.CheckedPasswordWidget())
# roles # roles
f.append('roles') f.append('roles')
@ -121,9 +128,16 @@ class UserView(MasterView):
def objectify(self, form, session=None): def objectify(self, form, session=None):
""" """ """ """
data = form.validated
# normal logic first # normal logic first
user = super().objectify(form) user = super().objectify(form)
# 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 # update roles for user
# TODO # TODO
# if self.has_perm('edit_roles'): # if self.has_perm('edit_roles'):

View file

@ -218,6 +218,18 @@ class TestForm(TestCase):
self.assertIn('name', schema) self.assertIn('name', schema)
self.assertIn('value', schema) self.assertIn('value', schema)
# ColanderAlchemy schema still has *all* requested fields
form = self.make_form(model_instance=model.Setting(name='uhoh'),
fields=['name', 'value', 'foo', 'bar'])
self.assertEqual(form.fields, ['name', 'value', 'foo', 'bar'])
self.assertIsNone(form.schema)
schema = form.get_schema()
self.assertEqual(len(schema.children), 4)
self.assertIn('name', schema)
self.assertIn('value', schema)
self.assertIn('foo', schema)
self.assertIn('bar', schema)
# schema nodes are required by default # schema nodes are required by default
form = self.make_form(fields=['foo', 'bar']) form = self.make_form(fields=['foo', 'bar'])
schema = form.get_schema() schema = form.get_schema()

View file

@ -15,6 +15,9 @@ class TestUserView(WebTestCase):
def make_view(self): def make_view(self):
return mod.UserView(self.request) return mod.UserView(self.request)
def test_includeme(self):
self.pyramid_config.include('wuttaweb.views.users')
def test_get_query(self): def test_get_query(self):
view = self.make_view() view = self.make_view()
query = view.get_query(session=self.session) query = view.get_query(session=self.session)
@ -76,11 +79,13 @@ class TestUserView(WebTestCase):
def test_objectify(self): def test_objectify(self):
model = self.app.model model = self.app.model
auth = self.app.get_auth_handler()
blokes = model.Role(name="Blokes") blokes = model.Role(name="Blokes")
self.session.add(blokes) self.session.add(blokes)
others = model.Role(name="Others") others = model.Role(name="Others")
self.session.add(others) self.session.add(others)
barney = model.User(username='barney') barney = model.User(username='barney')
auth.set_user_password(barney, 'testpass')
barney.roles.append(blokes) barney.roles.append(blokes)
self.session.add(barney) self.session.add(barney)
self.session.commit() self.session.commit()
@ -93,6 +98,14 @@ class TestUserView(WebTestCase):
self.assertEqual(len(barney.roles), 1) self.assertEqual(len(barney.roles), 1)
self.assertEqual(barney.roles[0].name, "Blokes") self.assertEqual(barney.roles[0].name, "Blokes")
# form can update user password
self.assertTrue(auth.check_user_password(barney, 'testpass'))
form = view.make_model_form(model_instance=barney)
form.validated = {'username': 'barney', 'set_password': 'testpass2'}
user = view.objectify(form, session=self.session)
self.assertIs(user, barney)
self.assertTrue(auth.check_user_password(barney, 'testpass2'))
# form can update user roles # form can update user roles
form = view.make_model_form(model_instance=barney) form = view.make_model_form(model_instance=barney)
form.validated = {'username': 'barney', 'roles': {others.uuid}} form.validated = {'username': 'barney', 'roles': {others.uuid}}