1
0
Fork 0

feat: add permission checks for menus, view routes

This commit is contained in:
Lance Edgar 2024-08-14 21:20:00 -05:00
parent 675b51cac2
commit e3942ce65e
11 changed files with 537 additions and 40 deletions

View file

@ -272,9 +272,8 @@ class MenuHandler(GenericHandler):
current user. current user.
""" """
perm = item.get('perm') perm = item.get('perm')
# TODO if perm:
# if perm: return request.has_perm(perm)
# return request.has_perm(perm)
return True return True
def _mark_allowed(self, request, menus): def _mark_allowed(self, request, menus):

View file

@ -139,6 +139,16 @@ def new_request_set_user(
pyramid_config.add_subscriber('wuttaweb.subscribers.new_request_set_user', pyramid_config.add_subscriber('wuttaweb.subscribers.new_request_set_user',
'pyramid.events.NewRequest') 'pyramid.events.NewRequest')
You may wish to "supplement" this hook by registering your own
custom hook and then invoking this one as needed. You can then
pass certain params to override only parts of the logic:
:param user_getter: Optional getter function to retrieve the user
from database, instead of :func:`default_user_getter()`.
:param db_session: Optional :term:`db session` to use,
instead of :class:`wuttaweb.db.Session`.
This will add to the request object: This will add to the request object:
.. attribute:: request.user .. attribute:: request.user
@ -158,19 +168,36 @@ def new_request_set_user(
privileges. This is only possible if :attr:`request.is_admin` privileges. This is only possible if :attr:`request.is_admin`
is also true. is also true.
You may wish to "supplement" this hook by registering your own .. attribute:: request.user_permissions
custom hook and then invoking this one as needed. You can then
pass certain params to override only parts of the logic:
:param user_getter: Optional getter function to retrieve the user The ``set`` of permission names which are granted to the
from database, instead of :func:`default_user_getter()`. current user.
This set is obtained by calling
:meth:`~wuttjamaican:wuttjamaican.auth.AuthHandler.get_permissions()`.
.. function:: request.has_perm(name)
Shortcut to check if current user has the given permission::
if not request.has_perm('users.edit'):
raise self.forbidden()
.. function:: request.has_any_perm(*names)
Shortcut to check if current user has any of the given
permissions::
if request.has_any_perm('users.list', 'users.view'):
return "can either list or view"
else:
raise self.forbidden()
:param db_session: Optional :term:`db session` to use,
instead of :class:`wuttaweb.db.Session`.
""" """
request = event.request request = event.request
config = request.registry.settings['wutta_config'] config = request.registry.settings['wutta_config']
app = config.get_app() app = config.get_app()
auth = app.get_auth_handler()
# request.user # request.user
if db_session: if db_session:
@ -179,7 +206,6 @@ def new_request_set_user(
# request.is_admin # request.is_admin
def is_admin(request): def is_admin(request):
auth = app.get_auth_handler()
return auth.user_is_admin(request.user) return auth.user_is_admin(request.user)
request.set_property(is_admin, reify=True) request.set_property(is_admin, reify=True)
@ -191,6 +217,29 @@ def new_request_set_user(
return False return False
request.set_property(is_root, reify=True) request.set_property(is_root, reify=True)
# request.user_permissions
def user_permissions(request):
session = db_session or Session()
return auth.get_permissions(session, request.user)
request.set_property(user_permissions, reify=True)
# request.has_perm()
def has_perm(name):
if request.is_root:
return True
if name in request.user_permissions:
return True
return False
request.has_perm = has_perm
# request.has_any_perm()
def has_any_perm(*names):
for name in names:
if request.has_perm(name):
return True
return False
request.has_any_perm = has_any_perm
def before_render(event): def before_render(event):
""" """

View file

@ -222,7 +222,7 @@
% else: % else:
<h1 class="title">${index_title}</h1> <h1 class="title">${index_title}</h1>
% endif % endif
% if master and master.creatable and not master.creating: % if master and master.creatable and not master.creating and master.has_perm('create'):
<wutta-button once type="is-primary" <wutta-button once type="is-primary"
tag="a" href="${url(f'{route_prefix}.create')}" tag="a" href="${url(f'{route_prefix}.create')}"
icon-left="plus" icon-left="plus"
@ -235,8 +235,7 @@
<div class="level-right"> <div class="level-right">
## TODO % if master and master.configurable and not master.configuring and master.has_perm('configure'):
% if master and master.configurable and not master.configuring:
<div class="level-item"> <div class="level-item">
<wutta-button once type="is-primary" <wutta-button once type="is-primary"
tag="a" href="${url(f'{route_prefix}.configure')}" tag="a" href="${url(f'{route_prefix}.configure')}"

View file

@ -102,10 +102,50 @@ class CommonView(View):
# assign admin role # assign admin role
admin = auth.get_role_administrator(session) admin = auth.get_role_administrator(session)
user.roles.append(admin) user.roles.append(admin)
admin.notes = ("users in this role may \"become root\".\n\n"
"it's recommended not to grant other perms to this role.")
# ensure all built-in roles exist # initialize built-in roles
auth.get_role_authenticated(session) authed = auth.get_role_authenticated(session)
auth.get_role_anonymous(session) authed.notes = ("this role represents any user who *is* logged in.\n\n"
"you may grant any perms you like to it.")
anon = auth.get_role_anonymous(session)
anon.notes = ("this role represents any user who is *not* logged in.\n\n"
"you may grant any perms you like to it.")
# also make "Site Admin" role
site_admin_perms = [
'appinfo.list',
'appinfo.configure',
'people.list',
'people.create',
'people.view',
'people.edit',
'people.delete',
'roles.list',
'roles.create',
'roles.view',
'roles.edit',
'roles.edit_builtin',
'roles.delete',
'settings.list',
'settings.create',
'settings.view',
'settings.edit',
'settings.delete',
'users.list',
'users.create',
'users.view',
'users.edit',
'users.delete',
]
admin2 = model.Role(name="Site Admin")
admin2.notes = ("this is the \"daily driver\" admin role.\n\n"
"you may grant any perms you like to it.")
session.add(admin2)
user.roles.append(admin2)
for perm in site_admin_perms:
auth.grant_permission(admin2, perm)
# maybe make person # maybe make person
if data['first_name'] or data['last_name']: if data['first_name'] or data['last_name']:

View file

@ -198,6 +198,8 @@ class MasterView(View):
i.e. it should have an :meth:`edit()` view. Default value is i.e. it should have an :meth:`edit()` view. Default value is
``True``. ``True``.
See also :meth:`is_editable()`.
.. attribute:: deletable .. attribute:: deletable
Boolean indicating whether the view model supports "deleting" - Boolean indicating whether the view model supports "deleting" -
@ -802,6 +804,43 @@ class MasterView(View):
# support methods # support methods
############################## ##############################
def has_perm(self, name):
"""
Shortcut to check if current user has the given permission.
This will automatically add the :attr:`permission_prefix` to
``name`` before passing it on to
:func:`~wuttaweb.subscribers.request.has_perm()`.
For instance within the
:class:`~wuttaweb.views.users.UserView` these give the same
result::
self.request.has_perm('users.edit')
self.has_perm('edit')
So this shortcut only applies to permissions defined for the
current master view. The first example above must still be
used to check for "foreign" permissions (i.e. any needing a
different prefix).
"""
permission_prefix = self.get_permission_prefix()
return self.request.has_perm(f'{permission_prefix}.{name}')
def has_any_perm(self, *names):
"""
Shortcut to check if current user has any of the given
permissions.
This calls :meth:`has_perm()` until one returns ``True``. If
none do, returns ``False``.
"""
for name in names:
if self.has_perm(name):
return True
return False
def render_to_response(self, template, context): def render_to_response(self, template, context):
""" """
Locate and render an appropriate template, with the given Locate and render an appropriate template, with the given
@ -937,15 +976,15 @@ class MasterView(View):
# TODO: should split this off into index_get_grid_actions() ? # TODO: should split this off into index_get_grid_actions() ?
if self.viewable: if self.viewable and self.has_perm('view'):
actions.append(self.make_grid_action('view', icon='eye', actions.append(self.make_grid_action('view', icon='eye',
url=self.get_action_url_view)) url=self.get_action_url_view))
if self.editable: if self.editable and self.has_perm('edit'):
actions.append(self.make_grid_action('edit', icon='edit', actions.append(self.make_grid_action('edit', icon='edit',
url=self.get_action_url_edit)) url=self.get_action_url_edit))
if self.deletable: if self.deletable and self.has_perm('delete'):
actions.append(self.make_grid_action('delete', icon='trash', actions.append(self.make_grid_action('delete', icon='trash',
url=self.get_action_url_delete, url=self.get_action_url_delete,
link_class='has-text-danger')) link_class='has-text-danger'))
@ -1137,13 +1176,18 @@ class MasterView(View):
def get_action_url_edit(self, obj, i): def get_action_url_edit(self, obj, i):
""" """
Returns the "edit" grid action URL for the given object. Returns the "edit" grid action URL for the given object, if
applicable.
Most typically this is like ``/widgets/XXX/edit`` where Most typically this is like ``/widgets/XXX/edit`` where
``XXX`` represents the object's key/ID. ``XXX`` represents the object's key/ID.
Calls :meth:`get_action_url()` under the hood. This first calls :meth:`is_editable()` and if that is false,
this method will return ``None``.
Calls :meth:`get_action_url()` to generate the true URL.
""" """
if self.is_editable(obj):
return self.get_action_url('edit', obj) return self.get_action_url('edit', obj)
def get_action_url_delete(self, obj, i): def get_action_url_delete(self, obj, i):
@ -1162,6 +1206,19 @@ class MasterView(View):
if self.is_deletable(obj): if self.is_deletable(obj):
return self.get_action_url('delete', obj) return self.get_action_url('delete', obj)
def is_editable(self, obj):
"""
Returns a boolean indicating whether "edit" should be allowed
for the given model instance (and for current user).
By default this always return ``True``; subclass can override
if needed.
Note that the use of this method implies :attr:`editable` is
true, so the method does not need to check that flag.
"""
return True
def is_deletable(self, obj): def is_deletable(self, obj):
""" """
Returns a boolean indicating whether "delete" should be Returns a boolean indicating whether "delete" should be
@ -1634,7 +1691,8 @@ class MasterView(View):
if cls.listable: if cls.listable:
config.add_route(route_prefix, f'{url_prefix}/') config.add_route(route_prefix, f'{url_prefix}/')
config.add_view(cls, attr='index', config.add_view(cls, attr='index',
route_name=route_prefix) route_name=route_prefix,
permission=f'{permission_prefix}.list')
config.add_wutta_permission(permission_prefix, config.add_wutta_permission(permission_prefix,
f'{permission_prefix}.list', f'{permission_prefix}.list',
f"Browse / search {model_title_plural}") f"Browse / search {model_title_plural}")
@ -1644,7 +1702,8 @@ class MasterView(View):
config.add_route(f'{route_prefix}.create', config.add_route(f'{route_prefix}.create',
f'{url_prefix}/new') f'{url_prefix}/new')
config.add_view(cls, attr='create', config.add_view(cls, attr='create',
route_name=f'{route_prefix}.create') route_name=f'{route_prefix}.create',
permission=f'{permission_prefix}.create')
config.add_wutta_permission(permission_prefix, config.add_wutta_permission(permission_prefix,
f'{permission_prefix}.create', f'{permission_prefix}.create',
f"Create new {model_title}") f"Create new {model_title}")
@ -1654,7 +1713,8 @@ class MasterView(View):
instance_url_prefix = cls.get_instance_url_prefix() instance_url_prefix = cls.get_instance_url_prefix()
config.add_route(f'{route_prefix}.view', instance_url_prefix) config.add_route(f'{route_prefix}.view', instance_url_prefix)
config.add_view(cls, attr='view', config.add_view(cls, attr='view',
route_name=f'{route_prefix}.view') route_name=f'{route_prefix}.view',
permission=f'{permission_prefix}.view')
config.add_wutta_permission(permission_prefix, config.add_wutta_permission(permission_prefix,
f'{permission_prefix}.view', f'{permission_prefix}.view',
f"View {model_title}") f"View {model_title}")
@ -1665,7 +1725,8 @@ class MasterView(View):
config.add_route(f'{route_prefix}.edit', config.add_route(f'{route_prefix}.edit',
f'{instance_url_prefix}/edit') f'{instance_url_prefix}/edit')
config.add_view(cls, attr='edit', config.add_view(cls, attr='edit',
route_name=f'{route_prefix}.edit') route_name=f'{route_prefix}.edit',
permission=f'{permission_prefix}.edit')
config.add_wutta_permission(permission_prefix, config.add_wutta_permission(permission_prefix,
f'{permission_prefix}.edit', f'{permission_prefix}.edit',
f"Edit {model_title}") f"Edit {model_title}")
@ -1676,7 +1737,8 @@ class MasterView(View):
config.add_route(f'{route_prefix}.delete', config.add_route(f'{route_prefix}.delete',
f'{instance_url_prefix}/delete') f'{instance_url_prefix}/delete')
config.add_view(cls, attr='delete', config.add_view(cls, attr='delete',
route_name=f'{route_prefix}.delete') route_name=f'{route_prefix}.delete',
permission=f'{permission_prefix}.delete')
config.add_wutta_permission(permission_prefix, config.add_wutta_permission(permission_prefix,
f'{permission_prefix}.delete', f'{permission_prefix}.delete',
f"Delete {model_title}") f"Delete {model_title}")
@ -1686,7 +1748,8 @@ class MasterView(View):
config.add_route(f'{route_prefix}.configure', config.add_route(f'{route_prefix}.configure',
f'{url_prefix}/configure') f'{url_prefix}/configure')
config.add_view(cls, attr='configure', config.add_view(cls, attr='configure',
route_name=f'{route_prefix}.configure') route_name=f'{route_prefix}.configure',
permission=f'{permission_prefix}.configure')
config.add_wutta_permission(permission_prefix, config.add_wutta_permission(permission_prefix,
f'{permission_prefix}.configure', f'{permission_prefix}.configure',
f"Configure {model_title_plural}") f"Configure {model_title_plural}")

View file

@ -69,6 +69,22 @@ class RoleView(MasterView):
# notes # notes
g.set_renderer('notes', self.grid_render_notes) g.set_renderer('notes', self.grid_render_notes)
def is_editable(self, role):
""" """
session = self.app.get_session(role)
auth = self.app.get_auth_handler()
# only "root" can edit admin role
if role is auth.get_role_administrator(session):
return self.request.is_root
# other built-in roles require special perm
if role in (auth.get_role_authenticated(session),
auth.get_role_anonymous(session)):
return self.has_perm('edit_builtin')
return True
def is_deletable(self, role): def is_deletable(self, role):
""" """ """ """
session = self.app.get_session(role) session = self.app.get_session(role)
@ -228,6 +244,21 @@ class RoleView(MasterView):
else: else:
auth.revoke_permission(role, pkey) auth.revoke_permission(role, pkey)
@classmethod
def defaults(cls, config):
cls._defaults(config)
cls._role_defaults(config)
@classmethod
def _role_defaults(cls, config):
permission_prefix = cls.get_permission_prefix()
model_title_plural = cls.get_model_title_plural()
# perm to edit built-in roles
config.add_wutta_permission(permission_prefix,
f'{permission_prefix}.edit_builtin',
f"Edit the Built-in {model_title_plural}")
def defaults(config, **kwargs): def defaults(config, **kwargs):
base = globals() base = globals()

View file

@ -3,20 +3,15 @@
from unittest import TestCase from unittest import TestCase
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
from wuttjamaican.conf import WuttaConfig
from pyramid import testing
from wuttaweb import menus as mod from wuttaweb import menus as mod
from tests.util import WebTestCase
class TestMenuHandler(TestCase): class TestMenuHandler(WebTestCase):
def setUp(self): def setUp(self):
self.config = WuttaConfig() self.setup_web()
self.app = self.config.get_app()
self.handler = mod.MenuHandler(self.config) self.handler = mod.MenuHandler(self.config)
self.request = testing.DummyRequest()
def test_make_admin_menu(self): def test_make_admin_menu(self):
menus = self.handler.make_admin_menu(self.request) menus = self.handler.make_admin_menu(self.request)
@ -27,7 +22,27 @@ class TestMenuHandler(TestCase):
self.assertIsInstance(menus, list) self.assertIsInstance(menus, list)
def test_is_allowed(self): def test_is_allowed(self):
# TODO: this should test auth/perm handling model = self.app.model
auth = self.app.get_auth_handler()
# user with perms
barney = model.User(username='barney')
self.session.add(barney)
blokes = model.Role(name="Blokes")
self.session.add(blokes)
barney.roles.append(blokes)
auth.grant_permission(blokes, 'appinfo.list')
self.request.user = barney
# perm not granted to user
item = {'perm': 'appinfo.configure'}
self.assertFalse(self.handler._is_allowed(self.request, item))
# perm *is* granted to user
item = {'perm': 'appinfo.list'}
self.assertTrue(self.handler._is_allowed(self.request, item))
# perm not required
item = {} item = {}
self.assertTrue(self.handler._is_allowed(self.request, item)) self.assertTrue(self.handler._is_allowed(self.request, item))

View file

@ -2,7 +2,7 @@
import json import json
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock from unittest.mock import MagicMock, patch
from wuttjamaican.conf import WuttaConfig from wuttjamaican.conf import WuttaConfig
@ -210,6 +210,137 @@ class TestNewRequestSetUser(TestCase):
self.assertTrue(self.request.is_admin) self.assertTrue(self.request.is_admin)
self.assertTrue(self.request.is_root) self.assertTrue(self.request.is_root)
def test_user_permissions(self):
model = self.app.model
auth = self.app.get_auth_handler()
event = MagicMock(request=self.request)
# anonymous user
self.assertFalse(hasattr(self.request, 'user_permissions'))
subscribers.new_request_set_user(event, db_session=self.session)
self.assertEqual(self.request.user_permissions, set())
# reset
del self.request.user_permissions
# add user to role with perms
blokes = model.Role(name="Blokes")
self.session.add(blokes)
auth.grant_permission(blokes, 'appinfo.list')
self.user.roles.append(blokes)
self.session.commit()
# authenticated user, with perms
self.request.user = self.user
subscribers.new_request_set_user(event, db_session=self.session)
self.assertEqual(self.request.user_permissions, {'appinfo.list'})
def test_has_perm(self):
model = self.app.model
auth = self.app.get_auth_handler()
event = MagicMock(request=self.request)
# anonymous user
self.assertFalse(hasattr(self.request, 'has_perm'))
subscribers.new_request_set_user(event, db_session=self.session)
self.assertFalse(self.request.has_perm('appinfo.list'))
# reset
del self.request.user_permissions
del self.request.has_perm
del self.request.has_any_perm
# add user to role with perms
blokes = model.Role(name="Blokes")
self.session.add(blokes)
auth.grant_permission(blokes, 'appinfo.list')
self.user.roles.append(blokes)
self.session.commit()
# authenticated user, with perms
self.request.user = self.user
subscribers.new_request_set_user(event, db_session=self.session)
self.assertTrue(self.request.has_perm('appinfo.list'))
# reset
del self.request.user_permissions
del self.request.has_perm
del self.request.has_any_perm
# drop user from role, no more perms
self.user.roles.remove(blokes)
self.session.commit()
subscribers.new_request_set_user(event, db_session=self.session)
self.assertFalse(self.request.has_perm('appinfo.list'))
# reset
del self.request.user_permissions
del self.request.has_perm
del self.request.has_any_perm
del self.request.is_admin
del self.request.is_root
# root user always has perms
admin = auth.get_role_administrator(self.session)
self.user.roles.append(admin)
self.session.commit()
self.request.session['is_root'] = True
subscribers.new_request_set_user(event, db_session=self.session)
self.assertTrue(self.request.has_perm('appinfo.list'))
def test_has_any_perm(self):
model = self.app.model
auth = self.app.get_auth_handler()
event = MagicMock(request=self.request)
# anonymous user
self.assertFalse(hasattr(self.request, 'has_any_perm'))
subscribers.new_request_set_user(event, db_session=self.session)
self.assertFalse(self.request.has_any_perm('appinfo.list'))
# reset
del self.request.user_permissions
del self.request.has_perm
del self.request.has_any_perm
# add user to role with perms
blokes = model.Role(name="Blokes")
self.session.add(blokes)
auth.grant_permission(blokes, 'appinfo.list')
self.user.roles.append(blokes)
self.session.commit()
# authenticated user, with perms
self.request.user = self.user
subscribers.new_request_set_user(event, db_session=self.session)
self.assertTrue(self.request.has_any_perm('appinfo.list', 'appinfo.view'))
# reset
del self.request.user_permissions
del self.request.has_perm
del self.request.has_any_perm
# drop user from role, no more perms
self.user.roles.remove(blokes)
self.session.commit()
subscribers.new_request_set_user(event, db_session=self.session)
self.assertFalse(self.request.has_any_perm('appinfo.list'))
# reset
del self.request.user_permissions
del self.request.has_perm
del self.request.has_any_perm
del self.request.is_admin
del self.request.is_root
# root user always has perms
admin = auth.get_role_administrator(self.session)
self.user.roles.append(admin)
self.session.commit()
self.request.session['is_root'] = True
subscribers.new_request_set_user(event, db_session=self.session)
self.assertTrue(self.request.has_any_perm('appinfo.list'))
class TestBeforeRender(TestCase): class TestBeforeRender(TestCase):

View file

@ -46,7 +46,7 @@ class WebTestCase(DataTestCase):
def setup_web(self): def setup_web(self):
self.setup_db() self.setup_db()
self.request = testing.DummyRequest() self.request = self.make_request()
self.pyramid_config = testing.setUp(request=self.request, settings={ self.pyramid_config = testing.setUp(request=self.request, settings={
'wutta_config': self.config, 'wutta_config': self.config,
'mako.directories': ['wuttaweb:templates'], 'mako.directories': ['wuttaweb:templates'],
@ -78,6 +78,9 @@ class WebTestCase(DataTestCase):
testing.tearDown() testing.tearDown()
self.teardown_db() self.teardown_db()
def make_request(self):
return testing.DummyRequest()
class NullMenuHandler(MenuHandler): class NullMenuHandler(MenuHandler):
""" """

View file

@ -331,6 +331,64 @@ class TestMasterView(WebTestCase):
# support methods # support methods
############################## ##############################
def test_has_perm(self):
model = self.app.model
auth = self.app.get_auth_handler()
with patch.multiple(master.MasterView, create=True,
model_name='Setting'):
view = self.make_view()
# anonymous user
self.assertFalse(view.has_perm('list'))
self.assertFalse(self.request.has_perm('list'))
# reset
del self.request.user_permissions
# make user with perms
barney = model.User(username='barney')
self.session.add(barney)
blokes = model.Role(name="Blokes")
self.session.add(blokes)
barney.roles.append(blokes)
auth.grant_permission(blokes, 'settings.list')
self.session.commit()
# this user has perms
self.request.user = barney
self.assertTrue(view.has_perm('list'))
self.assertTrue(self.request.has_perm('settings.list'))
def test_has_any_perm(self):
model = self.app.model
auth = self.app.get_auth_handler()
with patch.multiple(master.MasterView, create=True,
model_name='Setting'):
view = self.make_view()
# anonymous user
self.assertFalse(view.has_any_perm('list', 'view'))
self.assertFalse(self.request.has_any_perm('settings.list', 'settings.view'))
# reset
del self.request.user_permissions
# make user with perms
barney = model.User(username='barney')
self.session.add(barney)
blokes = model.Role(name="Blokes")
self.session.add(blokes)
barney.roles.append(blokes)
auth.grant_permission(blokes, 'settings.view')
self.session.commit()
# this user has perms
self.request.user = barney
self.assertTrue(view.has_any_perm('list', 'view'))
self.assertTrue(self.request.has_any_perm('settings.list', 'settings.view'))
def test_render_to_response(self): def test_render_to_response(self):
self.pyramid_config.include('wuttaweb.views.common') self.pyramid_config.include('wuttaweb.views.common')
self.pyramid_config.include('wuttaweb.views.auth') self.pyramid_config.include('wuttaweb.views.auth')
@ -387,6 +445,28 @@ class TestMasterView(WebTestCase):
grid = view.make_model_grid(session=self.session) grid = view.make_model_grid(session=self.session)
self.assertIs(grid.model_class, model.Setting) self.assertIs(grid.model_class, model.Setting)
# no actions by default
with patch.multiple(master.MasterView, create=True,
model_class=model.Setting):
grid = view.make_model_grid(session=self.session)
self.assertEqual(grid.actions, [])
# now let's test some more actions logic
with patch.multiple(master.MasterView, create=True,
model_class=model.Setting,
viewable=True,
editable=True,
deletable=True):
# should have 3 actions now, but for lack of perms
grid = view.make_model_grid(session=self.session)
self.assertEqual(len(grid.actions), 0)
# but root user has perms, so gets 3 actions
with patch.object(self.request, 'is_root', new=True):
grid = view.make_model_grid(session=self.session)
self.assertEqual(len(grid.actions), 3)
def test_get_grid_data(self): def test_get_grid_data(self):
model = self.app.model model = self.app.model
self.app.save_setting(self.session, 'foo', 'bar') self.app.save_setting(self.session, 'foo', 'bar')
@ -468,6 +548,57 @@ class TestMasterView(WebTestCase):
self.request.matchdict = {'name': 'blarg'} self.request.matchdict = {'name': 'blarg'}
self.assertRaises(HTTPNotFound, view.get_instance, session=self.session) self.assertRaises(HTTPNotFound, view.get_instance, session=self.session)
def test_get_action_url_view(self):
model = self.app.model
setting = model.Setting(name='foo', value='bar')
self.session.add(setting)
self.session.commit()
with patch.multiple(master.MasterView, create=True,
model_class=model.Setting):
master.MasterView.defaults(self.pyramid_config)
view = self.make_view()
url = view.get_action_url_view(setting, 0)
self.assertEqual(url, self.request.route_url('settings.view', name='foo'))
def test_get_action_url_edit(self):
model = self.app.model
setting = model.Setting(name='foo', value='bar')
self.session.add(setting)
self.session.commit()
with patch.multiple(master.MasterView, create=True,
model_class=model.Setting):
master.MasterView.defaults(self.pyramid_config)
view = self.make_view()
# typical
url = view.get_action_url_edit(setting, 0)
self.assertEqual(url, self.request.route_url('settings.edit', name='foo'))
# but null if instance not editable
with patch.object(view, 'is_editable', return_value=False):
url = view.get_action_url_edit(setting, 0)
self.assertIsNone(url)
def test_get_action_url_delete(self):
model = self.app.model
setting = model.Setting(name='foo', value='bar')
self.session.add(setting)
self.session.commit()
with patch.multiple(master.MasterView, create=True,
model_class=model.Setting):
master.MasterView.defaults(self.pyramid_config)
view = self.make_view()
# typical
url = view.get_action_url_delete(setting, 0)
self.assertEqual(url, self.request.route_url('settings.delete', name='foo'))
# but null if instance not deletable
with patch.object(view, 'is_deletable', return_value=False):
url = view.get_action_url_delete(setting, 0)
self.assertIsNone(url)
def test_make_model_form(self): def test_make_model_form(self):
model = self.app.model model = self.app.model

View file

@ -31,6 +31,42 @@ class TestRoleView(WebTestCase):
view.configure_grid(grid) view.configure_grid(grid)
self.assertTrue(grid.is_linked('name')) self.assertTrue(grid.is_linked('name'))
def test_is_editable(self):
model = self.app.model
auth = self.app.get_auth_handler()
blokes = model.Role(name="Blokes")
self.session.add(blokes)
self.session.commit()
view = self.make_view()
admin = auth.get_role_administrator(self.session)
authed = auth.get_role_authenticated(self.session)
anon = auth.get_role_anonymous(self.session)
# editable by default
self.assertTrue(view.is_editable(blokes))
# built-in roles not editable by default
self.assertFalse(view.is_editable(admin))
self.assertFalse(view.is_editable(authed))
self.assertFalse(view.is_editable(anon))
# reset
del self.request.user_permissions
barney = model.User(username='barney')
self.session.add(barney)
barney.roles.append(blokes)
auth.grant_permission(blokes, 'roles.edit_builtin')
self.session.commit()
# user with perms can edit *some* built-in
self.request.user = barney
self.assertTrue(view.is_editable(authed))
self.assertTrue(view.is_editable(anon))
# nb. not this one yet
self.assertFalse(view.is_editable(admin))
def test_is_deletable(self): def test_is_deletable(self):
model = self.app.model model = self.app.model
auth = self.app.get_auth_handler() auth = self.app.get_auth_handler()