1
0
Fork 0

feat: add basic Roles view

can't edit user/role/perm mappings yet, just minimal CRUD
This commit is contained in:
Lance Edgar 2024-08-13 10:52:30 -05:00
parent eac3b81918
commit 7ad6a9d5a0
8 changed files with 178 additions and 5 deletions

View file

@ -29,5 +29,6 @@
views.essential views.essential
views.master views.master
views.people views.people
views.roles
views.settings views.settings
views.users views.users

View file

@ -0,0 +1,6 @@
``wuttaweb.views.roles``
========================
.. automodule:: wuttaweb.views.roles
:members:

View file

@ -152,6 +152,11 @@ class MenuHandler(GenericHandler):
'route': 'users', 'route': 'users',
'perm': 'users.list', 'perm': 'users.list',
}, },
{
'title': "Roles",
'route': 'roles',
'perm': 'roles.list',
},
{'type': 'sep'}, {'type': 'sep'},
{ {
'title': "App Info", 'title': "App Info",

View file

@ -16,6 +16,9 @@
<b-field horizontal label="App Title"> <b-field horizontal label="App Title">
<span>${app.get_title()}</span> <span>${app.get_title()}</span>
</b-field> </b-field>
<b-field horizontal label="Production Mode">
<span>${config.production()}</span>
</b-field>
</div> </div>
</div> </div>
</nav> </nav>

View file

@ -33,6 +33,7 @@ That will in turn include the following modules:
* :mod:`wuttaweb.views.common` * :mod:`wuttaweb.views.common`
* :mod:`wuttaweb.views.settings` * :mod:`wuttaweb.views.settings`
* :mod:`wuttaweb.views.people` * :mod:`wuttaweb.views.people`
* :mod:`wuttaweb.views.roles`
* :mod:`wuttaweb.views.users` * :mod:`wuttaweb.views.users`
""" """
@ -44,6 +45,7 @@ def defaults(config, **kwargs):
config.include(mod('wuttaweb.views.common')) config.include(mod('wuttaweb.views.common'))
config.include(mod('wuttaweb.views.settings')) config.include(mod('wuttaweb.views.settings'))
config.include(mod('wuttaweb.views.people')) config.include(mod('wuttaweb.views.people'))
config.include(mod('wuttaweb.views.roles'))
config.include(mod('wuttaweb.views.users')) config.include(mod('wuttaweb.views.users'))

100
src/wuttaweb/views/roles.py Normal file
View file

@ -0,0 +1,100 @@
# -*- coding: utf-8; -*-
################################################################################
#
# wuttaweb -- Web App for Wutta Framework
# Copyright © 2024 Lance Edgar
#
# This file is part of Wutta Framework.
#
# Wutta Framework is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# Wutta Framework is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Views for roles
"""
from wuttjamaican.db.model import Role
from wuttaweb.views import MasterView
from wuttaweb.db import Session
class RoleView(MasterView):
"""
Master view for roles.
Notable URLs provided by this class:
* ``/roles/``
* ``/roles/new``
* ``/roles/XXX``
* ``/roles/XXX/edit``
* ``/roles/XXX/delete``
"""
model_class = Role
grid_columns = [
'name',
'notes',
]
# TODO: master should handle this, possibly via configure_form()
def get_query(self, session=None):
""" """
model = self.app.model
query = super().get_query(session=session)
return query.order_by(model.Role.name)
def configure_grid(self, g):
""" """
super().configure_grid(g)
# name
g.set_link('name')
def configure_form(self, f):
""" """
super().configure_form(f)
# never show these
f.remove('permission_refs',
'user_refs')
# name
f.set_validator('name', self.unique_name)
def unique_name(self, node, value):
""" """
model = self.app.model
session = Session()
query = session.query(model.Role)\
.filter(model.Role.name == value)
if self.editing:
uuid = self.request.matchdict['uuid']
query = query.filter(model.Role.uuid != uuid)
if query.count():
node.raise_invalid("Name must be unique")
def defaults(config, **kwargs):
base = globals()
RoleView = kwargs.get('RoleView', base['RoleView'])
RoleView.defaults(config)
def includeme(config):
defaults(config)

57
tests/views/test_roles.py Normal file
View file

@ -0,0 +1,57 @@
# -*- coding: utf-8; -*-
from unittest.mock import patch
from sqlalchemy import orm
import colander
from wuttaweb.views import roles as mod
from tests.util import WebTestCase
class TestRoleView(WebTestCase):
def make_view(self):
return mod.RoleView(self.request)
def test_get_query(self):
view = self.make_view()
query = view.get_query(session=self.session)
self.assertIsInstance(query, orm.Query)
def test_configure_grid(self):
model = self.app.model
view = self.make_view()
grid = view.make_grid(model_class=model.Role)
self.assertFalse(grid.is_linked('name'))
view.configure_grid(grid)
self.assertTrue(grid.is_linked('name'))
def test_configure_form(self):
model = self.app.model
view = self.make_view()
form = view.make_form(model_class=model.Person)
self.assertNotIn('name', form.validators)
view.configure_form(form)
self.assertIsNotNone(form.validators['name'])
def test_unique_name(self):
model = self.app.model
view = self.make_view()
role = model.Role(name='Foo')
self.session.add(role)
self.session.commit()
with patch.object(mod, 'Session', return_value=self.session):
# invalid if same name in data
node = colander.SchemaNode(colander.String(), name='name')
self.assertRaises(colander.Invalid, view.unique_name, node, 'Foo')
# but not if name belongs to current role
view.editing = True
self.request.matchdict = {'uuid': role.uuid}
node = colander.SchemaNode(colander.String(), name='name')
self.assertIsNone(view.unique_name(node, 'Foo'))

View file

@ -5,16 +5,15 @@ from unittest.mock import patch
from sqlalchemy import orm from sqlalchemy import orm
import colander import colander
from pyramid.httpexceptions import HTTPNotFound
from wuttaweb.views import users from wuttaweb.views import users as mod
from tests.util import WebTestCase from tests.util import WebTestCase
class TestPersonView(WebTestCase): class TestUserView(WebTestCase):
def make_view(self): def make_view(self):
return users.UserView(self.request) return mod.UserView(self.request)
def test_get_query(self): def test_get_query(self):
view = self.make_view() view = self.make_view()
@ -45,7 +44,7 @@ class TestPersonView(WebTestCase):
self.session.add(user) self.session.add(user)
self.session.commit() self.session.commit()
with patch.object(users, 'Session', return_value=self.session): with patch.object(mod, 'Session', return_value=self.session):
# invalid if same username in data # invalid if same username in data
node = colander.SchemaNode(colander.String(), name='username') node = colander.SchemaNode(colander.String(), name='username')