Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save jeffrey4l/b79624ea2f1fbfcdb0c9 to your computer and use it in GitHub Desktop.
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
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