From 1bde43d1c75abadf865fdfe52a990f87e9f15ecd Mon Sep 17 00:00:00 2001 From: Cindy Lu Date: Tue, 15 Jul 2014 10:10:42 -0700 Subject: [PATCH] add Previous link to Project > Stacks table Stack list API supports a sort direction and sort key which we can use to paginate back Following same convention as merged patch: https://review.openstack.org/#/c/91111/ Change-Id: I511d7d27a6234e1510c7fa3c7f84c007599f3721 Partially-implements: blueprint pagination-add-prev-link --- openstack_dashboard/api/heat.py | 15 +- .../dashboards/project/stacks/tests.py | 77 ++++++++-- .../dashboards/project/stacks/views.py | 27 +++- .../test/api_tests/heat_tests.py | 137 +++++++++++++++++- 4 files changed, 237 insertions(+), 19 deletions(-) diff --git a/openstack_dashboard/api/heat.py b/openstack_dashboard/api/heat.py index 63ff991ed4..38b7a99930 100644 --- a/openstack_dashboard/api/heat.py +++ b/openstack_dashboard/api/heat.py @@ -52,7 +52,8 @@ def heatclient(request, password=None): return client -def stacks_list(request, marker=None, paginate=False): +def stacks_list(request, marker=None, sort_dir='desc', sort_key='created_at', + paginate=False): limit = getattr(settings, 'API_RESULT_LIMIT', 1000) page_size = utils.get_page_size(request) @@ -61,20 +62,28 @@ def stacks_list(request, marker=None, paginate=False): else: request_size = limit - kwargs = {} + kwargs = {'sort_dir': sort_dir, 'sort_key': sort_key} if marker: kwargs['marker'] = marker stacks_iter = heatclient(request).stacks.list(limit=request_size, **kwargs) + has_prev_data = False has_more_data = False stacks = list(stacks_iter) + if paginate: if len(stacks) > page_size: stacks.pop() has_more_data = True - return (stacks, has_more_data) + if marker is not None: + has_prev_data = True + elif sort_dir == 'asc' and marker is not None: + has_more_data = True + elif marker is not None: + has_prev_data = True + return (stacks, has_more_data, has_prev_data) def stack_delete(request, stack_id): diff --git a/openstack_dashboard/dashboards/project/stacks/tests.py b/openstack_dashboard/dashboards/project/stacks/tests.py index cad325c8da..b36ac48fda 100644 --- a/openstack_dashboard/dashboards/project/stacks/tests.py +++ b/openstack_dashboard/dashboards/project/stacks/tests.py @@ -103,28 +103,31 @@ class StackTests(test.TestCase): @test.create_stubs({api.heat: ('stacks_list',)}) def test_index_paginated(self): stacks = self.stacks.list()[:5] - # import pdb; pdb.set_trace() api.heat.stacks_list(IsA(http.HttpRequest), marker=None, - paginate=True) \ + paginate=True, + sort_dir='desc') \ .AndReturn([stacks, - True]) + True, True]) api.heat.stacks_list(IsA(http.HttpRequest), marker=None, - paginate=True) \ + paginate=True, + sort_dir='desc') \ .AndReturn([stacks[:2], - True]) + True, True]) api.heat.stacks_list(IsA(http.HttpRequest), marker=stacks[2].id, - paginate=True) \ + paginate=True, + sort_dir='desc') \ .AndReturn([stacks[2:4], - True]) + True, True]) api.heat.stacks_list(IsA(http.HttpRequest), marker=stacks[4].id, - paginate=True) \ + paginate=True, + sort_dir='desc') \ .AndReturn([stacks[4:], - True]) + True, True]) self.mox.ReplayAll() url = reverse('horizon:project:stacks:index') @@ -153,6 +156,62 @@ class StackTests(test.TestCase): self.assertEqual(len(res.context['stacks_table'].data), 1) + @override_settings(API_RESULT_PAGE_SIZE=2) + @test.create_stubs({api.heat: ('stacks_list',)}) + def test_index_prev_paginated(self): + stacks = self.stacks.list()[:3] + + api.heat.stacks_list(IsA(http.HttpRequest), + marker=None, + paginate=True, + sort_dir='desc') \ + .AndReturn([stacks, + True, False]) + api.heat.stacks_list(IsA(http.HttpRequest), + marker=None, + paginate=True, + sort_dir='desc') \ + .AndReturn([stacks[:2], + True, True]) + api.heat.stacks_list(IsA(http.HttpRequest), + marker=stacks[2].id, + paginate=True, + sort_dir='desc') \ + .AndReturn([stacks[2:], + True, True]) + api.heat.stacks_list(IsA(http.HttpRequest), + marker=stacks[2].id, + paginate=True, + sort_dir='asc') \ + .AndReturn([stacks[:2], + True, True]) + self.mox.ReplayAll() + + url = reverse('horizon:project:stacks:index') + res = self.client.get(url) + # get all + self.assertEqual(len(res.context['stacks_table'].data), + len(stacks)) + self.assertTemplateUsed(res, 'project/stacks/index.html') + + res = self.client.get(url) + # get first page with 2 items + self.assertEqual(len(res.context['stacks_table'].data), + settings.API_RESULT_PAGE_SIZE) + + url = "%s?%s=%s" % (reverse('horizon:project:stacks:index'), + tables.StacksTable._meta.pagination_param, stacks[2].id) + res = self.client.get(url) + # get second page (item 3) + self.assertEqual(len(res.context['stacks_table'].data), 1) + + url = "%s?%s=%s" % (reverse('horizon:project:stacks:index'), + tables.StacksTable._meta.prev_pagination_param, stacks[2].id) + res = self.client.get(url) + # prev back to get first page with 2 pages + self.assertEqual(len(res.context['stacks_table'].data), + settings.API_RESULT_PAGE_SIZE) + @test.create_stubs({api.heat: ('stack_create', 'template_validate')}) def test_launch_stack(self): template = self.stack_templates.first() diff --git a/openstack_dashboard/dashboards/project/stacks/views.py b/openstack_dashboard/dashboards/project/stacks/views.py index b247215a94..e43d9db375 100644 --- a/openstack_dashboard/dashboards/project/stacks/views.py +++ b/openstack_dashboard/dashboards/project/stacks/views.py @@ -12,6 +12,7 @@ import json import logging +from operator import attrgetter from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse_lazy @@ -46,18 +47,34 @@ class IndexView(tables.DataTableView): super(IndexView, self).__init__(*args, **kwargs) self._more = None + def has_prev_data(self, table): + return self._prev + def has_more_data(self, table): return self._more def get_data(self): stacks = [] - marker = self.request.GET.get( - project_tables.StacksTable._meta.pagination_param) + prev_marker = self.request.GET.get( + project_tables.StacksTable._meta.prev_pagination_param) + if prev_marker is not None: + sort_dir = 'asc' + marker = prev_marker + else: + sort_dir = 'desc' + marker = self.request.GET.get( + project_tables.StacksTable._meta.pagination_param) try: - stacks, self._more = api.heat.stacks_list(self.request, - marker=marker, - paginate=True) + stacks, self._more, self._prev = api.heat.stacks_list( + self.request, + marker=marker, + paginate=True, + sort_dir=sort_dir) + if prev_marker is not None: + stacks = sorted(stacks, key=attrgetter('creation_time'), + reverse=True) except Exception: + self._prev = False self._more = False msg = _('Unable to retrieve stack list.') exceptions.handle(self.request, msg) diff --git a/openstack_dashboard/test/api_tests/heat_tests.py b/openstack_dashboard/test/api_tests/heat_tests.py index 0499d2f843..8753a27fcd 100644 --- a/openstack_dashboard/test/api_tests/heat_tests.py +++ b/openstack_dashboard/test/api_tests/heat_tests.py @@ -24,11 +24,144 @@ class HeatApiTests(test.APITestCase): heatclient = self.stub_heatclient() heatclient.stacks = self.mox.CreateMockAnything() - heatclient.stacks.list(limit=limit).AndReturn(iter(api_stacks)) + heatclient.stacks.list(limit=limit, + sort_dir='desc', + sort_key='created_at',) \ + .AndReturn(iter(api_stacks)) self.mox.ReplayAll() - stacks, has_more = api.heat.stacks_list(self.request) + stacks, has_more, has_prev = api.heat.stacks_list(self.request) self.assertItemsEqual(stacks, api_stacks) self.assertFalse(has_more) + self.assertFalse(has_prev) + + @override_settings(API_RESULT_PAGE_SIZE=2) + def test_stack_list_sort_options(self): + # Verify that sort_dir and sort_key work + api_stacks = self.stacks.list() + limit = getattr(settings, 'API_RESULT_LIMIT', 1000) + sort_dir = 'asc' + sort_key = 'size' + + heatclient = self.stub_heatclient() + heatclient.stacks = self.mox.CreateMockAnything() + heatclient.stacks.list(limit=limit, + sort_dir=sort_dir, + sort_key=sort_key,) \ + .AndReturn(iter(api_stacks)) + self.mox.ReplayAll() + + stacks, has_more, has_prev = api.heat.stacks_list(self.request, + sort_dir=sort_dir, + sort_key=sort_key) + self.assertItemsEqual(stacks, api_stacks) + self.assertFalse(has_more) + self.assertFalse(has_prev) + + @override_settings(API_RESULT_PAGE_SIZE=20) + def test_stack_list_pagination_less_page_size(self): + api_stacks = self.stacks.list() + page_size = settings.API_RESULT_PAGE_SIZE + sort_dir = 'desc' + sort_key = 'created_at' + + heatclient = self.stub_heatclient() + heatclient.stacks = self.mox.CreateMockAnything() + heatclient.stacks.list(limit=page_size + 1, + sort_dir=sort_dir, + sort_key=sort_key,) \ + .AndReturn(iter(api_stacks)) + self.mox.ReplayAll() + + stacks, has_more, has_prev = api.heat.stacks_list(self.request, + sort_dir=sort_dir, + sort_key=sort_key, + paginate=True) + expected_stacks = api_stacks[:page_size] + self.assertItemsEqual(stacks, expected_stacks) + self.assertFalse(has_more) + self.assertFalse(has_prev) + + @override_settings(API_RESULT_PAGE_SIZE=10) + def test_stack_list_pagination_equal_page_size(self): + api_stacks = self.stacks.list() + page_size = settings.API_RESULT_PAGE_SIZE + sort_dir = 'desc' + sort_key = 'created_at' + + heatclient = self.stub_heatclient() + heatclient.stacks = self.mox.CreateMockAnything() + heatclient.stacks.list(limit=page_size + 1, + sort_dir=sort_dir, + sort_key=sort_key,) \ + .AndReturn(iter(api_stacks)) + self.mox.ReplayAll() + + stacks, has_more, has_prev = api.heat.stacks_list(self.request, + sort_dir=sort_dir, + sort_key=sort_key, + paginate=True) + expected_stacks = api_stacks[:page_size] + self.assertItemsEqual(stacks, expected_stacks) + self.assertFalse(has_more) + self.assertFalse(has_prev) + + @override_settings(API_RESULT_PAGE_SIZE=2) + def test_stack_list_pagination_marker(self): + page_size = getattr(settings, 'API_RESULT_PAGE_SIZE', 20) + sort_dir = 'desc' + sort_key = 'created_at' + marker = 'nonsense' + + api_stacks = self.stacks.list() + + heatclient = self.stub_heatclient() + heatclient.stacks = self.mox.CreateMockAnything() + heatclient.stacks.list(limit=page_size + 1, + marker=marker, + sort_dir=sort_dir, + sort_key=sort_key,) \ + .AndReturn(iter(api_stacks[:page_size + 1])) + self.mox.ReplayAll() + + stacks, has_more, has_prev = api.heat.stacks_list(self.request, + marker=marker, + paginate=True, + sort_dir=sort_dir, + sort_key=sort_key,) + + self.assertEqual(len(stacks), page_size) + self.assertItemsEqual(stacks, api_stacks[:page_size]) + self.assertTrue(has_more) + self.assertTrue(has_prev) + + @override_settings(API_RESULT_PAGE_SIZE=2) + def test_stack_list_pagination_marker_prev(self): + page_size = getattr(settings, 'API_RESULT_PAGE_SIZE', 20) + sort_dir = 'asc' + sort_key = 'created_at' + marker = 'nonsense' + + api_stacks = self.stacks.list() + + heatclient = self.stub_heatclient() + heatclient.stacks = self.mox.CreateMockAnything() + heatclient.stacks.list(limit=page_size + 1, + marker=marker, + sort_dir=sort_dir, + sort_key=sort_key,) \ + .AndReturn(iter(api_stacks[:page_size + 1])) + self.mox.ReplayAll() + + stacks, has_more, has_prev = api.heat.stacks_list(self.request, + marker=marker, + paginate=True, + sort_dir=sort_dir, + sort_key=sort_key,) + + self.assertEqual(len(stacks), page_size) + self.assertItemsEqual(stacks, api_stacks[:page_size]) + self.assertTrue(has_more) + self.assertTrue(has_prev) def test_template_get(self): api_stacks = self.stacks.list()