Merge "DataTable column level policy"

This commit is contained in:
Jenkins 2016-12-08 21:23:31 +00:00 committed by Gerrit Code Review
commit 0744e8e594
2 changed files with 58 additions and 4 deletions

View File

@ -236,6 +236,22 @@ class Column(html.HTMLElement):
``link_attrs={"target": "_blank", "class": "link-foo link-bar"}``.
Defaults to ``None``.
.. attribute:: policy_rules
List of scope and rule tuples to do policy checks on, the
composition of which is (scope, rule)
scope: service type managing the policy for action
rule: string representing the action to be checked
for a policy that requires a single rule check:
policy_rules should look like
"(("compute", "compute:create_instance"),)"
for a policy that requires multiple rule checks:
rules should look like
"(("identity", "identity:list_users"),
("identity", "identity:list_roles"))"
.. attribute:: help_text
A string of simple help text displayed in a tooltip when you hover
@ -275,7 +291,7 @@ class Column(html.HTMLElement):
empty_value=None, filters=None, classes=None, summation=None,
auto=None, truncate=None, link_classes=None, wrap_list=False,
form_field=None, form_field_attributes=None,
update_action=None, link_attrs=None,
update_action=None, link_attrs=None, policy_rules=None,
cell_attributes_getter=None, help_text=None):
allowed_data_types = allowed_data_types or []
@ -313,6 +329,7 @@ class Column(html.HTMLElement):
self.form_field_attributes = form_field_attributes or {}
self.update_action = update_action
self.link_attrs = link_attrs or {}
self.policy_rules = policy_rules or []
self.help_text = help_text
if link_classes:
self.link_attrs['class'] = ' '.join(link_classes)
@ -345,6 +362,19 @@ class Column(html.HTMLElement):
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.name)
def allowed(self, request):
"""Determine whether processing/displaying the column is allowed
for the current request.
"""
if not self.policy_rules:
return True
policy_check = getattr(settings, "POLICY_CHECK_FUNCTION", None)
if policy_check:
return policy_check(self.policy_rules, request)
return True
def get_raw_data(self, datum):
"""Returns the raw data for this column, before any filters or
formatting are applied to it. This is useful when doing calculations
@ -1220,9 +1250,10 @@ class DataTable(object):
# Create a new set
columns = []
for key, _column in self._columns.items():
column = copy.copy(_column)
column.table = self
columns.append((key, column))
if _column.allowed(request):
column = copy.copy(_column)
column.table = self
columns.append((key, column))
self.columns = collections.OrderedDict(columns)
self._populate_data_cache()

View File

@ -20,6 +20,7 @@ from django import forms
from django import http
from django import shortcuts
from django.template import defaultfilters
from django.test.utils import override_settings
from django.utils.translation import ungettext_lazy
from mox3.mox import IsA # noqa
@ -306,6 +307,12 @@ class MyTable(tables.DataTable):
MyBatchActionWithHelpText)
class TableWithColumnsPolicy(tables.DataTable):
name = tables.Column('name')
restricted = tables.Column('restricted',
policy_rules=[('compute', 'role:admin')])
class MyServerFilterTable(MyTable):
class Meta(object):
name = "my_table"
@ -435,6 +442,22 @@ class DataTableTests(test.TestCase):
self.assertEqual(forms.CharField, name_column.form_field.__class__)
self.assertEqual({'class': 'test'}, name_column.form_field_attributes)
@override_settings(POLICY_CHECK_FUNCTION=lambda *args: False)
def test_table_column_policy_not_allowed(self):
self.table = TableWithColumnsPolicy(self.request, TEST_DATA)
self.assertEqual(TEST_DATA, self.table.data)
# The column "restricted" is not rendered because of policy
expected_columns = ['<Column: name>']
self.assertQuerysetEqual(self.table.columns.values(), expected_columns)
@override_settings(POLICY_CHECK_FUNCTION=lambda *args: True)
def test_table_column_policy_allowed(self):
self.table = TableWithColumnsPolicy(self.request, TEST_DATA)
self.assertEqual(TEST_DATA, self.table.data)
# Policy check returns True so the column "restricted" is rendered
expected_columns = ['<Column: name>', '<Column: restricted>']
self.assertQuerysetEqual(self.table.columns.values(), expected_columns)
def test_table_force_no_multiselect(self):
class TempTable(MyTable):
class Meta(object):