diff --git a/doc/source/topics/tables.rst b/doc/source/topics/tables.rst index b200096568..2b8a188134 100644 --- a/doc/source/topics/tables.rst +++ b/doc/source/topics/tables.rst @@ -114,7 +114,10 @@ all of the boilerplate associated with writing these types of actions and provides consistent error handling, logging, and user-facing interaction. It is worth noting that ``BatchAction`` and ``DeleteAction`` are extensions -of the standard ``Action`` class. +of the standard ``Action`` class. Some ``BatchAction`` or ``DeleteAction`` +classes may cause some unrecoverable results, like deleted images or +unrecoverable instances. It may be helpful to specify specific help_text to +explain the concern to the user, such as "Deleted images are not recoverable". Preemptive actions ------------------ diff --git a/horizon/static/horizon/js/horizon.tables.js b/horizon/static/horizon/js/horizon.tables.js index 23732e4327..cd93f8282d 100644 --- a/horizon/static/horizon/js/horizon.tables.js +++ b/horizon/static/horizon/js/horizon.tables.js @@ -174,6 +174,7 @@ horizon.datatables.confirm = function (action) { $modal_parent = $(action).closest('.modal'), name_array = [], closest_table_id, action_string, name_string, + help_text, title, body, modal, form; if($action.hasClass("disabled")) { return; @@ -198,8 +199,9 @@ horizon.datatables.confirm = function (action) { } name_string = interpolate(gettext("You have selected %s. "), [name_string]); } + help_text = $(action).attr("help_text"); title = interpolate(gettext("Confirm %s"), [action_string]); - body = name_string + gettext("Please confirm your selection. This action cannot be undone."); + body = name_string + gettext("Please confirm your selection. ") + help_text; modal = horizon.modals.create(title, body, action_string); modal.modal(); if($modal_parent.length) { diff --git a/horizon/tables/actions.py b/horizon/tables/actions.py index d231d8136a..80a9b8301f 100644 --- a/horizon/tables/actions.py +++ b/horizon/tables/actions.py @@ -635,8 +635,15 @@ class BatchAction(Action): Optional location to redirect after completion of the delete action. Defaults to the current page. + + .. attribute:: help_text + + Optional message for providing an appropriate help text for + the horizon user. """ + help_text = _("This action cannot be undone.") + def __init__(self, **kwargs): super(BatchAction, self).__init__(**kwargs) @@ -696,6 +703,8 @@ class BatchAction(Action): # Keep record of successfully handled objects self.success_ids = [] + self.help_text = kwargs.get('help_text', self.help_text) + def _allowed(self, request, datum=None): # Override the default internal action method to prevent batch # actions from appearing on tables with no data. diff --git a/horizon/templates/horizon/common/_data_table_row_action_dropdown.html b/horizon/templates/horizon/common/_data_table_row_action_dropdown.html index d766fb3151..aee336f6b4 100644 --- a/horizon/templates/horizon/common/_data_table_row_action_dropdown.html +++ b/horizon/templates/horizon/common/_data_table_row_action_dropdown.html @@ -1,5 +1,5 @@ {% if action.method != "GET" %} - + {% else %} {{ action.verbose_name }} {% endif %} diff --git a/horizon/templates/horizon/common/_data_table_table_action.html b/horizon/templates/horizon/common/_data_table_table_action.html index 9830cc60e0..b0e819e2ef 100644 --- a/horizon/templates/horizon/common/_data_table_table_action.html +++ b/horizon/templates/horizon/common/_data_table_table_action.html @@ -1,5 +1,5 @@ {% if action.method != "GET" %} - diff --git a/horizon/test/tests/tables.py b/horizon/test/tests/tables.py index 7d869a882f..f3e3e684b2 100644 --- a/horizon/test/tests/tables.py +++ b/horizon/test/tests/tables.py @@ -132,6 +132,13 @@ class MyBatchAction(tables.BatchAction): pass +class MyBatchActionWithHelpText(MyBatchAction): + name = "batch_help" + help_text = "this is help." + action_present = "BatchHelp" + action_past = "BatchedHelp" + + class MyToggleAction(tables.BatchAction): name = "toggle" action_present = ("Down", "Up") @@ -237,8 +244,10 @@ class MyTable(tables.DataTable): columns = ('id', 'name', 'value', 'optional', 'status') row_class = MyRow column_class = MyColumn - table_actions = (MyFilterAction, MyAction, MyBatchAction) - row_actions = (MyAction, MyLinkAction, MyBatchAction, MyToggleAction) + table_actions = (MyFilterAction, MyAction, MyBatchAction, + MyBatchActionWithHelpText) + row_actions = (MyAction, MyLinkAction, MyBatchAction, MyToggleAction, + MyBatchActionWithHelpText) class MyServerFilterTable(MyTable): @@ -250,7 +259,8 @@ class MyServerFilterTable(MyTable): row_class = MyRow column_class = MyColumn table_actions = (MyServerFilterAction, MyAction, MyBatchAction) - row_actions = (MyAction, MyLinkAction, MyBatchAction, MyToggleAction) + row_actions = (MyAction, MyLinkAction, MyBatchAction, MyToggleAction, + MyBatchActionWithHelpText) class MyTableSelectable(MyTable): @@ -338,6 +348,7 @@ class DataTableTests(test.TestCase): # Actions (these also test ordering) self.assertQuerysetEqual(self.table.base_actions.values(), ['', + '', '', '', '', @@ -345,12 +356,14 @@ class DataTableTests(test.TestCase): self.assertQuerysetEqual(self.table.get_table_actions(), ['', '', - '']) + '', + '']) self.assertQuerysetEqual(self.table.get_row_actions(TEST_DATA[0]), ['', '', '', - '']) + '', + '']) # Auto-generated columns multi_select = self.table.columns['multi_select'] self.assertEqual("multi_select", multi_select.auto) @@ -561,15 +574,30 @@ class DataTableTests(test.TestCase): self.assertContains(resp, "my_table__filter__q", 1) self.assertContains(resp, "my_table__delete", 1) self.assertContains(resp, 'id="my_table__action_delete"', 1) + + # Table BatchActions + self.assertContains(resp, 'id="my_table__action_batch_help"', 1) + self.assertContains(resp, 'help_text="this is help."', 1) + self.assertContains(resp, 'BatchHelp Item', 1) + # Row actions row_actions = self.table.render_row_actions(TEST_DATA[0]) resp = http.HttpResponse(row_actions) - self.assertContains(resp, "', '', - '']) + '', + '']) # Zero objects in table # BatchAction not available diff --git a/openstack_dashboard/dashboards/project/images/images/tables.py b/openstack_dashboard/dashboards/project/images/images/tables.py index 3986daaa74..6218e26329 100644 --- a/openstack_dashboard/dashboards/project/images/images/tables.py +++ b/openstack_dashboard/dashboards/project/images/images/tables.py @@ -57,6 +57,10 @@ class LaunchImage(tables.LinkAction): class DeleteImage(tables.DeleteAction): + # NOTE: The bp/add-batchactions-help-text + # will add appropriate help text to some batch/delete actions. + help_text = _("Deleted images are not recoverable.") + @staticmethod def action_present(count): return ungettext_lazy( diff --git a/openstack_dashboard/dashboards/project/images/tests.py b/openstack_dashboard/dashboards/project/images/tests.py index 29b97c8f5f..8dd9223614 100644 --- a/openstack_dashboard/dashboards/project/images/tests.py +++ b/openstack_dashboard/dashboards/project/images/tests.py @@ -46,6 +46,8 @@ class ImagesAndSnapshotsTests(test.TestCase): res = self.client.get(INDEX_URL) self.assertTemplateUsed(res, 'project/images/index.html') + self.assertContains(res, 'help_text="Deleted images' + ' are not recoverable."') self.assertIn('images_table', res.context) images_table = res.context['images_table'] images = images_table.data