Sort images list in ascending alphabetical order

Move most of the pagination-logic to `api.glance.image_list_detailed`,
thus making code in Admin/Project->Images->get_data() less confusing
(and remove hard-coded 'asc'|'desc' values).

Also prepare to get images both from glanceclient.v1 and
glanceclient.v2 (which doesn't set `is_public` attr on images using
`visibility` attr instead).

Change-Id: Ibe6d3dd1e94a1d1fbf95382599a5f53c3559ce5a
Closes-Bug: #1534670
Closes-Bug: #1336317
This commit is contained in:
Timur Sufiev 2014-10-24 20:14:46 +04:00
parent abad2d3af4
commit f8e595b0fa
6 changed files with 123 additions and 51 deletions

View File

@ -80,8 +80,63 @@ def image_get(request, image_id):
return image return image
def is_image_public(im):
is_public_v1 = getattr(im, 'is_public', None)
if is_public_v1 is not None:
return is_public_v1
else:
return im.visibility == 'public'
def image_list_detailed(request, marker=None, sort_dir='desc', def image_list_detailed(request, marker=None, sort_dir='desc',
sort_key='created_at', filters=None, paginate=False): sort_key='created_at', filters=None, paginate=False,
reversed_order=False):
"""Thin layer above glanceclient, for handling pagination issues.
It provides iterating both forward and backward on top of ascetic
OpenStack pagination API - which natively supports only iterating forward
through the entries. Thus in order to retrieve list of objects at previous
page, a request with the reverse entries order had to be made to Glance,
using the first object id on current page as the marker - restoring
the original items ordering before sending them back to the UI.
.. param:: request
The request object coming from browser to be passed further into
Glance service.
.. param:: marker
The id of an object which defines a starting point of a query sent to
Glance service.
.. param:: sort_dir
The direction by which the resulting image list throughout all pages
(if pagination is enabled) will be sorted. Could be either 'asc'
(ascending) or 'desc' (descending), defaults to 'desc'.
.. param:: sort_key
The name of key by by which the resulting image list throughout all
pages (if pagination is enabled) will be sorted. Defaults to
'created_at'.
.. param:: filters
A dictionary of filters passed as is to Glance service.
.. param:: paginate
Whether the pagination is enabled. If it is, then the number of
entries on a single page of images table is limited to the specific
number stored in browser cookies.
.. param:: reversed_order
Set this flag to True when it's necessary to get a reversed list of
images from Glance (used for navigating the images list back in UI).
"""
limit = getattr(settings, 'API_RESULT_LIMIT', 1000) limit = getattr(settings, 'API_RESULT_LIMIT', 1000)
page_size = utils.get_page_size(request) page_size = utils.get_page_size(request)
@ -93,9 +148,13 @@ def image_list_detailed(request, marker=None, sort_dir='desc',
kwargs = {'filters': filters or {}} kwargs = {'filters': filters or {}}
if marker: if marker:
kwargs['marker'] = marker kwargs['marker'] = marker
kwargs['sort_dir'] = sort_dir
kwargs['sort_key'] = sort_key kwargs['sort_key'] = sort_key
if not reversed_order:
kwargs['sort_dir'] = sort_dir
else:
kwargs['sort_dir'] = 'desc' if sort_dir == 'asc' else 'asc'
images_iter = glanceclient(request).images.list(page_size=request_size, images_iter = glanceclient(request).images.list(page_size=request_size,
limit=limit, limit=limit,
**kwargs) **kwargs)
@ -111,14 +170,21 @@ def image_list_detailed(request, marker=None, sort_dir='desc',
if marker is not None: if marker is not None:
has_prev_data = True has_prev_data = True
# first page condition when reached via prev back # first page condition when reached via prev back
elif sort_dir == 'asc' and marker is not None: elif reversed_order and marker is not None:
has_more_data = True has_more_data = True
# last page condition # last page condition
elif marker is not None: elif marker is not None:
has_prev_data = True has_prev_data = True
# restore the original ordering here
if reversed_order:
images = sorted(images, key=lambda image:
(getattr(image, sort_key) or '').lower(),
reverse=(sort_dir == 'desc'))
else: else:
images = list(images_iter) images = list(images_iter)
return (images, has_more_data, has_prev_data)
return images, has_more_data, has_prev_data
def image_update(request, image_id, **kwargs): def image_update(request, image_id, **kwargs):

View File

@ -51,7 +51,9 @@ class ImagesViewTest(test.BaseAdminViewTests):
marker=None, marker=None,
paginate=True, paginate=True,
filters=filters, filters=filters,
sort_dir='desc') \ sort_dir='asc',
sort_key='name',
reversed_order=False) \
.AndReturn([self.images.list(), .AndReturn([self.images.list(),
False, False]) False, False])
# Test tenant list # Test tenant list
@ -72,29 +74,24 @@ class ImagesViewTest(test.BaseAdminViewTests):
def test_images_list_get_pagination(self): def test_images_list_get_pagination(self):
images = self.images.list()[:5] images = self.images.list()[:5]
filters = {'is_public': None} filters = {'is_public': None}
kwargs = {'paginate': True, 'filters': filters,
'sort_dir': 'asc', 'sort_key': 'name',
'reversed_order': False}
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None, marker=None,
paginate=True, **kwargs) \
filters=filters,
sort_dir='desc') \
.AndReturn([images, True, True]) .AndReturn([images, True, True])
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None, marker=None,
paginate=True, **kwargs) \
filters=filters,
sort_dir='desc') \
.AndReturn([images[:2], True, True]) .AndReturn([images[:2], True, True])
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=images[2].id, marker=images[2].id,
paginate=True, **kwargs) \
filters=filters,
sort_dir='desc') \
.AndReturn([images[2:4], True, True]) .AndReturn([images[2:4], True, True])
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=images[4].id, marker=images[4].id,
paginate=True, **kwargs) \
filters=filters,
sort_dir='desc') \
.AndReturn([images[4:], True, True]) .AndReturn([images[4:], True, True])
# Test tenant list # Test tenant list
api.keystone.tenant_list(IsA(http.HttpRequest)).MultipleTimes().\ api.keystone.tenant_list(IsA(http.HttpRequest)).MultipleTimes().\
@ -138,29 +135,27 @@ class ImagesViewTest(test.BaseAdminViewTests):
def test_images_list_get_prev_pagination(self): def test_images_list_get_prev_pagination(self):
images = self.images.list()[:3] images = self.images.list()[:3]
filters = {'is_public': None} filters = {'is_public': None}
kwargs = {'paginate': True, 'filters': filters,
'sort_dir': 'asc', 'sort_key': 'name'}
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None, marker=None,
paginate=True, reversed_order=False,
filters=filters, **kwargs) \
sort_dir='desc') \
.AndReturn([images, True, False]) .AndReturn([images, True, False])
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None, marker=None,
paginate=True, reversed_order=False,
filters=filters, **kwargs) \
sort_dir='desc') \
.AndReturn([images[:2], True, True]) .AndReturn([images[:2], True, True])
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=images[2].id, marker=images[2].id,
paginate=True, reversed_order=False,
filters=filters, **kwargs) \
sort_dir='desc') \
.AndReturn([images[2:], True, True]) .AndReturn([images[2:], True, True])
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=images[2].id, marker=images[2].id,
paginate=True, reversed_order=True,
filters=filters, **kwargs) \
sort_dir='asc') \
.AndReturn([images[:2], True, True]) .AndReturn([images[:2], True, True])
# Test tenant list # Test tenant list
api.keystone.tenant_list(IsA(http.HttpRequest)).MultipleTimes().\ api.keystone.tenant_list(IsA(http.HttpRequest)).MultipleTimes().\

View File

@ -56,23 +56,20 @@ class IndexView(tables.DataTableView):
project_tables.AdminImagesTable._meta.prev_pagination_param, None) project_tables.AdminImagesTable._meta.prev_pagination_param, None)
if prev_marker is not None: if prev_marker is not None:
sort_dir = 'asc'
marker = prev_marker marker = prev_marker
else: else:
sort_dir = 'desc'
marker = self.request.GET.get( marker = self.request.GET.get(
project_tables.AdminImagesTable._meta.pagination_param, None) project_tables.AdminImagesTable._meta.pagination_param, None)
reversed_order = prev_marker is not None
try: try:
images, self._more, self._prev = api.glance.image_list_detailed( images, self._more, self._prev = api.glance.image_list_detailed(
self.request, self.request,
marker=marker, marker=marker,
paginate=True, paginate=True,
filters=filters, filters=filters,
sort_dir=sort_dir) sort_dir='asc',
sort_key='name',
if prev_marker is not None: reversed_order=reversed_order)
images = sorted(images, key=lambda image:
getattr(image, 'created_at'), reverse=True)
except Exception: except Exception:
self._prev = False self._prev = False

View File

@ -222,13 +222,13 @@ class OwnerFilter(tables.FixedFilterAction):
def get_image_categories(im, user_tenant_id): def get_image_categories(im, user_tenant_id):
categories = [] categories = []
if im.is_public: if api.glance.is_image_public(im):
categories.append('public') categories.append('public')
if im.owner == user_tenant_id: if im.owner == user_tenant_id:
categories.append('project') categories.append('project')
elif im.owner in filter_tenant_ids(): elif im.owner in filter_tenant_ids():
categories.append(im.owner) categories.append(im.owner)
elif not im.is_public: elif not api.glance.is_image_public(im):
categories.append('shared') categories.append('shared')
return categories return categories
@ -339,4 +339,3 @@ class ImagesTable(tables.DataTable):
row_actions = launch_actions + (CreateVolumeFromImage, row_actions = launch_actions + (CreateVolumeFromImage,
EditImage, UpdateMetadata, EditImage, UpdateMetadata,
DeleteImage,) DeleteImage,)
pagination_param = "image_marker"

View File

@ -46,8 +46,9 @@ class ImagesAndSnapshotsTests(test.TestCase):
def test_index(self): def test_index(self):
images = self.images.list() images = self.images.list()
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None, marker=None, paginate=True,
paginate=True) \ sort_dir='asc', sort_key='name',
reversed_order=False) \
.AndReturn([images, False, False]) .AndReturn([images, False, False])
self.mox.ReplayAll() self.mox.ReplayAll()
@ -72,8 +73,9 @@ class ImagesAndSnapshotsTests(test.TestCase):
@test.create_stubs({api.glance: ('image_list_detailed',)}) @test.create_stubs({api.glance: ('image_list_detailed',)})
def test_index_no_images(self): def test_index_no_images(self):
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None, marker=None, paginate=True,
paginate=True) \ sort_dir='asc', sort_key='name',
reversed_order=False) \
.AndReturn([(), False, False]) .AndReturn([(), False, False])
self.mox.ReplayAll() self.mox.ReplayAll()
@ -84,8 +86,9 @@ class ImagesAndSnapshotsTests(test.TestCase):
@test.create_stubs({api.glance: ('image_list_detailed',)}) @test.create_stubs({api.glance: ('image_list_detailed',)})
def test_index_error(self): def test_index_error(self):
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None, marker=None, paginate=True,
paginate=True) \ sort_dir='asc', sort_key='name',
reversed_order=False) \
.AndRaise(self.exceptions.glance) .AndRaise(self.exceptions.glance)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -96,8 +99,9 @@ class ImagesAndSnapshotsTests(test.TestCase):
def test_snapshot_actions(self): def test_snapshot_actions(self):
snapshots = self.snapshots.list() snapshots = self.snapshots.list()
api.glance.image_list_detailed(IsA(http.HttpRequest), api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None, marker=None, paginate=True,
paginate=True) \ sort_dir='asc', sort_key='name',
reversed_order=False) \
.AndReturn([snapshots, False, False]) .AndReturn([snapshots, False, False])
self.mox.ReplayAll() self.mox.ReplayAll()

View File

@ -44,14 +44,25 @@ class IndexView(tables.DataTableView):
return getattr(self, "_more", False) return getattr(self, "_more", False)
def get_data(self): def get_data(self):
marker = self.request.GET.get( prev_marker = self.request.GET.get(
images_tables.ImagesTable._meta.pagination_param, None) images_tables.ImagesTable._meta.prev_pagination_param, None)
if prev_marker is not None:
marker = prev_marker
else:
marker = self.request.GET.get(
images_tables.ImagesTable._meta.pagination_param, None)
reversed_order = prev_marker is not None
try: try:
(images, self._more, self._prev) = api.glance.image_list_detailed( images, self._more, self._prev = api.glance.image_list_detailed(
self.request, self.request,
marker=marker, marker=marker,
paginate=True) paginate=True,
sort_dir='asc',
sort_key='name',
reversed_order=reversed_order)
except Exception: except Exception:
images = [] images = []
self._prev = self._more = False
exceptions.handle(self.request, _("Unable to retrieve images.")) exceptions.handle(self.request, _("Unable to retrieve images."))
return images return images