diff --git a/horizon/api/glance.py b/horizon/api/glance.py
index 1f8f8cd318..cda7cb2975 100644
--- a/horizon/api/glance.py
+++ b/horizon/api/glance.py
@@ -25,6 +25,7 @@ import logging
import urlparse
from django.utils.decorators import available_attrs
+from django.conf import settings
from glance import client as glance_client
from glance.common import exception as glance_exception
@@ -122,8 +123,17 @@ def image_get_meta(request, image_id):
@catch_glance_exception
-def image_list_detailed(request):
- return [Image(i) for i in glanceclient(request).get_images_detailed()]
+def image_list_detailed(request, marker=None, filters=None):
+ filters = filters or {}
+ limit = getattr(settings, 'API_RESULT_LIMIT', 1000)
+ image_dicts = glanceclient(request).get_images_detailed(limit=limit + 1,
+ marker=marker,
+ filters=filters)
+ images = [Image(i) for i in image_dicts]
+ if(len(images) > limit):
+ return (images[0:-1], True)
+ else:
+ return (images, False)
@catch_glance_exception
@@ -134,9 +144,15 @@ def image_update(request, image_id, image_meta=None):
@catch_glance_exception
-def snapshot_list_detailed(request):
- filters = {}
- filters['property-image_type'] = 'snapshot'
- filters['is_public'] = 'none'
- return [Image(i) for i in glanceclient(request)
- .get_images_detailed(filters=filters)]
+def snapshot_list_detailed(request, marker=None, extra_filters=None):
+ filters = {'property-image_type': 'snapshot'}
+ filters.update(extra_filters or {})
+ limit = getattr(settings, 'API_RESULT_LIMIT', 1000)
+ image_dicts = glanceclient(request).get_images_detailed(limit=limit + 1,
+ marker=marker,
+ filters=filters)
+ images = [Image(i) for i in image_dicts]
+ if(len(images) > limit):
+ return (images[0:-1], True)
+ else:
+ return (images, False)
diff --git a/horizon/dashboards/nova/images_and_snapshots/images/tables.py b/horizon/dashboards/nova/images_and_snapshots/images/tables.py
index df57e6f01c..b22827b62f 100644
--- a/horizon/dashboards/nova/images_and_snapshots/images/tables.py
+++ b/horizon/dashboards/nova/images_and_snapshots/images/tables.py
@@ -99,3 +99,4 @@ class ImagesTable(tables.DataTable):
verbose_name = _("Images")
table_actions = (DeleteImage,)
row_actions = (LaunchImage, EditImage, DeleteImage)
+ pagination_param = "image_marker"
diff --git a/horizon/dashboards/nova/images_and_snapshots/snapshots/tables.py b/horizon/dashboards/nova/images_and_snapshots/snapshots/tables.py
index 13b19dd314..b8abedb7d5 100644
--- a/horizon/dashboards/nova/images_and_snapshots/snapshots/tables.py
+++ b/horizon/dashboards/nova/images_and_snapshots/snapshots/tables.py
@@ -35,3 +35,4 @@ class SnapshotsTable(ImagesTable):
verbose_name = _("Instance Snapshots")
table_actions = (DeleteSnapshot,)
row_actions = (LaunchImage, EditImage, DeleteSnapshot)
+ pagination_param = "snapshot_marker"
diff --git a/horizon/dashboards/nova/images_and_snapshots/tests.py b/horizon/dashboards/nova/images_and_snapshots/tests.py
index 124fad570f..cb0944cf19 100644
--- a/horizon/dashboards/nova/images_and_snapshots/tests.py
+++ b/horizon/dashboards/nova/images_and_snapshots/tests.py
@@ -38,8 +38,13 @@ class ImagesAndSnapshotsTests(test.TestCase):
snapshots = self.snapshots.list()
self.mox.StubOutWithMock(api, 'image_list_detailed')
self.mox.StubOutWithMock(api, 'snapshot_list_detailed')
- api.image_list_detailed(IsA(http.HttpRequest)).AndReturn(images)
- api.snapshot_list_detailed(IsA(http.HttpRequest)).AndReturn(snapshots)
+ self.mox.StubOutWithMock(api, 'volume_snapshot_list')
+ api.volume_snapshot_list(IsA(http.HttpRequest)) \
+ .AndReturn(self.volumes.list())
+ api.image_list_detailed(IsA(http.HttpRequest),
+ marker=None).AndReturn([images, False])
+ api.snapshot_list_detailed(IsA(http.HttpRequest),
+ marker=None).AndReturn([snapshots, False])
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
@@ -53,9 +58,13 @@ class ImagesAndSnapshotsTests(test.TestCase):
def test_index_no_images(self):
self.mox.StubOutWithMock(api, 'snapshot_list_detailed')
self.mox.StubOutWithMock(api, 'image_list_detailed')
- api.image_list_detailed(IsA(http.HttpRequest)).AndReturn([])
- api.snapshot_list_detailed(IsA(http.HttpRequest)) \
- .AndReturn(self.snapshots.list())
+ self.mox.StubOutWithMock(api, 'volume_snapshot_list')
+ api.volume_snapshot_list(IsA(http.HttpRequest)) \
+ .AndReturn(self.volumes.list())
+ api.image_list_detailed(IsA(http.HttpRequest),
+ marker=None).AndReturn([(), False])
+ api.snapshot_list_detailed(IsA(http.HttpRequest), marker=None) \
+ .AndReturn([self.snapshots.list(), False])
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
@@ -65,9 +74,13 @@ class ImagesAndSnapshotsTests(test.TestCase):
self.mox.StubOutWithMock(api, 'image_list_detailed')
self.mox.StubOutWithMock(api, 'snapshot_list_detailed')
exc = glance_exception.ClientConnectionError('clientConnError')
- api.image_list_detailed(IsA(http.HttpRequest)).AndRaise(exc)
- api.snapshot_list_detailed(IsA(http.HttpRequest)) \
- .AndReturn(self.snapshots.list())
+ self.mox.StubOutWithMock(api, 'volume_snapshot_list')
+ api.volume_snapshot_list(IsA(http.HttpRequest)) \
+ .AndReturn(self.volumes.list())
+ api.image_list_detailed(IsA(http.HttpRequest),
+ marker=None).AndRaise(exc)
+ api.snapshot_list_detailed(IsA(http.HttpRequest), marker=None) \
+ .AndReturn([self.snapshots.list(), False])
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
@@ -86,9 +99,13 @@ class ImagesAndSnapshotsTests(test.TestCase):
new_snapshots = [snapshot1, snapshot2]
self.mox.StubOutWithMock(api, 'image_list_detailed')
self.mox.StubOutWithMock(api, 'snapshot_list_detailed')
- api.image_list_detailed(IsA(http.HttpRequest)).AndReturn(images)
- api.snapshot_list_detailed(IsA(http.HttpRequest)).\
- AndReturn(new_snapshots)
+ self.mox.StubOutWithMock(api, 'volume_snapshot_list')
+ api.volume_snapshot_list(IsA(http.HttpRequest)) \
+ .AndReturn(self.volumes.list())
+ api.image_list_detailed(IsA(http.HttpRequest),
+ marker=None).AndReturn([images, False])
+ api.snapshot_list_detailed(IsA(http.HttpRequest), marker=None) \
+ .AndReturn([new_snapshots, False])
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
diff --git a/horizon/dashboards/nova/images_and_snapshots/views.py b/horizon/dashboards/nova/images_and_snapshots/views.py
index 82d0d0b9c8..7b84e5c504 100644
--- a/horizon/dashboards/nova/images_and_snapshots/views.py
+++ b/horizon/dashboards/nova/images_and_snapshots/views.py
@@ -42,9 +42,16 @@ class IndexView(tables.MultiTableView):
table_classes = (ImagesTable, SnapshotsTable, VolumeSnapshotsTable)
template_name = 'nova/images_and_snapshots/index.html'
+ def has_more_data(self, table):
+ return getattr(self, "_more_%s" % table.name, False)
+
def get_images_data(self):
+ marker = self.request.GET.get(ImagesTable._meta.pagination_param, None)
try:
- all_images = api.image_list_detailed(self.request)
+ # FIXME(gabriel): The paging is going to be strange here due to
+ # our filtering after the fact.
+ all_images, _more_images = api.image_list_detailed(self.request,
+ marker=marker)
images = [im for im in all_images
if im['container_format'] not in ['aki', 'ari'] and
getattr(im.properties, "image_type", '') != "snapshot"]
@@ -54,12 +61,15 @@ class IndexView(tables.MultiTableView):
return images
def get_snapshots_data(self):
+ req = self.request
+ marker = req.GET.get(SnapshotsTable._meta.pagination_param, None)
try:
- snapshots = api.snapshot_list_detailed(self.request)
+ snaps, self._more_snapshots = api.snapshot_list_detailed(req,
+ marker=marker)
except:
- snapshots = []
- exceptions.handle(self.request, _("Unable to retrieve snapshots."))
- return snapshots
+ snaps = []
+ exceptions.handle(req, _("Unable to retrieve snapshots."))
+ return snaps
def get_volume_snapshots_data(self):
try:
diff --git a/horizon/dashboards/nova/instances_and_volumes/instances/tests.py b/horizon/dashboards/nova/instances_and_volumes/instances/tests.py
index 11da77067e..527df6be66 100644
--- a/horizon/dashboards/nova/instances_and_volumes/instances/tests.py
+++ b/horizon/dashboards/nova/instances_and_volumes/instances/tests.py
@@ -287,8 +287,10 @@ class InstanceViewTests(test.TestCase):
server.id,
"snapshot1")
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
- api.snapshot_list_detailed(IsA(http.HttpRequest)).AndReturn([])
- api.image_list_detailed(IsA(http.HttpRequest)).AndReturn([])
+ api.snapshot_list_detailed(IsA(http.HttpRequest), marker=None) \
+ .AndReturn(([], False))
+ api.image_list_detailed(IsA(http.HttpRequest), marker=None) \
+ .AndReturn(([], False))
api.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list())
diff --git a/horizon/dashboards/syspanel/images/views.py b/horizon/dashboards/syspanel/images/views.py
index d4821c52c2..c2b9775fbd 100644
--- a/horizon/dashboards/syspanel/images/views.py
+++ b/horizon/dashboards/syspanel/images/views.py
@@ -37,11 +37,18 @@ class IndexView(tables.DataTableView):
table_class = AdminImagesTable
template_name = 'syspanel/images/index.html'
+ def has_more_data(self, table):
+ return self._more
+
def get_data(self):
images = []
+ marker = self.request.GET.get(AdminImagesTable._meta.pagination_param,
+ None)
try:
- images = api.image_list_detailed(self.request)
+ images, self._more = api.image_list_detailed(self.request,
+ marker=marker)
except:
+ self._more = False
msg = _('Unable to retrieve image list.')
exceptions.handle(self.request, msg)
return images
diff --git a/horizon/tables/base.py b/horizon/tables/base.py
index 66b3355c52..bea4654b54 100644
--- a/horizon/tables/base.py
+++ b/horizon/tables/base.py
@@ -558,6 +558,13 @@ class DataTableOptions(object):
The name of the context variable which will contain the table when
it is rendered. Defaults to ``"table"``.
+ .. attribute:: pagination_param
+
+ The name of the query string parameter which will be used when
+ paginating this table. When using multiple tables in a single
+ view this will need to be changed to differentiate between the
+ tables. Default: ``"marker"``.
+
.. attribute:: status_columns
A list or tuple of column names which represents the "state"
@@ -592,6 +599,7 @@ class DataTableOptions(object):
self.row_actions = getattr(options, 'row_actions', [])
self.row_class = getattr(options, 'row_class', Row)
self.column_class = getattr(options, 'column_class', Column)
+ self.pagination_param = getattr(options, 'pagination_param', 'marker')
# Set self.filter if we have any FilterActions
filter_actions = [action for action in self.table_actions if
@@ -1038,6 +1046,10 @@ class DataTable(object):
"""
return http.urlquote_plus(self.get_object_id(self.data[-1]))
+ def get_pagination_string(self):
+ """ Returns the query parameter string to paginate this table. """
+ return "=".join([self._meta.pagination_param, self.get_marker()])
+
def calculate_row_status(self, statuses):
"""
Returns a boolean value determining the overall row status
diff --git a/horizon/templates/horizon/common/_data_table.html b/horizon/templates/horizon/common/_data_table.html
index e64ebf70b6..54c743d872 100644
--- a/horizon/templates/horizon/common/_data_table.html
+++ b/horizon/templates/horizon/common/_data_table.html
@@ -31,7 +31,7 @@
{% blocktrans count counter=rows|length %}Displaying {{ counter }} item{% plural %}Displaying {{ counter }} items{% endblocktrans %}
{% if table.has_more_data %}
|
- More »
+ More »
{% endif %}
diff --git a/horizon/tests/api_tests/glance_tests.py b/horizon/tests/api_tests/glance_tests.py
index f6960971f4..f5f858ca6b 100644
--- a/horizon/tests/api_tests/glance_tests.py
+++ b/horizon/tests/api_tests/glance_tests.py
@@ -19,6 +19,7 @@
# under the License.
from glance.common import exception as glance_exception
+from django.conf import settings
from horizon import api
from horizon import test
@@ -48,15 +49,14 @@ class GlanceApiTests(test.APITestCase):
def test_image_list_detailed(self):
""" Verify "list" returns our custom Image class. """
images = self.images.list()
-
glanceclient = self.stub_glanceclient()
- glanceclient.get_images_detailed().AndReturn(images)
+ glanceclient.get_images_detailed(filters={}, limit=1001, marker=None) \
+ .AndReturn([images, False])
self.mox.ReplayAll()
- ret_val = api.image_list_detailed(self.request)
+ ret_val, _more = api.image_list_detailed(self.request)
for image in ret_val:
self.assertIsInstance(image, api.glance.Image)
- self.assertIn(image._apidict, images)
def test_glance_exception_wrapping_for_internal_server_errors(self):
"""
@@ -66,8 +66,8 @@ class GlanceApiTests(test.APITestCase):
"""
# TODO(johnp): Remove once Bug 952618 is fixed in the glance client.
glanceclient = self.stub_glanceclient()
- glanceclient.get_images_detailed().AndRaise(
- Exception("Internal Server error: "))
+ glanceclient.get_images_detailed(filters={}, limit=1001, marker=None) \
+ .AndRaise(Exception("Internal Server error: "))
self.mox.ReplayAll()
with self.assertRaises(glance_exception.ClientConnectionError):
@@ -80,8 +80,9 @@ class GlanceApiTests(test.APITestCase):
"""
# TODO(johnp): Remove once Bug 952618 is fixed in the glance client.
glanceclient = self.stub_glanceclient()
- glanceclient.get_images_detailed().AndRaise(
- Exception("Unknown error occurred! 503 Service Unavailable"))
+ exc = Exception("Unknown error occurred! 503 Service Unavailable")
+ glanceclient.get_images_detailed(filters={}, limit=1001, marker=None) \
+ .AndRaise(exc)
self.mox.ReplayAll()
with self.assertRaises(glance_exception.ClientConnectionError):