Fix actions inside instance details view page

At the current moment user can not suspend an
instance from the instance "Details" page if the
instance does not belong to the first page of the
instances list. This is fixed.

Co-Authored-By: Ivan Kolodyazhny <e0ne@e0ne.info>
Co-Authored-By: Vladislav Kuzmin <vkuzmin@mirantis.com>
Change-Id: I4d805e4a65e838242af38677cbb9efefc498a96f
Closes-Bug: #1553142
This commit is contained in:
Dmitry Ratushyy 2016-03-08 17:03:32 +03:00 committed by Vladislav Kuzmin
parent d616b41000
commit 9e1eb5f5d7
7 changed files with 149 additions and 9 deletions

View File

@ -14,6 +14,7 @@
import collections import collections
import copy import copy
import inspect
import json import json
import logging import logging
from operator import attrgetter from operator import attrgetter
@ -466,6 +467,8 @@ class Column(html.HTMLElement):
return None return None
obj_id = self.table.get_object_id(datum) obj_id = self.table.get_object_id(datum)
if callable(self.link): if callable(self.link):
if 'request' in inspect.getargspec(self.link).args:
return self.link(datum, request=self.table.request)
return self.link(datum) return self.link(datum)
try: try:
return urlresolvers.reverse(self.link, args=(obj_id,)) return urlresolvers.reverse(self.link, args=(obj_id,))
@ -670,11 +673,19 @@ class Row(html.HTMLElement):
def get_ajax_update_url(self): def get_ajax_update_url(self):
table_url = self.table.get_absolute_url() table_url = self.table.get_absolute_url()
params = urlencode(collections.OrderedDict([ marker_name = self.table._meta.pagination_param
marker = self.table.request.GET.get(marker_name, None)
if not marker:
marker_name = self.table._meta.prev_pagination_param
marker = self.table.request.GET.get(marker_name, None)
request_params = [
("action", self.ajax_action_name), ("action", self.ajax_action_name),
("table", self.table.name), ("table", self.table.name),
("obj_id", self.table.get_object_id(self.datum)) ("obj_id", self.table.get_object_id(self.datum)),
])) ]
if marker:
request_params.append((marker_name, marker))
params = urlencode(collections.OrderedDict(request_params))
return "%s?%s" % (table_url, params) return "%s?%s" % (table_url, params)
def can_be_selected(self, datum): def can_be_selected(self, datum):

View File

@ -26,6 +26,7 @@ from openstack_dashboard.dashboards.project.instances import audit_tables
from openstack_dashboard.dashboards.project.instances \ from openstack_dashboard.dashboards.project.instances \
import tables as project_tables import tables as project_tables
from openstack_dashboard import policy from openstack_dashboard import policy
from openstack_dashboard.views import get_url_with_pagination
class AdminEditInstance(project_tables.EditInstance): class AdminEditInstance(project_tables.EditInstance):
@ -109,6 +110,13 @@ class AdminInstanceFilterAction(tables.FilterAction):
) + project_tables.INSTANCE_FILTER_CHOICES ) + project_tables.INSTANCE_FILTER_CHOICES
def get_server_detail_link(obj, request):
return get_url_with_pagination(
request, AdminInstancesTable._meta.pagination_param,
AdminInstancesTable._meta.prev_pagination_param,
"horizon:admin:instances:detail", obj.id)
class AdminInstancesTable(tables.DataTable): class AdminInstancesTable(tables.DataTable):
TASK_STATUS_CHOICES = ( TASK_STATUS_CHOICES = (
(None, True), (None, True),
@ -134,7 +142,7 @@ class AdminInstancesTable(tables.DataTable):
verbose_name=_("Host"), verbose_name=_("Host"),
classes=('nowrap-col',)) classes=('nowrap-col',))
name = tables.WrappingColumn("name", name = tables.WrappingColumn("name",
link="horizon:admin:instances:detail", link=get_server_detail_link,
verbose_name=_("Name")) verbose_name=_("Name"))
image_name = tables.Column("image_name", image_name = tables.Column("image_name",
verbose_name=_("Image Name")) verbose_name=_("Image Name"))

View File

@ -44,7 +44,7 @@ from openstack_dashboard.dashboards.project.instances.workflows \
from openstack_dashboard.dashboards.project.instances.workflows \ from openstack_dashboard.dashboards.project.instances.workflows \
import update_instance import update_instance
from openstack_dashboard import policy from openstack_dashboard import policy
from openstack_dashboard.views import get_url_with_pagination
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -1207,6 +1207,14 @@ def render_locked(instance):
return mark_safe(locked_status) return mark_safe(locked_status)
def get_server_detail_link(obj, request):
return get_url_with_pagination(request,
InstancesTable._meta.pagination_param,
InstancesTable._meta.prev_pagination_param,
'horizon:project:instances:detail',
obj.id)
class InstancesTable(tables.DataTable): class InstancesTable(tables.DataTable):
TASK_STATUS_CHOICES = ( TASK_STATUS_CHOICES = (
(None, True), (None, True),
@ -1223,7 +1231,7 @@ class InstancesTable(tables.DataTable):
("shelved_offloaded", True), ("shelved_offloaded", True),
) )
name = tables.WrappingColumn("name", name = tables.WrappingColumn("name",
link="horizon:project:instances:detail", link=get_server_detail_link,
verbose_name=_("Instance Name")) verbose_name=_("Instance Name"))
image_name = tables.Column("image_name", image_name = tables.Column("image_name",
verbose_name=_("Image Name")) verbose_name=_("Image Name"))

View File

@ -45,6 +45,7 @@ from openstack_dashboard.dashboards.project.instances import tabs
from openstack_dashboard.dashboards.project.instances import workflows from openstack_dashboard.dashboards.project.instances import workflows
from openstack_dashboard.test import helpers from openstack_dashboard.test import helpers
from openstack_dashboard.usage import quotas from openstack_dashboard.usage import quotas
from openstack_dashboard.views import get_url_with_pagination
INDEX_TEMPLATE = 'horizon/common/_data_table_view.html' INDEX_TEMPLATE = 'horizon/common/_data_table_view.html'
@ -611,12 +612,51 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase):
six.text_type(server.id)) six.text_type(server.id))
self.mox.ReplayAll() self.mox.ReplayAll()
formData = {'action': 'instances__suspend__%s' % server.id} formData = {'action': 'instances__suspend__%s' % server.id}
res = self.client.post(INDEX_URL, formData) url = get_url_with_pagination(
self.request, 'next', 'prev', 'horizon:project:instances:index')
res = self.client.post(url, formData)
self.assertRedirectsNoFollow(res, INDEX_URL) self.assertRedirectsNoFollow(res, INDEX_URL)
@django.test.utils.override_settings(API_RESULT_PAGE_SIZE=2)
@helpers.create_stubs({api.nova: ('server_suspend',
'server_list',
'flavor_list',
'extension_supported',
'is_feature_available',),
api.glance: ('image_list_detailed',),
api.network: ('servers_update_addresses',)})
def test_suspend_instance_if_placed_on_2nd_page(self):
page_size = getattr(settings, 'API_RESULT_PAGE_SIZE', 2)
servers = self.servers.list()[:3]
api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \
.MultipleTimes().AndReturn(True)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.glance.image_list_detailed(IgnoreArg()) \
.AndReturn((self.images.list(), False, False))
api.nova.server_list(IsA(http.HttpRequest), search_opts={
'marker': servers[page_size - 1].id, 'paginate': True}) \
.AndReturn([servers[page_size:], False])
api.network.servers_update_addresses(
IsA(http.HttpRequest), servers[page_size:])
api.nova.server_suspend(IsA(http.HttpRequest),
six.text_type(servers[-1].id))
self.mox.ReplayAll()
self.request.GET['marker'] = servers[-2].id
params = "=".join([tables.InstancesTable._meta.pagination_param,
servers[page_size - 1].id])
url = "?".join([reverse('horizon:project:instances:index'),
params])
formData = {'action': 'instances__suspend__%s' % servers[-1].id}
self.client.post(url, formData)
@helpers.create_stubs({api.nova: ('server_suspend', @helpers.create_stubs({api.nova: ('server_suspend',
'server_list', 'server_list',
'flavor_list', 'flavor_list',

View File

@ -53,6 +53,7 @@ from openstack_dashboard.dashboards.project.instances \
import tabs as project_tabs import tabs as project_tabs
from openstack_dashboard.dashboards.project.instances \ from openstack_dashboard.dashboards.project.instances \
import workflows as project_workflows import workflows as project_workflows
from openstack_dashboard.views import get_url_with_pagination
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -322,7 +323,12 @@ class DetailView(tabs.TabView):
args=[instance.image['id']]) args=[instance.image['id']])
instance.volume_url = self.volume_url instance.volume_url = self.volume_url
context["instance"] = instance context["instance"] = instance
context["url"] = reverse(self.redirect_url) context["url"] = get_url_with_pagination(
self.request,
project_tables.InstancesTable._meta.pagination_param,
project_tables.InstancesTable._meta.prev_pagination_param,
self.redirect_url)
context["actions"] = self._get_actions(instance) context["actions"] = self._get_actions(instance)
return context return context

View File

@ -0,0 +1,46 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import six
from openstack_dashboard.test import helpers as test
from openstack_dashboard import views
class DashboardViewsTest(test.TestCase):
def test_get_url_with_pagination(self):
req = self.request
url_string = 'horizon:project:instances:index'
url = views.get_url_with_pagination(req, None, None, url_string, None)
self.assertEqual(six.text_type('/project/instances/'), url)
def test_get_url_with_pagination_with_if(self):
req = self.request
url_string = 'horizon:project:instances:detail'
url = views.get_url_with_pagination(req, None, None, url_string, 'id')
self.assertEqual(six.text_type('/project/instances/id/'), url)
def test_get_url_with_pagination_next(self):
req = self.request
url_string = 'horizon:project:instances:index'
req.GET.update({'next': 'id'})
url = views.get_url_with_pagination(
req, 'next', None, url_string, None)
self.assertEqual(six.text_type('/project/instances/?next=id'), url)
def test_get_url_with_pagination_prev(self):
req = self.request
url_string = 'horizon:project:instances:index'
req.GET.update({'prev': 'id'})
url = views.get_url_with_pagination(
req, None, 'prev', url_string, None)
self.assertEqual(six.text_type('/project/instances/?prev=id'), url)

View File

@ -13,8 +13,10 @@
# under the License. # under the License.
from django.conf import settings from django.conf import settings
from django.core import urlresolvers
from django import shortcuts from django import shortcuts
import django.views.decorators.vary import django.views.decorators.vary
from six.moves import urllib
import horizon import horizon
from horizon import base from horizon import base
@ -59,3 +61,22 @@ def splash(request):
if MESSAGES_PATH: if MESSAGES_PATH:
notifications.process_message_notification(request, MESSAGES_PATH) notifications.process_message_notification(request, MESSAGES_PATH)
return response return response
def get_url_with_pagination(request, marker_name, prev_marker_name, url_string,
object_id=None):
if object_id:
url = urlresolvers.reverse(url_string, args=(object_id,))
else:
url = urlresolvers.reverse(url_string)
marker = request.GET.get(marker_name, None)
if marker:
return "{}?{}".format(url,
urllib.parse.urlencode({marker_name: marker}))
prev_marker = request.GET.get(prev_marker_name, None)
if prev_marker:
return "{}?{}".format(url,
urllib.parse.urlencode({prev_marker_name:
prev_marker}))
return url