feat: expose User "roles" for editing
This commit is contained in:
parent
bdfa0197b2
commit
97e914c2e0
|
@ -120,6 +120,13 @@ class Form:
|
|||
|
||||
See also :meth:`set_validator()`.
|
||||
|
||||
.. attribute:: defaults
|
||||
|
||||
Dict of default field values, used to construct the form in
|
||||
:meth:`get_schema()`.
|
||||
|
||||
See also :meth:`set_default()`.
|
||||
|
||||
.. attribute:: readonly
|
||||
|
||||
Boolean indicating the form does not allow submit. In practice
|
||||
|
@ -248,6 +255,7 @@ class Form:
|
|||
nodes={},
|
||||
widgets={},
|
||||
validators={},
|
||||
defaults={},
|
||||
readonly=False,
|
||||
readonly_fields=[],
|
||||
required_fields={},
|
||||
|
@ -271,6 +279,7 @@ class Form:
|
|||
self.nodes = nodes or {}
|
||||
self.widgets = widgets or {}
|
||||
self.validators = validators or {}
|
||||
self.defaults = defaults or {}
|
||||
self.readonly = readonly
|
||||
self.readonly_fields = set(readonly_fields or [])
|
||||
self.required_fields = required_fields or {}
|
||||
|
@ -375,6 +384,23 @@ class Form:
|
|||
"""
|
||||
self.fields = FieldList(fields)
|
||||
|
||||
def append(self, *keys):
|
||||
"""
|
||||
Add some fields(s) to the form.
|
||||
|
||||
This is a convenience to allow adding multiple fields at
|
||||
once::
|
||||
|
||||
form.append('first_field',
|
||||
'second_field',
|
||||
'third_field')
|
||||
|
||||
It will add each field to :attr:`fields`.
|
||||
"""
|
||||
for key in keys:
|
||||
if key not in self.fields:
|
||||
self.fields.append(key)
|
||||
|
||||
def remove(self, *keys):
|
||||
"""
|
||||
Remove some fields(s) from the form.
|
||||
|
@ -471,6 +497,18 @@ class Form:
|
|||
if self.schema and key in self.schema:
|
||||
self.schema[key].validator = validator
|
||||
|
||||
def set_default(self, key, value):
|
||||
"""
|
||||
Set/override the default value for a field.
|
||||
|
||||
:param key: Name of field.
|
||||
|
||||
:param validator: Default value for the field.
|
||||
|
||||
Default value overrides are tracked via :attr:`defaults`.
|
||||
"""
|
||||
self.defaults[key] = value
|
||||
|
||||
def set_readonly(self, key, readonly=True):
|
||||
"""
|
||||
Enable or disable the "readonly" flag for a given field.
|
||||
|
@ -624,30 +662,17 @@ class Form:
|
|||
|
||||
if self.model_class:
|
||||
|
||||
# first define full list of 'includes' - final schema
|
||||
# should contain all of these fields
|
||||
includes = list(fields)
|
||||
|
||||
# determine which we want ColanderAlchemy to handle
|
||||
auto_includes = []
|
||||
for key in includes:
|
||||
|
||||
# skip if we already have a node defined
|
||||
# collect list of field names and/or nodes
|
||||
includes = []
|
||||
for key in fields:
|
||||
if key in self.nodes:
|
||||
continue
|
||||
|
||||
# we want the magic for this field
|
||||
auto_includes.append(key)
|
||||
includes.append(self.nodes[key])
|
||||
else:
|
||||
includes.append(key)
|
||||
|
||||
# make initial schema with ColanderAlchemy magic
|
||||
schema = SQLAlchemySchemaNode(self.model_class,
|
||||
includes=auto_includes)
|
||||
|
||||
# now fill in the blanks for non-magic fields
|
||||
for key in includes:
|
||||
if key not in auto_includes:
|
||||
node = self.nodes[key]
|
||||
schema.add(node)
|
||||
includes=includes)
|
||||
|
||||
else:
|
||||
|
||||
|
@ -685,6 +710,11 @@ class Form:
|
|||
elif key in schema: # field-level
|
||||
schema[key].validator = validator
|
||||
|
||||
# apply default value overrides
|
||||
for key, value in self.defaults.items():
|
||||
if key in schema:
|
||||
schema[key].default = value
|
||||
|
||||
# apply required flags
|
||||
for key, required in self.required_fields.items():
|
||||
if key in schema:
|
||||
|
|
|
@ -257,3 +257,44 @@ class PersonRef(ObjectRef):
|
|||
def sort_query(self, query):
|
||||
""" """
|
||||
return query.order_by(self.model_class.full_name)
|
||||
|
||||
|
||||
class RoleRefs(colander.Set):
|
||||
"""
|
||||
Form schema type for the User
|
||||
:attr:`~wuttjamaican:wuttjamaican.db.model.auth.User.roles`
|
||||
association proxy field.
|
||||
"""
|
||||
|
||||
def __init__(self, request, session=None, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.request = request
|
||||
self.config = self.request.wutta_config
|
||||
self.app = self.config.get_app()
|
||||
self.session = session or Session()
|
||||
|
||||
def widget_maker(self, **kwargs):
|
||||
"""
|
||||
Constructs a default widget for the field.
|
||||
|
||||
:returns: Instance of
|
||||
:class:`~wuttaweb.forms.widgets.RoleRefsWidget`.
|
||||
"""
|
||||
kwargs.setdefault('session', self.session)
|
||||
|
||||
if 'values' not in kwargs:
|
||||
model = self.app.model
|
||||
auth = self.app.get_auth_handler()
|
||||
avoid = {
|
||||
auth.get_role_authenticated(self.session),
|
||||
auth.get_role_anonymous(self.session),
|
||||
}
|
||||
avoid = set([role.uuid for role in avoid])
|
||||
roles = self.session.query(model.Role)\
|
||||
.filter(~model.Role.uuid.in_(avoid))\
|
||||
.order_by(model.Role.name)\
|
||||
.all()
|
||||
values = [(role.uuid, role.name) for role in roles]
|
||||
kwargs['values'] = values
|
||||
|
||||
return widgets.RoleRefsWidget(self.request, **kwargs)
|
||||
|
|
|
@ -32,11 +32,16 @@ in the namespace:
|
|||
* :class:`deform:deform.widget.TextInputWidget`
|
||||
* :class:`deform:deform.widget.TextAreaWidget`
|
||||
* :class:`deform:deform.widget.SelectWidget`
|
||||
* :class:`deform:deform.widget.CheckboxChoiceWidget`
|
||||
"""
|
||||
|
||||
from deform.widget import Widget, TextInputWidget, TextAreaWidget, SelectWidget
|
||||
import colander
|
||||
from deform.widget import (Widget, TextInputWidget, TextAreaWidget,
|
||||
SelectWidget, CheckboxChoiceWidget)
|
||||
from webhelpers2.html import HTML
|
||||
|
||||
from wuttaweb.db import Session
|
||||
|
||||
|
||||
class ObjectRefWidget(SelectWidget):
|
||||
"""
|
||||
|
@ -96,3 +101,45 @@ class NotesWidget(TextAreaWidget):
|
|||
* ``readonly/notes``
|
||||
"""
|
||||
readonly_template = 'readonly/notes'
|
||||
|
||||
|
||||
class RoleRefsWidget(CheckboxChoiceWidget):
|
||||
"""
|
||||
Widget for use with User
|
||||
:attr:`~wuttjamaican:wuttjamaican.db.model.auth.User.roles` field.
|
||||
|
||||
This is a subclass of
|
||||
:class:`deform:deform.widget.CheckboxChoiceWidget` and uses these
|
||||
Deform templates:
|
||||
|
||||
* ``checkbox_choice``
|
||||
* ``readonly/checkbox_choice``
|
||||
"""
|
||||
|
||||
def __init__(self, request, session=None, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.request = request
|
||||
self.config = self.request.wutta_config
|
||||
self.app = self.config.get_app()
|
||||
self.session = session or Session()
|
||||
|
||||
def serialize(self, field, cstruct, **kw):
|
||||
""" """
|
||||
# special logic when field is editable
|
||||
readonly = kw.get('readonly', self.readonly)
|
||||
if not readonly:
|
||||
|
||||
# but does not apply if current user is root
|
||||
if not self.request.is_root:
|
||||
auth = self.app.get_auth_handler()
|
||||
admin = auth.get_role_administrator(self.session)
|
||||
|
||||
# prune admin role from values list; it should not be
|
||||
# one of the options since current user is not admin
|
||||
values = kw.get('values', self.values)
|
||||
values = [val for val in values
|
||||
if val[0] != admin.uuid]
|
||||
kw['values'] = values
|
||||
|
||||
# default logic from here
|
||||
return super().serialize(field, cstruct, **kw)
|
||||
|
|
|
@ -186,6 +186,23 @@ class Grid:
|
|||
"""
|
||||
self.columns = FieldList(columns)
|
||||
|
||||
def append(self, *keys):
|
||||
"""
|
||||
Add some columns(s) to the grid.
|
||||
|
||||
This is a convenience to allow adding multiple columns at
|
||||
once::
|
||||
|
||||
grid.append('first_field',
|
||||
'second_field',
|
||||
'third_field')
|
||||
|
||||
It will add each column to :attr:`columns`.
|
||||
"""
|
||||
for key in keys:
|
||||
if key not in self.columns:
|
||||
self.columns.append(key)
|
||||
|
||||
def remove(self, *keys):
|
||||
"""
|
||||
Remove some column(s) from the grid.
|
||||
|
|
18
src/wuttaweb/templates/deform/checkbox_choice.pt
Normal file
18
src/wuttaweb/templates/deform/checkbox_choice.pt
Normal file
|
@ -0,0 +1,18 @@
|
|||
<div tal:define="css_class css_class|field.widget.css_class;
|
||||
style style|field.widget.style;
|
||||
oid oid|field.oid;">
|
||||
${field.start_sequence()}
|
||||
<div style="display: flex; flex-direction: column; gap: 0.5rem;">
|
||||
<div tal:repeat="choice values | field.widget.values"
|
||||
tal:omit-tag="">
|
||||
<b-checkbox tal:define="(value, title) choice"
|
||||
name="checkbox"
|
||||
native-value="${value}"
|
||||
tal:attributes=":value 'true' if value in cstruct else 'false';
|
||||
attributes|field.widget.attributes|{};">
|
||||
${title}
|
||||
</b-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
${field.end_sequence()}
|
||||
</div>
|
|
@ -28,6 +28,8 @@ import importlib
|
|||
import json
|
||||
import logging
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
import colander
|
||||
from webhelpers2.html import HTML, tags
|
||||
|
||||
|
@ -420,14 +422,17 @@ def get_model_fields(config, model_class=None):
|
|||
that to determine the field listing if applicable. Otherwise this
|
||||
returns ``None``.
|
||||
"""
|
||||
if model_class:
|
||||
import sqlalchemy as sa
|
||||
app = config.get_app()
|
||||
model = app.model
|
||||
if model_class and issubclass(model_class, model.Base):
|
||||
mapper = sa.inspect(model_class)
|
||||
fields = list([prop.key for prop in mapper.iterate_properties])
|
||||
return fields
|
||||
if not model_class:
|
||||
return
|
||||
|
||||
app = config.get_app()
|
||||
model = app.model
|
||||
if not issubclass(model_class, model.Base):
|
||||
return
|
||||
|
||||
mapper = sa.inspect(model_class)
|
||||
fields = [prop.key for prop in mapper.iterate_properties]
|
||||
return fields
|
||||
|
||||
|
||||
def make_json_safe(value, key=None, warn=True):
|
||||
|
|
|
@ -28,7 +28,7 @@ import colander
|
|||
|
||||
from wuttjamaican.db.model import User
|
||||
from wuttaweb.views import MasterView
|
||||
from wuttaweb.forms.schema import PersonRef
|
||||
from wuttaweb.forms.schema import PersonRef, RoleRefs
|
||||
from wuttaweb.db import Session
|
||||
|
||||
|
||||
|
@ -77,10 +77,10 @@ class UserView(MasterView):
|
|||
def configure_form(self, f):
|
||||
""" """
|
||||
super().configure_form(f)
|
||||
user = f.model_instance
|
||||
|
||||
# never show these
|
||||
f.remove('person_uuid',
|
||||
'password',
|
||||
'role_refs')
|
||||
|
||||
# person
|
||||
|
@ -90,6 +90,18 @@ class UserView(MasterView):
|
|||
# username
|
||||
f.set_validator('username', self.unique_username)
|
||||
|
||||
# password
|
||||
# if not (self.creating or self.editing):
|
||||
# f.remove('password')
|
||||
f.remove('password')
|
||||
|
||||
# roles
|
||||
f.append('roles')
|
||||
f.set_node('roles', RoleRefs(self.request))
|
||||
|
||||
if not self.creating:
|
||||
f.set_default('roles', [role.uuid for role in user.roles])
|
||||
|
||||
def unique_username(self, node, value):
|
||||
""" """
|
||||
model = self.app.model
|
||||
|
@ -105,6 +117,62 @@ class UserView(MasterView):
|
|||
if query.count():
|
||||
node.raise_invalid("Username must be unique")
|
||||
|
||||
def objectify(self, form, session=None):
|
||||
""" """
|
||||
# normal logic first
|
||||
user = super().objectify(form)
|
||||
|
||||
# update roles for user
|
||||
# TODO
|
||||
# if self.has_perm('edit_roles'):
|
||||
self.update_roles(user, form, session=session)
|
||||
|
||||
return user
|
||||
|
||||
def update_roles(self, user, form, session=None):
|
||||
""" """
|
||||
# TODO
|
||||
# if not self.has_perm('edit_roles'):
|
||||
# return
|
||||
data = form.validated
|
||||
if 'roles' not in data:
|
||||
return
|
||||
|
||||
model = self.app.model
|
||||
session = session or Session()
|
||||
auth = self.app.get_auth_handler()
|
||||
|
||||
old_roles = set([role.uuid for role in user.roles])
|
||||
new_roles = data['roles']
|
||||
|
||||
admin = auth.get_role_administrator(session)
|
||||
ignored = {
|
||||
auth.get_role_authenticated(session).uuid,
|
||||
auth.get_role_anonymous(session).uuid,
|
||||
}
|
||||
|
||||
# add any new roles for the user, taking care to avoid certain
|
||||
# unwanted operations for built-in roles
|
||||
for uuid in new_roles:
|
||||
if uuid in ignored:
|
||||
continue
|
||||
if uuid in old_roles:
|
||||
continue
|
||||
if uuid == admin.uuid and not self.request.is_root:
|
||||
continue
|
||||
role = session.get(model.Role, uuid)
|
||||
user.roles.append(role)
|
||||
|
||||
# remove any roles which were *not* specified, taking care to
|
||||
# avoid certain unwanted operations for built-in roles
|
||||
for uuid in old_roles:
|
||||
if uuid in new_roles:
|
||||
continue
|
||||
if uuid == admin.uuid and not self.request.is_root:
|
||||
continue
|
||||
role = session.get(model.Role, uuid)
|
||||
user.roles.remove(role)
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
|
|
@ -84,6 +84,12 @@ class TestForm(TestCase):
|
|||
form.set_fields(['baz'])
|
||||
self.assertEqual(form.fields, ['baz'])
|
||||
|
||||
def test_append(self):
|
||||
form = self.make_form(fields=['one', 'two'])
|
||||
self.assertEqual(form.fields, ['one', 'two'])
|
||||
form.append('one', 'two', 'three')
|
||||
self.assertEqual(form.fields, ['one', 'two', 'three'])
|
||||
|
||||
def test_remove(self):
|
||||
form = self.make_form(fields=['one', 'two', 'three', 'four'])
|
||||
self.assertEqual(form.fields, ['one', 'two', 'three', 'four'])
|
||||
|
@ -157,6 +163,14 @@ class TestForm(TestCase):
|
|||
self.assertIs(form.validators['foo'], validate2)
|
||||
self.assertIs(schema['foo'].validator, validate2)
|
||||
|
||||
def test_set_default(self):
|
||||
form = self.make_form(fields=['foo', 'bar'])
|
||||
self.assertEqual(form.defaults, {})
|
||||
|
||||
# basic
|
||||
form.set_default('foo', 42)
|
||||
self.assertEqual(form.defaults['foo'], 42)
|
||||
|
||||
def test_get_schema(self):
|
||||
model = self.app.model
|
||||
form = self.make_form()
|
||||
|
@ -233,6 +247,12 @@ class TestForm(TestCase):
|
|||
schema = form.get_schema()
|
||||
self.assertIs(schema.validator, validate)
|
||||
|
||||
# default value overrides are honored
|
||||
form = self.make_form(model_class=model.Setting)
|
||||
form.set_default('name', 'foo')
|
||||
schema = form.get_schema()
|
||||
self.assertEqual(schema['name'].default, 'foo')
|
||||
|
||||
def test_get_deform(self):
|
||||
model = self.app.model
|
||||
schema = self.make_schema()
|
||||
|
@ -422,6 +442,19 @@ class TestForm(TestCase):
|
|||
# nb. no error message
|
||||
self.assertNotIn('message', html)
|
||||
|
||||
def test_get_vue_model_data(self):
|
||||
schema = self.make_schema()
|
||||
form = self.make_form(schema=schema)
|
||||
|
||||
# 2 fields by default (foo, bar)
|
||||
data = form.get_vue_model_data()
|
||||
self.assertEqual(len(data), 2)
|
||||
|
||||
# still just 2 fields even if we request more
|
||||
form.set_fields(['foo', 'bar', 'baz'])
|
||||
data = form.get_vue_model_data()
|
||||
self.assertEqual(len(data), 2)
|
||||
|
||||
def test_get_field_errors(self):
|
||||
schema = self.make_schema()
|
||||
form = self.make_form(schema=schema)
|
||||
|
|
|
@ -197,3 +197,27 @@ class TestPersonRef(DataTestCase):
|
|||
sorted_query = typ.sort_query(query)
|
||||
self.assertIsInstance(sorted_query, orm.Query)
|
||||
self.assertIsNot(sorted_query, query)
|
||||
|
||||
|
||||
class TestRoleRefs(DataTestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.setup_db()
|
||||
self.request = testing.DummyRequest(wutta_config=self.config)
|
||||
|
||||
def test_widget_maker(self):
|
||||
model = self.app.model
|
||||
auth = self.app.get_auth_handler()
|
||||
admin = auth.get_role_administrator(self.session)
|
||||
authed = auth.get_role_authenticated(self.session)
|
||||
anon = auth.get_role_anonymous(self.session)
|
||||
blokes = model.Role(name="Blokes")
|
||||
self.session.add(blokes)
|
||||
self.session.commit()
|
||||
|
||||
# default values for widget includes all but: authed, anon
|
||||
typ = mod.RoleRefs(self.request, session=self.session)
|
||||
widget = typ.widget_maker()
|
||||
self.assertEqual(len(widget.values), 2)
|
||||
self.assertEqual(widget.values[0][1], "Administrator")
|
||||
self.assertEqual(widget.values[1][1], "Blokes")
|
||||
|
|
|
@ -4,8 +4,8 @@ import colander
|
|||
import deform
|
||||
from pyramid import testing
|
||||
|
||||
from wuttaweb.forms import widgets
|
||||
from wuttaweb.forms.schema import PersonRef
|
||||
from wuttaweb.forms import widgets as mod
|
||||
from wuttaweb.forms.schema import PersonRef, RoleRefs
|
||||
from tests.util import WebTestCase
|
||||
|
||||
|
||||
|
@ -25,7 +25,7 @@ class TestObjectRefWidget(WebTestCase):
|
|||
|
||||
# standard (editable)
|
||||
node = colander.SchemaNode(PersonRef(self.request, session=self.session))
|
||||
widget = widgets.ObjectRefWidget(self.request)
|
||||
widget = mod.ObjectRefWidget(self.request)
|
||||
field = self.make_field(node)
|
||||
html = widget.serialize(field, person.uuid)
|
||||
self.assertIn('<b-select ', html)
|
||||
|
@ -33,7 +33,45 @@ class TestObjectRefWidget(WebTestCase):
|
|||
# readonly
|
||||
node = colander.SchemaNode(PersonRef(self.request, session=self.session))
|
||||
node.model_instance = person
|
||||
widget = widgets.ObjectRefWidget(self.request)
|
||||
widget = mod.ObjectRefWidget(self.request)
|
||||
field = self.make_field(node)
|
||||
html = widget.serialize(field, person.uuid, readonly=True)
|
||||
self.assertEqual(html.strip(), '<span>Betty Boop</span>')
|
||||
|
||||
|
||||
class TestRoleRefsWidget(WebTestCase):
|
||||
|
||||
def make_field(self, node, **kwargs):
|
||||
# TODO: not sure why default renderer is in use even though
|
||||
# pyramid_deform was included in setup? but this works..
|
||||
kwargs.setdefault('renderer', deform.Form.default_renderer)
|
||||
return deform.Field(node, **kwargs)
|
||||
|
||||
def test_serialize(self):
|
||||
model = self.app.model
|
||||
auth = self.app.get_auth_handler()
|
||||
admin = auth.get_role_administrator(self.session)
|
||||
blokes = model.Role(name="Blokes")
|
||||
self.session.add(blokes)
|
||||
self.session.commit()
|
||||
|
||||
# nb. we let the field construct the widget via our type
|
||||
node = colander.SchemaNode(RoleRefs(self.request, session=self.session))
|
||||
field = self.make_field(node)
|
||||
widget = field.widget
|
||||
|
||||
# readonly values list includes admin
|
||||
html = widget.serialize(field, {admin.uuid, blokes.uuid}, readonly=True)
|
||||
self.assertIn(admin.name, html)
|
||||
self.assertIn(blokes.name, html)
|
||||
|
||||
# editable values list *excludes* admin (by default)
|
||||
html = widget.serialize(field, {admin.uuid, blokes.uuid})
|
||||
self.assertNotIn(admin.uuid, html)
|
||||
self.assertIn(blokes.uuid, html)
|
||||
|
||||
# but admin is included for root user
|
||||
self.request.is_root = True
|
||||
html = widget.serialize(field, {admin.uuid, blokes.uuid})
|
||||
self.assertIn(admin.uuid, html)
|
||||
self.assertIn(blokes.uuid, html)
|
||||
|
|
|
@ -69,6 +69,12 @@ class TestGrid(TestCase):
|
|||
self.assertEqual(grid.columns, ['name', 'value'])
|
||||
self.assertEqual(grid.get_columns(), ['name', 'value'])
|
||||
|
||||
def test_append(self):
|
||||
grid = self.make_grid(columns=['one', 'two'])
|
||||
self.assertEqual(grid.columns, ['one', 'two'])
|
||||
grid.append('one', 'two', 'three')
|
||||
self.assertEqual(grid.columns, ['one', 'two', 'three'])
|
||||
|
||||
def test_remove(self):
|
||||
grid = self.make_grid(columns=['one', 'two', 'three', 'four'])
|
||||
self.assertEqual(grid.columns, ['one', 'two', 'three', 'four'])
|
||||
|
|
|
@ -442,6 +442,14 @@ class TestGetModelFields(TestCase):
|
|||
self.config = WuttaConfig()
|
||||
self.app = self.config.get_app()
|
||||
|
||||
def test_empty_model_class(self):
|
||||
fields = util.get_model_fields(self.config)
|
||||
self.assertIsNone(fields)
|
||||
|
||||
def test_unknown_model_class(self):
|
||||
fields = util.get_model_fields(self.config, TestCase)
|
||||
self.assertIsNone(fields)
|
||||
|
||||
def test_basic(self):
|
||||
model = self.app.model
|
||||
fields = util.get_model_fields(self.config, model.Setting)
|
||||
|
|
|
@ -30,11 +30,29 @@ class TestUserView(WebTestCase):
|
|||
|
||||
def test_configure_form(self):
|
||||
model = self.app.model
|
||||
barney = model.User(username='barney')
|
||||
self.session.add(barney)
|
||||
self.session.commit()
|
||||
view = self.make_view()
|
||||
form = view.make_form(model_class=model.Person)
|
||||
self.assertIsNone(form.is_required('person'))
|
||||
view.configure_form(form)
|
||||
self.assertFalse(form.is_required('person'))
|
||||
|
||||
# person is *not* required
|
||||
with patch.object(view, 'creating', new=True):
|
||||
form = view.make_form(model_class=model.User)
|
||||
self.assertIsNone(form.is_required('person'))
|
||||
view.configure_form(form)
|
||||
self.assertFalse(form.is_required('person'))
|
||||
|
||||
# password removed (always, for now)
|
||||
with patch.object(view, 'viewing', new=True):
|
||||
form = view.make_form(model_instance=barney)
|
||||
self.assertIn('password', form)
|
||||
view.configure_form(form)
|
||||
self.assertNotIn('password', form)
|
||||
with patch.object(view, 'editing', new=True):
|
||||
form = view.make_form(model_instance=barney)
|
||||
self.assertIn('password', form)
|
||||
view.configure_form(form)
|
||||
self.assertNotIn('password', form)
|
||||
|
||||
def test_unique_username(self):
|
||||
model = self.app.model
|
||||
|
@ -55,3 +73,103 @@ class TestUserView(WebTestCase):
|
|||
self.request.matchdict = {'uuid': user.uuid}
|
||||
node = colander.SchemaNode(colander.String(), name='username')
|
||||
self.assertIsNone(view.unique_username(node, 'foo'))
|
||||
|
||||
def test_objectify(self):
|
||||
model = self.app.model
|
||||
blokes = model.Role(name="Blokes")
|
||||
self.session.add(blokes)
|
||||
others = model.Role(name="Others")
|
||||
self.session.add(others)
|
||||
barney = model.User(username='barney')
|
||||
barney.roles.append(blokes)
|
||||
self.session.add(barney)
|
||||
self.session.commit()
|
||||
view = self.make_view()
|
||||
view.editing = True
|
||||
self.request.matchdict = {'uuid': barney.uuid}
|
||||
|
||||
# sanity check, user is just in 'blokes' role
|
||||
self.session.refresh(barney)
|
||||
self.assertEqual(len(barney.roles), 1)
|
||||
self.assertEqual(barney.roles[0].name, "Blokes")
|
||||
|
||||
# form can update user roles
|
||||
form = view.make_model_form(model_instance=barney)
|
||||
form.validated = {'username': 'barney', 'roles': {others.uuid}}
|
||||
user = view.objectify(form, session=self.session)
|
||||
self.assertIs(user, barney)
|
||||
self.assertEqual(len(user.roles), 1)
|
||||
self.assertEqual(user.roles[0].name, "Others")
|
||||
|
||||
def test_update_roles(self):
|
||||
model = self.app.model
|
||||
auth = self.app.get_auth_handler()
|
||||
admin = auth.get_role_administrator(self.session)
|
||||
authed = auth.get_role_authenticated(self.session)
|
||||
anon = auth.get_role_anonymous(self.session)
|
||||
blokes = model.Role(name="Blokes")
|
||||
self.session.add(blokes)
|
||||
others = model.Role(name="Others")
|
||||
self.session.add(others)
|
||||
barney = model.User(username='barney')
|
||||
barney.roles.append(blokes)
|
||||
self.session.add(barney)
|
||||
self.session.commit()
|
||||
view = self.make_view()
|
||||
view.editing = True
|
||||
self.request.matchdict = {'uuid': barney.uuid}
|
||||
|
||||
# no error if data is missing roles
|
||||
form = view.make_model_form(model_instance=barney)
|
||||
form.validated = {'username': 'barneyx'}
|
||||
user = view.objectify(form, session=self.session)
|
||||
self.assertIs(user, barney)
|
||||
self.assertEqual(barney.username, 'barneyx')
|
||||
|
||||
# sanity check, user is just in 'blokes' role
|
||||
self.session.refresh(barney)
|
||||
self.assertEqual(len(barney.roles), 1)
|
||||
self.assertEqual(barney.roles[0].name, "Blokes")
|
||||
|
||||
# let's test a bunch at once to ensure:
|
||||
# - user roles are updated
|
||||
# - authed / anon roles are not added
|
||||
# - admin role not added if current user is not root
|
||||
form = view.make_model_form(model_instance=barney)
|
||||
form.validated = {'username': 'barney',
|
||||
'roles': {admin.uuid, authed.uuid, anon.uuid, others.uuid}}
|
||||
user = view.objectify(form, session=self.session)
|
||||
self.assertIs(user, barney)
|
||||
self.assertEqual(len(user.roles), 1)
|
||||
self.assertEqual(user.roles[0].name, "Others")
|
||||
|
||||
# let's test a bunch at once to ensure:
|
||||
# - user roles are updated
|
||||
# - admin role is added if current user is root
|
||||
self.request.is_root = True
|
||||
form = view.make_model_form(model_instance=barney)
|
||||
form.validated = {'username': 'barney',
|
||||
'roles': {admin.uuid, blokes.uuid, others.uuid}}
|
||||
user = view.objectify(form, session=self.session)
|
||||
self.assertIs(user, barney)
|
||||
self.assertEqual(len(user.roles), 3)
|
||||
role_uuids = set([role.uuid for role in user.roles])
|
||||
self.assertEqual(role_uuids, {admin.uuid, blokes.uuid, others.uuid})
|
||||
|
||||
# admin role not removed if current user is not root
|
||||
self.request.is_root = False
|
||||
form = view.make_model_form(model_instance=barney)
|
||||
form.validated = {'username': 'barney',
|
||||
'roles': {blokes.uuid, others.uuid}}
|
||||
user = view.objectify(form, session=self.session)
|
||||
self.assertIs(user, barney)
|
||||
self.assertEqual(len(user.roles), 3)
|
||||
|
||||
# admin role is removed if current user is root
|
||||
self.request.is_root = True
|
||||
form = view.make_model_form(model_instance=barney)
|
||||
form.validated = {'username': 'barney',
|
||||
'roles': {blokes.uuid, others.uuid}}
|
||||
user = view.objectify(form, session=self.session)
|
||||
self.assertIs(user, barney)
|
||||
self.assertEqual(len(user.roles), 2)
|
||||
|
|
Loading…
Reference in a new issue