Created
January 7, 2015 08:27
-
-
Save jeffrey4l/b79624ea2f1fbfcdb0c9 to your computer and use it in GitHub Desktop.
A patch for horizon bug https://bugs.launchpad.net/horizon/+bug/1287093, This patch is tested on OpenStack Havana
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/horizon/tables/base.py b/horizon/tables/base.py | |
index adc284c..1337989 100644 | |
--- a/horizon/tables/base.py | |
+++ b/horizon/tables/base.py | |
@@ -1031,6 +1031,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 d8a2637..592364c 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 786b942..d19771b 100644 | |
--- a/openstack_dashboard/dashboards/project/containers/tables.py | |
+++ b/openstack_dashboard/dashboards/project/containers/tables.py | |
@@ -133,6 +133,16 @@ class ContainersTable(tables.DataTable): | |
browser_table = "navigation" | |
footer = False | |
+ 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" | |
@@ -254,3 +264,14 @@ class ObjectsTable(tables.DataTable): | |
data_types = ("subfolders", "objects") | |
browser_table = "content" | |
footer = False | |
+ | |
+ 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 f778730..f7063ef 100644 | |
--- a/openstack_dashboard/dashboards/project/instances/tests.py | |
+++ b/openstack_dashboard/dashboards/project/instances/tests.py | |
@@ -2128,3 +2128,93 @@ class InstanceTests(test.TestCase): | |
password=password, | |
confirm_password=password) | |
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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment