diff options
author | Jenkins <jenkins@review.openstack.org> | 2014-03-21 14:43:10 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2014-03-21 14:43:10 +0000 |
commit | 9dba2cfa41eb20fabc9d68924229812415225053 (patch) | |
tree | 7d62ff878f25e47c7bccd47b3c00dde307d50585 | |
parent | 537dbacdcc316a60fe7c456d9a96f773c9c73948 (diff) | |
parent | e95a04733d463658f3ca87e9df213b8e1dd0326d (diff) | |
download | horizon-9dba2cfa41eb20fabc9d68924229812415225053.tar.gz |
Merge "Selected instances are not deleted with pagination"
-rw-r--r-- | horizon/tables/base.py | 10 | ||||
-rw-r--r-- | horizon/templates/horizon/common/_data_table.html | 2 | ||||
-rw-r--r-- | openstack_dashboard/dashboards/project/containers/tables.py | 22 | ||||
-rw-r--r-- | openstack_dashboard/dashboards/project/instances/tests.py | 91 |
4 files changed, 124 insertions, 1 deletions
diff --git a/horizon/tables/base.py b/horizon/tables/base.py index b6c3dbe2b..10aaa98db 100644 --- a/horizon/tables/base.py +++ b/horizon/tables/base.py @@ -1161,6 +1161,16 @@ class DataTable(object): """ return self.request.get_full_path().partition('?')[0] + def get_full_url(self): + """Returns the full URL path for this table. + + This is used for the POST action attribute on the form element + wrapping the table. We use this method to persist the + pagination marker. + + """ + return self.request.get_full_path() + def get_empty_message(self): """Returns the message to be displayed when there is no data.""" return self._no_data_message diff --git a/horizon/templates/horizon/common/_data_table.html b/horizon/templates/horizon/common/_data_table.html index d8a26370b..592364c96 100644 --- a/horizon/templates/horizon/common/_data_table.html +++ b/horizon/templates/horizon/common/_data_table.html @@ -1,7 +1,7 @@ {% load i18n %} {% with table.needs_form_wrapper as needs_form_wrapper %} <div class="table_wrapper"> - {% if needs_form_wrapper %}<form action="{{ table.get_absolute_url }}" method="POST">{% csrf_token %}{% endif %} + {% if needs_form_wrapper %}<form action="{{ table.get_full_url }}" method="POST">{% csrf_token %}{% endif %} {% with columns=table.get_columns rows=table.get_rows %} {% block table %} <table id="{{ table.name }}" class="table table-bordered table-striped datatable"> diff --git a/openstack_dashboard/dashboards/project/containers/tables.py b/openstack_dashboard/dashboards/project/containers/tables.py index c8acc3c2a..a14c880b0 100644 --- a/openstack_dashboard/dashboards/project/containers/tables.py +++ b/openstack_dashboard/dashboards/project/containers/tables.py @@ -269,6 +269,17 @@ class ContainersTable(tables.DataTable): url = super(ContainersTable, self).get_absolute_url() return http.urlquote(url) + def get_full_url(self): + """Returns the encoded absolute URL path with its query string. + + This is used for the POST action attribute on the form element + wrapping the table. We use this method to persist the + pagination marker. + + """ + url = super(ContainersTable, self).get_full_url() + return http.urlquote(url) + class ViewObject(tables.LinkAction): name = "view" @@ -416,3 +427,14 @@ class ObjectsTable(tables.DataTable): def get_absolute_url(self): url = super(ObjectsTable, self).get_absolute_url() return http.urlquote(url) + + def get_full_url(self): + """Returns the encoded absolute URL path with its query string. + + This is used for the POST action attribute on the form element + wrapping the table. We use this method to persist the + pagination marker. + + """ + url = super(ObjectsTable, self).get_full_url() + return http.urlquote(url) diff --git a/openstack_dashboard/dashboards/project/instances/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py index 6b4fce762..32cb13f0c 100644 --- a/openstack_dashboard/dashboards/project/instances/tests.py +++ b/openstack_dashboard/dashboards/project/instances/tests.py @@ -21,6 +21,7 @@ import json import uuid +from django.conf import settings from django.core.urlresolvers import reverse from django import http from django.test import utils as test_utils @@ -2646,6 +2647,96 @@ class InstanceTests(test.TestCase): disk_config='AUTO') self.assertRedirectsNoFollow(res, INDEX_URL) + @test_utils.override_settings(API_RESULT_PAGE_SIZE=2) + @test.create_stubs({api.nova: ('flavor_list', + 'server_list', + 'tenant_absolute_limits', + 'extension_supported',), + api.glance: ('image_list_detailed',), + api.network: + ('floating_ip_simple_associate_supported',), + }) + def test_index_form_action_with_pagination(self): + """The form action on the next page should have marker + object from the previous page last element. + """ + 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)) \ + .MultipleTimes().AndReturn(self.flavors.list()) + api.glance.image_list_detailed(IgnoreArg()) \ + .MultipleTimes().AndReturn((self.images.list(), False)) + + search_opts = {'marker': None, 'paginate': True} + api.nova.server_list(IsA(http.HttpRequest), search_opts=search_opts) \ + .AndReturn([servers[:page_size], True]) + api.nova.server_list(IsA(http.HttpRequest), search_opts={ + 'marker': servers[page_size - 1].id, 'paginate': True}) \ + .AndReturn([servers[page_size:], False]) + + api.nova.tenant_absolute_limits(IsA(http.HttpRequest), reserved=True) \ + .MultipleTimes().AndReturn(self.limits['absolute']) + api.network.floating_ip_simple_associate_supported( + IsA(http.HttpRequest)).MultipleTimes().AndReturn(True) + + self.mox.ReplayAll() + + res = self.client.get(INDEX_URL) + self.assertTemplateUsed(res, 'project/instances/index.html') + # get first page with 2 items + self.assertEqual(len(res.context['instances_table'].data), page_size) + + # update INDEX_URL with marker object + next_page_url = "?".join([reverse('horizon:project:instances:index'), + "=".join([tables.InstancesTable._meta.pagination_param, + servers[page_size - 1].id])]) + form_action = 'action="%s"' % next_page_url + + res = self.client.get(next_page_url) + # get next page with remaining items (item 3) + self.assertEqual(len(res.context['instances_table'].data), 1) + # ensure that marker object exists in form action + self.assertContains(res, form_action, count=1) + + @test_utils.override_settings(API_RESULT_PAGE_SIZE=2) + @test.create_stubs({api.nova: ('server_list', + 'flavor_list', + 'server_delete',), + api.glance: ('image_list_detailed',), + api.network: ('servers_update_addresses',)}) + def test_terminate_instance_with_pagination(self): + """Instance should be deleted from + the next page. + """ + page_size = getattr(settings, 'API_RESULT_PAGE_SIZE', 2) + servers = self.servers.list()[:3] + server = servers[-1] + + search_opts = {'marker': servers[page_size - 1].id, 'paginate': True} + api.nova.server_list(IsA(http.HttpRequest), search_opts=search_opts) \ + .AndReturn([servers[page_size:], False]) + api.network.servers_update_addresses(IsA(http.HttpRequest), + servers[page_size:]) + api.nova.flavor_list(IgnoreArg()).AndReturn(self.flavors.list()) + api.glance.image_list_detailed(IgnoreArg()) \ + .AndReturn((self.images.list(), False)) + api.nova.server_delete(IsA(http.HttpRequest), server.id) + self.mox.ReplayAll() + + # update INDEX_URL with marker object + next_page_url = "?".join([reverse('horizon:project:instances:index'), + "=".join([tables.InstancesTable._meta.pagination_param, + servers[page_size - 1].id])]) + formData = {'action': 'instances__terminate__%s' % server.id} + res = self.client.post(next_page_url, formData) + + self.assertRedirectsNoFollow(res, next_page_url) + self.assertMessageCount(success=1) + class InstanceAjaxTests(test.TestCase): @test.create_stubs({api.nova: ("server_get", |