Add support for "local only" Person, User, plus related security

also add "view / edit roles for user" permissions
This commit is contained in:
Lance Edgar 2019-10-04 22:31:19 -05:00
parent 4d1fa4f2d6
commit 47669a23bc
2 changed files with 138 additions and 24 deletions

View file

@ -98,6 +98,10 @@ class MasterView(View):
supports_prev_next = False
supports_import_batch_from_file = False
# set to True to add "View *global* Objects" permission, and
# expose / leverage the ``local_only`` object flag
secure_global_objects = False
# quickie (search)
supports_quickie_search = False
expose_quickie_search = False
@ -272,6 +276,16 @@ class MasterView(View):
labels.update(cls.row_labels)
return labels
def has_perm(self, name):
"""
Convenience function which returns boolean which should indicate
whether the current user has been granted the named permission. Note
that this method actually assembles the permission name, using the
``name`` provided, but also :meth:`get_permission_prefix()`.
"""
return self.request.has_perm('{}.{}'.format(
self.get_permission_prefix(), name))
##############################
# Available Views
##############################
@ -390,8 +404,16 @@ class MasterView(View):
return defaults
def configure_grid(self, grid):
"""
Perform "final" configuration for the main data grid.
"""
self.set_labels(grid)
# hide "local only" grid filter, unless global access allowed
if self.secure_global_objects:
if not self.has_perm('view_global'):
grid.remove_filter('local_only')
def grid_extra_class(self, obj, i):
"""
Returns string of extra class(es) for the table row corresponding to
@ -1362,6 +1384,11 @@ class MasterView(View):
self.set_labels(form)
# hide "local only" field, unless global access allowed
if self.secure_global_objects:
if not self.has_perm('view_global'):
form.remove_field('local_only')
def configure_mobile_form(self, form):
"""
Configure the main "mobile" form for the view's data model.
@ -2542,7 +2569,15 @@ class MasterView(View):
users. You would modify the base query to hide what you wanted,
regardless of the user's filter selections.
"""
return session.query(self.get_model_class())
model_class = self.get_model_class()
query = session.query(model_class)
# only show "local only" objects, unless global access allowed
if self.secure_global_objects:
if not self.has_perm('view_global'):
query = query.filter(model_class.local_only == True)
return query
def get_effective_query(self, session=None, **kwargs):
return self.get_effective_data(session=session, **kwargs)
@ -2802,7 +2837,6 @@ class MasterView(View):
obj = self.Session.query(self.get_model_class()).get(key)
if not obj:
raise self.notfound()
return obj
else: # composite key; fetch accordingly
# TODO: should perhaps use filter() instead of get() here?
@ -2814,7 +2848,14 @@ class MasterView(View):
obj = query.one()
except NoResultFound:
raise self.notfound()
return obj
# pretend global object doesn't exist, unless access allowed
if self.secure_global_objects:
if not obj.local_only:
if not self.has_perm('view_global'):
raise self.notfound()
return obj
def get_instance_title(self, instance):
"""
@ -2966,11 +3007,26 @@ class MasterView(View):
return False
def objectify(self, form, data=None):
"""
Create and/or update the model instance from the given form, and return
this object.
.. todo::
This needs a better explanation. And probably tests.
"""
if data is None:
data = form.validated
obj = form.schema.objectify(data, context=form.model_instance)
if self.is_contact:
obj = self.objectify_contact(obj, data)
# force "local only" flag unless global access granted
if self.secure_global_objects:
if not self.has_perm('view_global'):
obj.local_only = True
return obj
def objectify_contact(self, contact, data):
@ -3598,6 +3654,9 @@ class MasterView(View):
if cls.has_pk_fields:
config.add_tailbone_permission(permission_prefix, '{}.view_pk_fields'.format(permission_prefix),
"View all PK-type fields for {}".format(model_title_plural))
if cls.secure_global_objects:
config.add_tailbone_permission(permission_prefix, '{}.view_global'.format(permission_prefix),
"View *global* {}".format(model_title_plural))
# view by grid index
config.add_route('{}.view_index'.format(route_prefix), '{}/view'.format(url_prefix))