Table checkbox display problem when updating row

Multi-process:
  If there only one table action, DeleteAction.
  Assume the first time retrieving an empty table in one
  process, the checkbox is hidden.
  In the other process, creating a item, like a instance.
  Now, the checkbox is shown in second process.
  Then updating the item state in first process, the checkbox
  will be hidden. In second process, the checkbox will be shown.

  For above case, we need set the checkbox visibility every time
  when updating single row by ajax.

Change-Id: Ib230ad775070ee089c8f82e7df3ec30c1cda7aa0
Closes-Bug: #1799151
This commit is contained in:
wangliangyu 2018-10-22 17:59:05 +08:00 committed by Akihiro Motoki
parent 673bf049a0
commit 3ac9037677
5 changed files with 42 additions and 14 deletions

View File

@ -687,7 +687,10 @@ class BatchAction(Action):
def _allowed(self, request, datum=None):
# Override the default internal action method to prevent batch
# actions from appearing on tables with no data.
if not self.table.data and not datum:
# Updating single row of table by ajax prove that there is one
# data at least.
action = request.GET.get('action')
if action != 'row_update' and not self.table.data and not datum:
return False
return super(BatchAction, self)._allowed(request, datum)

View File

@ -1105,6 +1105,8 @@ class DataTableOptions(object):
# Set self.filter if we have any FilterActions
filter_actions = [action for action in self.table_actions if
issubclass(action, FilterAction)]
batch_actions = [action for action in self.table_actions if
issubclass(action, BatchAction)]
if len(filter_actions) > 1:
raise NotImplementedError("Multiple filter actions are not "
"currently supported.")
@ -1137,7 +1139,7 @@ class DataTableOptions(object):
len(self.row_actions) > 0)
self.multi_select = getattr(options,
'multi_select',
len(self.table_actions) > 0)
len(batch_actions) > 0)
# Set runtime table defaults; not configurable.
self.has_prev_data = False
@ -1301,6 +1303,16 @@ class DataTable(object):
self.needs_summary_row = any([col.summation
for col in self.columns.values()])
# For multi-process, we need to set the multi_column to be visible
# or hidden each time.
# Example: first process the multi_column visible but second
# process the column is hidden. Updating row by ajax will
# make the bug#1799151
if request.GET.get('action') == 'row_update':
bound_actions = self.get_table_actions()
batch_actions = [action for action in bound_actions
if isinstance(action, BatchAction)]
self.set_multiselect_column_visibility(bool(batch_actions))
def __str__(self):
return six.text_type(self._meta.verbose_name)
@ -1570,7 +1582,7 @@ class DataTable(object):
if self._meta.table_actions_menu_label:
extra_context['table_actions_menu_label'] = \
self._meta.table_actions_menu_label
self.set_multiselect_column_visibility(len(batch_actions) > 0)
self.set_multiselect_column_visibility(bool(batch_actions))
return table_actions_template.render(extra_context, self.request)
def render_row_actions(self, datum, row=False):

View File

@ -500,7 +500,7 @@ class DataTableTests(test.TestCase):
class TempTable(MyTable):
class Meta(object):
columns = ('id',)
table_actions = (MyFilterAction, MyAction,)
table_actions = (MyFilterAction, MyAction, MyBatchAction)
row_actions = (MyAction, MyLinkAction,)
actions_column = False
self.table = TempTable(self.request, TEST_DATA)
@ -528,7 +528,7 @@ class DataTableTests(test.TestCase):
class TempTable(MyTable):
class Meta(object):
columns = ('id',)
table_actions = (MyFilterAction, MyAction,)
table_actions = (MyFilterAction, MyAction, MyBatchAction)
self.table = TempTable(self.request, TEST_DATA)
self.assertQuerysetEqual(self.table.columns.values(),
['<Column: multi_select>',
@ -550,7 +550,7 @@ class DataTableTests(test.TestCase):
class Meta(object):
name = "temp_table"
table_actions = (MyFilterAction, MyAction,)
table_actions = (MyFilterAction, MyAction, MyBatchAction)
row_actions = (MyAction, MyLinkAction,)
self.table = TempTable(self.request, TEST_DATA)

View File

@ -5181,7 +5181,8 @@ class InstanceAjaxTests(helpers.TestCase, InstanceTestHelperMixin):
@helpers.create_mocks({api.nova: ("server_get",
"flavor_get",
"extension_supported",
"is_feature_available"),
"is_feature_available",
"tenant_absolute_limits"),
api.network: ('servers_update_addresses',)})
def test_row_update(self):
server = self.servers.first()
@ -5220,7 +5221,8 @@ class InstanceAjaxTests(helpers.TestCase, InstanceTestHelperMixin):
@helpers.create_mocks({api.nova: ("server_get",
"flavor_get",
'is_feature_available',
"extension_supported"),
"extension_supported",
"tenant_absolute_limits"),
api.network: ('servers_update_addresses',)})
def test_row_update_instance_error(self):
server = self.servers.first()
@ -5280,7 +5282,8 @@ class InstanceAjaxTests(helpers.TestCase, InstanceTestHelperMixin):
@helpers.create_mocks({api.nova: ("server_get",
"flavor_get",
'is_feature_available',
"extension_supported"),
"extension_supported",
"tenant_absolute_limits"),
api.network: ('servers_update_addresses',)})
def test_row_update_flavor_not_found(self):
server = self.servers.first()

View File

@ -1137,7 +1137,9 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.assertEqual(res.status_code, 200)
mock_get.assert_called_once_with(test.IsHttpRequest(), volume.id)
mock_limits.assert_called_once()
self.assert_mock_multiple_calls_with_same_arguments(
mock_limits, 2,
mock.call(test.IsHttpRequest()))
self.assertNotContains(res, 'Delete Volume')
self.assertNotContains(res, 'delete')
@ -1275,7 +1277,9 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
content)
self.assertNotIn('disabled', content)
mock_get.assert_called_once_with(test.IsHttpRequest(), volume.id)
mock_limits.assert_called_once()
self.assert_mock_multiple_calls_with_same_arguments(
mock_limits, 2,
mock.call(test.IsHttpRequest()))
@mock.patch.object(cinder, 'tenant_absolute_limits')
@mock.patch.object(cinder, 'volume_get')
@ -1306,7 +1310,9 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.assertIn('disabled', content,
'The create snapshot button should be disabled')
mock_get.assert_called_once_with(test.IsHttpRequest(), volume.id)
mock_limits.assert_called_once()
self.assert_mock_multiple_calls_with_same_arguments(
mock_limits, 2,
mock.call(test.IsHttpRequest()))
@test.create_mocks({
api.nova: ['server_list'],
@ -1504,7 +1510,9 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.assertEqual(volume.name, volume.id)
mock_get.assert_called_once_with(test.IsHttpRequest(), volume.id)
mock_limits.assert_called_once()
self.assert_mock_multiple_calls_with_same_arguments(
mock_limits, 2,
mock.call(test.IsHttpRequest()))
@test.create_mocks({
api.nova: ['server_get'],
@ -1748,7 +1756,9 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.assertContains(res, 'retype')
mock_get.assert_called_once_with(test.IsHttpRequest(), volume.id)
mock_limits.assert_called_once()
self.assert_mock_multiple_calls_with_same_arguments(
mock_limits, 2,
mock.call(test.IsHttpRequest()))
@test.create_mocks({
cinder: ['volume_type_list',