Make table BatchAction help text configurable

Now the BatchAction help text is consistent.
This patch make the BatchAction/DeleteAction help text configurable,
so horizon user can understand the BatchAction (mostly dangerous)
more clearly.

Implements blueprint: make-batchaction-help-text-configurable

Anoter blueprint: add-batchactions-help-text do the "add
appropriate help text" work.

Change-Id: I08c219cf0b918a28da60ca74830a1e17f5453a2f
This commit is contained in:
LIU-Yulong 2014-11-07 22:04:30 +08:00 committed by LIU Yulong
parent 4eb1c20951
commit a65258f77e
8 changed files with 68 additions and 13 deletions

View File

@ -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
------------------

View File

@ -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) {

View File

@ -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.

View File

@ -1,5 +1,5 @@
{% if action.method != "GET" %}
<button {{ action.attr_string|safe }} name="action" value="{{ action.table.name }}__{{ action.name }}__{{ row_id }}" type="submit">{{ action.verbose_name }}</button>
<button {{ action.attr_string|safe }} {% if action.help_text %}help_text="{{ action.help_text }}"{% endif %} name="action" value="{{ action.table.name }}__{{ action.name }}__{{ row_id }}" type="submit">{{ action.verbose_name }}</button>
{% else %}
<a href='{{ action.bound_url }}' {{ action.attr_string|safe }}>{{ action.verbose_name }}</a>
{% endif %}

View File

@ -1,5 +1,5 @@
{% if action.method != "GET" %}
<button {{ action.attr_string|safe }} name="action" value="{{ action.get_param_name }}" type="submit">
<button {{ action.attr_string|safe }} name="action" value="{{ action.get_param_name }}" {% if action.help_text %}help_text="{{ action.help_text }}"{% endif %} type="submit">
{% if action.icon != None %}<span class="fa fa-{{ action.icon }}"></span> {% endif %}
{% if action.handles_multiple %}{{ action.verbose_name_plural }}{% else %}{{ action.verbose_name }}{% endif %}
</button>

View File

@ -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(),
['<MyBatchAction: batch>',
'<MyBatchActionWithHelpText: batch_help>',
'<MyAction: delete>',
'<MyFilterAction: filter>',
'<MyLinkAction: login>',
@ -345,12 +356,14 @@ class DataTableTests(test.TestCase):
self.assertQuerysetEqual(self.table.get_table_actions(),
['<MyFilterAction: filter>',
'<MyAction: delete>',
'<MyBatchAction: batch>'])
'<MyBatchAction: batch>',
'<MyBatchActionWithHelpText: batch_help>'])
self.assertQuerysetEqual(self.table.get_row_actions(TEST_DATA[0]),
['<MyAction: delete>',
'<MyLinkAction: login>',
'<MyBatchAction: batch>',
'<MyToggleAction: toggle>'])
'<MyToggleAction: toggle>',
'<MyBatchActionWithHelpText: batch_help>'])
# 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, "<li", 3)
self.assertContains(resp, "<li", 4)
self.assertContains(resp, "my_table__delete__1", 1)
self.assertContains(resp, "my_table__toggle__1", 1)
self.assertContains(resp, "/auth/login/", 1)
self.assertContains(resp, "ajax-modal", 1)
self.assertContains(resp, 'id="my_table__row_1__action_delete"', 1)
# Row BatchActions
row_actions = self.table.render_row_actions(TEST_DATA[0])
resp = http.HttpResponse(row_actions)
self.assertContains(resp, 'id="my_table__row_1__action_batch_help"', 1)
self.assertContains(resp, 'help_text="this is help."', 1)
self.assertContains(resp, 'value="my_table__batch_help__1"', 1)
self.assertContains(resp, 'BatchHelp Item', 1)
# Whole table
resp = http.HttpResponse(self.table.render())
self.assertContains(resp, '<table id="my_table"', 1)
@ -802,11 +830,17 @@ class DataTableTests(test.TestCase):
toggle_action = self.table.get_row_actions(TEST_DATA_3[0])[2]
self.assertEqual("Batch Item", unicode(toggle_action.verbose_name))
# Batch action with custom help text
req = self.factory.get('/my_url/')
self.table = MyTable(req, TEST_DATA_3)
toggle_action = self.table.get_row_actions(TEST_DATA_3[0])[4]
self.assertEqual("BatchHelp Item", unicode(toggle_action.verbose_name))
# Single object toggle action
# GET page - 'up' to 'down'
req = self.factory.get('/my_url/')
self.table = MyTable(req, TEST_DATA_3)
self.assertEqual(4, len(self.table.get_row_actions(TEST_DATA_3[0])))
self.assertEqual(5, len(self.table.get_row_actions(TEST_DATA_3[0])))
toggle_action = self.table.get_row_actions(TEST_DATA_3[0])[3]
self.assertEqual("Down Item", unicode(toggle_action.verbose_name))
@ -827,7 +861,7 @@ class DataTableTests(test.TestCase):
# GET page - 'down' to 'up'
req = self.factory.get('/my_url/')
self.table = MyTable(req, TEST_DATA_2)
self.assertEqual(3, len(self.table.get_row_actions(TEST_DATA_2[0])))
self.assertEqual(4, len(self.table.get_row_actions(TEST_DATA_2[0])))
toggle_action = self.table.get_row_actions(TEST_DATA_2[0])[2]
self.assertEqual("Up Item", unicode(toggle_action.verbose_name))
@ -883,7 +917,8 @@ class DataTableTests(test.TestCase):
self.assertQuerysetEqual(self.table.get_table_actions(),
['<MyFilterAction: filter>',
'<MyAction: delete>',
'<MyBatchAction: batch>'])
'<MyBatchAction: batch>',
'<MyBatchActionWithHelpText: batch_help>'])
# Zero objects in table
# BatchAction not available

View File

@ -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(

View File

@ -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