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:
parent
d616b41000
commit
9e1eb5f5d7
@ -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):
|
||||||
|
@ -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"))
|
||||||
|
@ -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"))
|
||||||
|
@ -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',
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
46
openstack_dashboard/test/views.py
Normal file
46
openstack_dashboard/test/views.py
Normal 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)
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user