Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Allow permissions to be set on tables

Sometimes we want to hide certain UI elements depending on the
permissions granted to the current user. At the moment this is
possible for Actions and Panels. This commit extends support to
DataTable too.

We also set the permission on the volume snapshots table so that,
like the volumes panel, it is only displayed when the 'volume'
service is active.

Fixes bug #1087128.

Change-Id: Icc12b479c3eb888320af735b8b7810e58517eef0
  • Loading branch information...
commit eab5fc7712ce9c0b0e226a059f054773426ad6ae 1 parent c4746aa
@conkiztador conkiztador authored
View
7 horizon/tables/base.py
@@ -718,6 +718,11 @@ class DataTableOptions(object):
Boolean to control whether or not to show the table's footer.
Default: ``True``.
+
+ .. attribute:: permissions
+
+ A list of permission names which this table requires in order to be
+ displayed. Defaults to an empty list (``[]``).
"""
def __init__(self, options):
self.name = getattr(options, 'name', self.__class__.__name__)
@@ -736,6 +741,7 @@ def __init__(self, options):
self.no_data_message = getattr(options,
"no_data_message",
_("No items to display."))
+ self.permissions = getattr(options, 'permissions', [])
# Set self.filter if we have any FilterActions
filter_actions = [action for action in self.table_actions if
@@ -895,6 +901,7 @@ def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs):
self._no_data_message = self._meta.no_data_message
self.breadcrumb = None
self.current_item_id = None
+ self.permissions = self._meta.permissions
# Create a new set
columns = []
View
15 horizon/tables/views.py
@@ -18,6 +18,8 @@
from django.views import generic
+from horizon.templatetags.horizon import has_permissions
+
class MultiTableMixin(object):
""" A generic mixin which provides methods for handling DataTables. """
@@ -73,7 +75,7 @@ def check_method_exist(self, func_pattern="%s", *names):
func = getattr(self, func_name, None)
if not func or not callable(func):
cls_name = self.__class__.__name__
- raise NotImplementedError("You must define a %s method"
+ raise NotImplementedError("You must define a %s method "
"in %s." % (func_name, cls_name))
else:
return func
@@ -89,6 +91,9 @@ def get_tables(self):
'on %s.' % self.__class__.__name__)
if not self._tables:
for table in self.table_classes:
+ if not has_permissions(self.request.user,
+ table._meta):
+ continue
func_name = "get_%s_table" % table._meta.name
table_func = getattr(self, func_name, None)
if table_func is None:
@@ -183,7 +188,10 @@ def get_data(self):
def get_tables(self):
if not self._tables:
- self._tables = {self.table_class._meta.name: self.get_table()}
+ self._tables = {}
+ if has_permissions(self.request.user,
+ self.table_class._meta):
+ self._tables[self.table_class._meta.name] = self.get_table()
return self._tables
def get_table(self):
@@ -197,7 +205,8 @@ def get_table(self):
def get_context_data(self, **kwargs):
context = super(DataTableView, self).get_context_data(**kwargs)
- context[self.context_object_name] = self.table
+ if hasattr(self, "table"):
+ context[self.context_object_name] = self.table
return context
View
77 horizon/test/tests/tables.py
@@ -22,6 +22,7 @@
from mox import IsA
from horizon import tables
+from horizon.tables import views as table_views
from horizon.test import helpers as test
@@ -676,3 +677,79 @@ def test_table_action_object_display_is_none(self):
self.assertEqual(handled["location"], "/my_url/")
self.assertEqual(list(req._messages)[0].message,
u"Downed Item: N/A")
+
+
+class SingleTableView(table_views.DataTableView):
+ table_class = MyTable
+ name = _("Single Table")
+ slug = "single"
+ template_name = "horizon/common/_detail_table.html"
+
+ def get_data(self):
+ return TEST_DATA
+
+
+class TableWithPermissions(tables.DataTable):
+ id = tables.Column('id')
+
+ class Meta:
+ name = "table_with_permissions"
+ permissions = ('horizon.test',)
+
+
+class SingleTableViewWithPermissions(SingleTableView):
+ table_class = TableWithPermissions
+
+
+class MultiTableView(tables.MultiTableView):
+ table_classes = (TableWithPermissions, MyTable)
+
+ def get_table_with_permissions_data(self):
+ return TEST_DATA
+
+ def get_my_table_data(self):
+ return TEST_DATA
+
+
+class DataTableViewTests(test.TestCase):
+ def _prepare_view(self, cls, *args, **kwargs):
+ req = self.factory.get('/my_url/')
+ req.user = self.user
+ view = cls()
+ view.request = req
+ view.args = args
+ view.kwargs = kwargs
+ return view
+
+ def test_data_table_view(self):
+ view = self._prepare_view(SingleTableView)
+ context = view.get_context_data()
+ self.assertEqual(context['table'].__class__,
+ SingleTableView.table_class)
+
+ def test_data_table_view_not_authorized(self):
+ view = self._prepare_view(SingleTableViewWithPermissions)
+ context = view.get_context_data()
+ self.assertNotIn('table', context)
+
+ def test_data_table_view_authorized(self):
+ view = self._prepare_view(SingleTableViewWithPermissions)
+ self.set_permissions(permissions=['test'])
+ context = view.get_context_data()
+ self.assertIn('table', context)
+ self.assertEqual(context['table'].__class__,
+ SingleTableViewWithPermissions.table_class)
+
+ def test_multi_table_view_not_authorized(self):
+ view = self._prepare_view(MultiTableView)
+ context = view.get_context_data()
+ self.assertEqual(context['my_table_table'].__class__, MyTable)
+ self.assertNotIn('table_with_permissions_table', context)
+
+ def test_multi_table_view_authorized(self):
+ view = self._prepare_view(MultiTableView)
+ self.set_permissions(permissions=['test'])
+ context = view.get_context_data()
+ self.assertEqual(context['my_table_table'].__class__, MyTable)
+ self.assertEqual(context['table_with_permissions_table'].__class__,
+ TableWithPermissions)
View
1  openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tables.py
@@ -89,3 +89,4 @@ class Meta:
row_actions = (CreateVolumeFromSnapshot, DeleteVolumeSnapshot)
row_class = UpdateRow
status_columns = ("status",)
+ permissions = ['openstack.services.volume']
Please sign in to comment.
Something went wrong with that request. Please try again.