From 9e1eb5f5d7354d12cf25ec77dadff3432752c741 Mon Sep 17 00:00:00 2001 From: Dmitry Ratushyy Date: Tue, 8 Mar 2016 17:03:32 +0300 Subject: [PATCH] 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 Co-Authored-By: Vladislav Kuzmin Change-Id: I4d805e4a65e838242af38677cbb9efefc498a96f Closes-Bug: #1553142 --- horizon/tables/base.py | 17 +++++-- .../dashboards/admin/instances/tables.py | 10 +++- .../dashboards/project/instances/tables.py | 12 ++++- .../dashboards/project/instances/tests.py | 44 +++++++++++++++++- .../dashboards/project/instances/views.py | 8 +++- openstack_dashboard/test/views.py | 46 +++++++++++++++++++ openstack_dashboard/views.py | 21 +++++++++ 7 files changed, 149 insertions(+), 9 deletions(-) create mode 100644 openstack_dashboard/test/views.py diff --git a/horizon/tables/base.py b/horizon/tables/base.py index 1e78e4232d..955dcd313c 100644 --- a/horizon/tables/base.py +++ b/horizon/tables/base.py @@ -14,6 +14,7 @@ import collections import copy +import inspect import json import logging from operator import attrgetter @@ -466,6 +467,8 @@ class Column(html.HTMLElement): return None obj_id = self.table.get_object_id(datum) if callable(self.link): + if 'request' in inspect.getargspec(self.link).args: + return self.link(datum, request=self.table.request) return self.link(datum) try: return urlresolvers.reverse(self.link, args=(obj_id,)) @@ -670,11 +673,19 @@ class Row(html.HTMLElement): def get_ajax_update_url(self): 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), ("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) def can_be_selected(self, datum): diff --git a/openstack_dashboard/dashboards/admin/instances/tables.py b/openstack_dashboard/dashboards/admin/instances/tables.py index ecdcba973b..15170928bf 100644 --- a/openstack_dashboard/dashboards/admin/instances/tables.py +++ b/openstack_dashboard/dashboards/admin/instances/tables.py @@ -26,6 +26,7 @@ from openstack_dashboard.dashboards.project.instances import audit_tables from openstack_dashboard.dashboards.project.instances \ import tables as project_tables from openstack_dashboard import policy +from openstack_dashboard.views import get_url_with_pagination class AdminEditInstance(project_tables.EditInstance): @@ -109,6 +110,13 @@ class AdminInstanceFilterAction(tables.FilterAction): ) + 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): TASK_STATUS_CHOICES = ( (None, True), @@ -134,7 +142,7 @@ class AdminInstancesTable(tables.DataTable): verbose_name=_("Host"), classes=('nowrap-col',)) name = tables.WrappingColumn("name", - link="horizon:admin:instances:detail", + link=get_server_detail_link, verbose_name=_("Name")) image_name = tables.Column("image_name", verbose_name=_("Image Name")) diff --git a/openstack_dashboard/dashboards/project/instances/tables.py b/openstack_dashboard/dashboards/project/instances/tables.py index 2773eb205f..857ceb5735 100644 --- a/openstack_dashboard/dashboards/project/instances/tables.py +++ b/openstack_dashboard/dashboards/project/instances/tables.py @@ -44,7 +44,7 @@ from openstack_dashboard.dashboards.project.instances.workflows \ from openstack_dashboard.dashboards.project.instances.workflows \ import update_instance from openstack_dashboard import policy - +from openstack_dashboard.views import get_url_with_pagination LOG = logging.getLogger(__name__) @@ -1207,6 +1207,14 @@ def render_locked(instance): 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): TASK_STATUS_CHOICES = ( (None, True), @@ -1223,7 +1231,7 @@ class InstancesTable(tables.DataTable): ("shelved_offloaded", True), ) name = tables.WrappingColumn("name", - link="horizon:project:instances:detail", + link=get_server_detail_link, verbose_name=_("Instance Name")) image_name = tables.Column("image_name", verbose_name=_("Image Name")) diff --git a/openstack_dashboard/dashboards/project/instances/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py index 967381b5ca..c169566e21 100644 --- a/openstack_dashboard/dashboards/project/instances/tests.py +++ b/openstack_dashboard/dashboards/project/instances/tests.py @@ -45,6 +45,7 @@ from openstack_dashboard.dashboards.project.instances import tabs from openstack_dashboard.dashboards.project.instances import workflows from openstack_dashboard.test import helpers from openstack_dashboard.usage import quotas +from openstack_dashboard.views import get_url_with_pagination INDEX_TEMPLATE = 'horizon/common/_data_table_view.html' @@ -611,12 +612,51 @@ class InstanceTests(helpers.ResetImageAPIVersionMixin, helpers.TestCase): six.text_type(server.id)) self.mox.ReplayAll() - 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) + @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', 'server_list', 'flavor_list', diff --git a/openstack_dashboard/dashboards/project/instances/views.py b/openstack_dashboard/dashboards/project/instances/views.py index a4fc9e561e..6a21f363c5 100644 --- a/openstack_dashboard/dashboards/project/instances/views.py +++ b/openstack_dashboard/dashboards/project/instances/views.py @@ -53,6 +53,7 @@ from openstack_dashboard.dashboards.project.instances \ import tabs as project_tabs from openstack_dashboard.dashboards.project.instances \ import workflows as project_workflows +from openstack_dashboard.views import get_url_with_pagination LOG = logging.getLogger(__name__) @@ -322,7 +323,12 @@ class DetailView(tabs.TabView): args=[instance.image['id']]) instance.volume_url = self.volume_url 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) return context diff --git a/openstack_dashboard/test/views.py b/openstack_dashboard/test/views.py new file mode 100644 index 0000000000..66a1a6a505 --- /dev/null +++ b/openstack_dashboard/test/views.py @@ -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) diff --git a/openstack_dashboard/views.py b/openstack_dashboard/views.py index 97223b73bb..4861134566 100644 --- a/openstack_dashboard/views.py +++ b/openstack_dashboard/views.py @@ -13,8 +13,10 @@ # under the License. from django.conf import settings +from django.core import urlresolvers from django import shortcuts import django.views.decorators.vary +from six.moves import urllib import horizon from horizon import base @@ -59,3 +61,22 @@ def splash(request): if MESSAGES_PATH: notifications.process_message_notification(request, MESSAGES_PATH) 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