Merge "Add pages menu to volume backups pagination"

This commit is contained in:
Zuul 2020-05-28 19:26:01 +00:00 committed by Gerrit Code Review
commit 1e7dc763b7
9 changed files with 205 additions and 46 deletions

View File

@ -31,6 +31,7 @@ from horizon.tables.views import MixedDataTableView
from horizon.tables.views import MultiTableMixin from horizon.tables.views import MultiTableMixin
from horizon.tables.views import MultiTableView from horizon.tables.views import MultiTableView
from horizon.tables.views import PagedTableMixin from horizon.tables.views import PagedTableMixin
from horizon.tables.views import PagedTableWithPageMenu
__all__ = [ __all__ = [
@ -50,4 +51,5 @@ __all__ = [
'MultiTableMixin', 'MultiTableMixin',
'MultiTableView', 'MultiTableView',
'PagedTableMixin', 'PagedTableMixin',
'PagedTableWithPageMenu',
] ]

View File

@ -389,3 +389,47 @@ class PagedTableMixin(object):
if marker: if marker:
return marker, "desc" return marker, "desc"
return None, "desc" return None, "desc"
class PagedTableWithPageMenu(object):
def __init__(self, *args, **kwargs):
super(PagedTableWithPageMenu, self).__init__(*args, **kwargs)
self._current_page = 1
self._number_of_pages = 0
self._total_of_entries = 0
self._page_size = 0
def handle_table(self, table):
name = table.name
self._tables[name]._meta.current_page = self.current_page
self._tables[name]._meta.number_of_pages = self.number_of_pages
return super(PagedTableWithPageMenu, self).handle_table(table)
def has_prev_data(self, table):
return self._current_page > 1
def has_more_data(self, table):
return self._current_page < self._number_of_pages
def current_page(self, table=None):
return self._current_page
def number_of_pages(self, table=None):
return self._number_of_pages
def current_offset(self, table):
return self._current_page * self._page_size + 1
def get_page_param(self, table):
try:
meta = self.table_class._meta
except AttributeError:
meta = self.table_classes[0]._meta
return meta.pagination_param
def _get_page_number(self):
page_number = self.request.GET.get(self.get_page_param(None), None)
if page_number:
return int(page_number)
return 1

View File

@ -24,7 +24,11 @@
{% endif %} {% endif %}
{% endblock table_breadcrumb %} {% endblock table_breadcrumb %}
{% if table.footer and rows %} {% if table.footer and rows %}
{% include "horizon/common/_data_table_pagination.html" %} {% if table.number_of_pages is defined %}
{% include "horizon/common/_data_table_pagination.html" %}
{% else %}
{% include "horizon/common/_data_table_pagination_with_pages.html" %}
{% endif %}
{% endif %} {% endif %}
{% block table_columns %} {% block table_columns %}
{% if not table.is_browser_table %} {% if not table.is_browser_table %}
@ -72,7 +76,11 @@
{% endfor %} {% endfor %}
</tr> </tr>
{% endif %} {% endif %}
{% include "horizon/common/_data_table_pagination.html" %} {% if table.number_of_pages is defined %}
{% include "horizon/common/_data_table_pagination.html" %}
{% else %}
{% include "horizon/common/_data_table_pagination_with_pages.html" %}
{% endif %}
</tfoot> </tfoot>
{% endif %} {% endif %}
{% endblock table_footer %} {% endblock table_footer %}

View File

@ -0,0 +1,27 @@
{% load i18n %}
{% load form_helpers %}
<tr>
<td colspan="{{ columns|length }}">
<span class="table_count">{% blocktrans count counter=rows|length trimmed %}
Displaying {{ counter }} item{% plural %}
Displaying {{ counter }} items{% endblocktrans %}</span>
{% if table.has_prev_data or table.has_more_data %}
<span class="spacer">|</span>
{% endif %}
{% if table.has_prev_data %}
<a href="{{ table.get_pagination_string }}1">{% trans "&laquo;&laquo;&nbsp;First" %}</a>
<a href="{{ table.get_pagination_string }}{{ table.current_page | add:-1 }}">{% trans "&laquo;&nbsp;Prev&nbsp;" %}</a>
{% endif %}
{% for page in table.number_of_pages|get_range %}
{% if table.current_page == page %}
<span>{{page}}&nbsp;</span>
{% else %}
<a href="{{ table.get_pagination_string }}{{page}}">{{page}}&nbsp;</a>
{% endif %}
{% endfor %}
{% if table.has_more_data %}
<a href="{{ table.get_pagination_string }}{{ table.current_page | add:1 }}">{% trans "Next&nbsp;&raquo;" %}</a>
<a href="{{ table.get_pagination_string }}{{ table.number_of_pages }}">{% trans "Last&nbsp;&raquo;&raquo;" %}</a>
{% endif %}
</td>
</tr>

View File

@ -80,3 +80,10 @@ def wrapper_classes(field):
if is_multiple_checkbox(field): if is_multiple_checkbox(field):
classes.append('multiple-checkbox') classes.append('multiple-checkbox')
return ' '.join(classes) return ' '.join(classes)
@register.filter
def get_range(val):
if val:
return range(1, val + 1)
return []

View File

@ -21,6 +21,7 @@
from __future__ import absolute_import from __future__ import absolute_import
import logging import logging
import math
from django.conf import settings from django.conf import settings
from django.utils.translation import pgettext_lazy from django.utils.translation import pgettext_lazy
@ -585,6 +586,42 @@ def volume_backup_list(request):
return backups return backups
@profiler.trace
def volume_backup_list_paged_with_page_menu(request, page_number=1,
sort_dir="desc"):
backups = []
count = 0
pages_count = 0
page_size = utils.get_page_size(request)
c_client = cinderclient(request, '3.45')
if c_client is None:
return backups, 0, count, pages_count
if VERSIONS.active > 1:
offset = (page_number - 1) * page_size
sort = 'created_at:' + sort_dir
bkps, count = c_client.backups.list(limit=page_size,
sort=sort,
search_opts={'with_count': True,
'offset': offset})
if not bkps:
return backups, page_size, count, pages_count
if isinstance(bkps[0], list):
bkps = bkps[0]
pages_count = int(math.ceil(float(count) / float(page_size)))
for b in bkps:
backups.append(VolumeBackup(b))
return backups, page_size, count, pages_count
else:
for b in c_client.backups.list():
backups.append(VolumeBackup(b))
return backups, 0, count, pages_count
@profiler.trace @profiler.trace
def volume_backup_list_paged(request, marker=None, paginate=False, def volume_backup_list_paged(request, marker=None, paginate=False,
sort_dir="desc"): sort_dir="desc"):

View File

@ -178,11 +178,19 @@ class BackupsTable(tables.DataTable):
verbose_name=_("Snapshot"), verbose_name=_("Snapshot"),
link="horizon:project:snapshots:detail") link="horizon:project:snapshots:detail")
def current_page(self):
return self._meta.current_page()
def number_of_pages(self):
return self._meta.number_of_pages()
def get_pagination_string(self):
return '?%s=' % self._meta.pagination_param
class Meta(object): class Meta(object):
name = "volume_backups" name = "volume_backups"
verbose_name = _("Volume Backups") verbose_name = _("Volume Backups")
pagination_param = 'backup_marker' pagination_param = 'page'
prev_pagination_param = 'prev_backup_marker'
status_columns = ("status",) status_columns = ("status",)
row_class = UpdateRow row_class = UpdateRow
table_actions = (DeleteBackup,) table_actions = (DeleteBackup,)

View File

@ -28,11 +28,13 @@ INDEX_URL = reverse('horizon:project:backups:index')
class VolumeBackupsViewTests(test.TestCase): class VolumeBackupsViewTests(test.TestCase):
@test.create_mocks({api.cinder: ('volume_list', 'volume_snapshot_list', @test.create_mocks({api.cinder: ('volume_list', 'volume_snapshot_list',
'volume_backup_list_paged')}) 'volume_backup_list_paged_with_page_menu')
def _test_backups_index_paginated(self, marker, sort_dir, backups, url, })
has_more, has_prev): def _test_backups_index_paginated(self, page_number, backups,
self.mock_volume_backup_list_paged.return_value = [backups, url, page_size, total_of_entries,
has_more, has_prev] number_of_pages, has_prev, has_more):
self.mock_volume_backup_list_paged_with_page_menu.return_value = [
backups, page_size, total_of_entries, number_of_pages]
self.mock_volume_list.return_value = self.cinder_volumes.list() self.mock_volume_list.return_value = self.cinder_volumes.list()
self.mock_volume_snapshot_list.return_value \ self.mock_volume_snapshot_list.return_value \
= self.cinder_volume_snapshots.list() = self.cinder_volume_snapshots.list()
@ -41,9 +43,17 @@ class VolumeBackupsViewTests(test.TestCase):
self.assertEqual(res.status_code, 200) self.assertEqual(res.status_code, 200)
self.assertTemplateUsed(res, 'horizon/common/_data_table_view.html') self.assertTemplateUsed(res, 'horizon/common/_data_table_view.html')
self.mock_volume_backup_list_paged.assert_called_once_with( self.assertEqual(has_more,
test.IsHttpRequest(), marker=marker, sort_dir=sort_dir, res.context_data['view'].has_more_data(None))
paginate=True) self.assertEqual(has_prev,
res.context_data['view'].has_prev_data(None))
self.assertEqual(
page_number, res.context_data['view'].current_page(None))
self.assertEqual(
number_of_pages, res.context_data['view'].number_of_pages(None))
self.mock_volume_backup_list_paged_with_page_menu.\
assert_called_once_with(test.IsHttpRequest(),
page_number=page_number)
self.mock_volume_list.assert_called_once_with(test.IsHttpRequest()) self.mock_volume_list.assert_called_once_with(test.IsHttpRequest())
self.mock_volume_snapshot_list.assert_called_once_with( self.mock_volume_snapshot_list.assert_called_once_with(
test.IsHttpRequest()) test.IsHttpRequest())
@ -55,34 +65,38 @@ class VolumeBackupsViewTests(test.TestCase):
expected_snapshosts = self.cinder_volume_snapshots.list() expected_snapshosts = self.cinder_volume_snapshots.list()
size = settings.API_RESULT_PAGE_SIZE size = settings.API_RESULT_PAGE_SIZE
base_url = INDEX_URL base_url = INDEX_URL
next = backup_tables.BackupsTable._meta.pagination_param number_of_pages = len(backups)
pag = backup_tables.BackupsTable._meta.pagination_param
page_number = 1
# get first page # get first page
expected_backups = backups[:size] expected_backups = backups[:size]
res = self._test_backups_index_paginated( res = self._test_backups_index_paginated(
marker=None, sort_dir="desc", backups=expected_backups, page_number=page_number, backups=expected_backups, url=base_url,
url=base_url, has_more=True, has_prev=False) has_more=True, has_prev=False, page_size=size,
number_of_pages=number_of_pages, total_of_entries=number_of_pages)
result = res.context['volume_backups_table'].data result = res.context['volume_backups_table'].data
self.assertCountEqual(result, expected_backups) self.assertCountEqual(result, expected_backups)
# get second page # get second page
expected_backups = backups[size:2 * size] expected_backups = backups[size:2 * size]
marker = expected_backups[0].id page_number = 2
url = base_url + "?%s=%s" % (pag, page_number)
url = base_url + "?%s=%s" % (next, marker)
res = self._test_backups_index_paginated( res = self._test_backups_index_paginated(
marker=marker, sort_dir="desc", backups=expected_backups, url=url, page_number=page_number, backups=expected_backups, url=url,
has_more=True, has_prev=True) has_more=True, has_prev=True, page_size=size,
number_of_pages=number_of_pages, total_of_entries=number_of_pages)
result = res.context['volume_backups_table'].data result = res.context['volume_backups_table'].data
self.assertCountEqual(result, expected_backups) self.assertCountEqual(result, expected_backups)
self.assertEqual(result[0].snapshot.id, expected_snapshosts[1].id) self.assertEqual(result[0].snapshot.id, expected_snapshosts[1].id)
# get last page # get last page
expected_backups = backups[-size:] expected_backups = backups[-size:]
marker = expected_backups[0].id page_number = 3
url = base_url + "?%s=%s" % (next, marker) url = base_url + "?%s=%s" % (pag, page_number)
res = self._test_backups_index_paginated( res = self._test_backups_index_paginated(
marker=marker, sort_dir="desc", backups=expected_backups, url=url, page_number=page_number, backups=expected_backups, url=url,
has_more=False, has_prev=True) has_more=False, has_prev=True, page_size=size,
number_of_pages=number_of_pages, total_of_entries=number_of_pages)
result = res.context['volume_backups_table'].data result = res.context['volume_backups_table'].data
self.assertCountEqual(result, expected_backups) self.assertCountEqual(result, expected_backups)
@ -90,26 +104,29 @@ class VolumeBackupsViewTests(test.TestCase):
def test_backups_index_paginated_prev_page(self): def test_backups_index_paginated_prev_page(self):
backups = self.cinder_volume_backups.list() backups = self.cinder_volume_backups.list()
size = settings.API_RESULT_PAGE_SIZE size = settings.API_RESULT_PAGE_SIZE
number_of_pages = len(backups)
base_url = INDEX_URL base_url = INDEX_URL
prev = backup_tables.BackupsTable._meta.prev_pagination_param pag = backup_tables.BackupsTable._meta.pagination_param
# prev from some page # prev from some page
expected_backups = backups[size:2 * size] expected_backups = backups[size:2 * size]
marker = expected_backups[0].id page_number = 2
url = base_url + "?%s=%s" % (prev, marker) url = base_url + "?%s=%s" % (pag, page_number)
res = self._test_backups_index_paginated( res = self._test_backups_index_paginated(
marker=marker, sort_dir="asc", backups=expected_backups, url=url, page_number=page_number, backups=expected_backups, url=url,
has_more=True, has_prev=True) has_more=True, has_prev=True, page_size=size,
number_of_pages=number_of_pages, total_of_entries=number_of_pages)
result = res.context['volume_backups_table'].data result = res.context['volume_backups_table'].data
self.assertCountEqual(result, expected_backups) self.assertCountEqual(result, expected_backups)
# back to first page # back to first page
expected_backups = backups[:size] expected_backups = backups[:size]
marker = expected_backups[0].id page_number = 1
url = base_url + "?%s=%s" % (prev, marker) url = base_url + "?%s=%s" % (pag, page_number)
res = self._test_backups_index_paginated( res = self._test_backups_index_paginated(
marker=marker, sort_dir="asc", backups=expected_backups, url=url, page_number=page_number, backups=expected_backups, url=url,
has_more=True, has_prev=False) has_more=True, has_prev=False, page_size=size,
number_of_pages=number_of_pages, total_of_entries=number_of_pages)
result = res.context['volume_backups_table'].data result = res.context['volume_backups_table'].data
self.assertCountEqual(result, expected_backups) self.assertCountEqual(result, expected_backups)
@ -267,16 +284,20 @@ class VolumeBackupsViewTests(test.TestCase):
@test.create_mocks({api.cinder: ('volume_list', @test.create_mocks({api.cinder: ('volume_list',
'volume_snapshot_list', 'volume_snapshot_list',
'volume_backup_list_paged', 'volume_backup_list_paged_with_page_menu',
'volume_backup_delete')}) 'volume_backup_delete')})
def test_delete_volume_backup(self): def test_delete_volume_backup(self):
vol_backups = self.cinder_volume_backups.list() vol_backups = self.cinder_volume_backups.list()
volumes = self.cinder_volumes.list() volumes = self.cinder_volumes.list()
backup = self.cinder_volume_backups.first() backup = self.cinder_volume_backups.first()
snapshots = self.cinder_volume_snapshots.list() snapshots = self.cinder_volume_snapshots.list()
page_number = 1
page_size = 1
total_of_entries = 1
number_of_pages = 1
self.mock_volume_backup_list_paged.return_value = [vol_backups, self.mock_volume_backup_list_paged_with_page_menu.return_value = [
False, False] vol_backups, page_size, total_of_entries, number_of_pages]
self.mock_volume_list.return_value = volumes self.mock_volume_list.return_value = volumes
self.mock_volume_backup_delete.return_value = None self.mock_volume_backup_delete.return_value = None
self.mock_volume_snapshot_list.return_value = snapshots self.mock_volume_snapshot_list.return_value = snapshots
@ -286,9 +307,9 @@ class VolumeBackupsViewTests(test.TestCase):
self.assertRedirectsNoFollow(res, INDEX_URL) self.assertRedirectsNoFollow(res, INDEX_URL)
self.assertMessageCount(success=1) self.assertMessageCount(success=1)
self.mock_volume_backup_list_paged.assert_called_once_with( self.mock_volume_backup_list_paged_with_page_menu.\
test.IsHttpRequest(), marker=None, sort_dir='desc', assert_called_once_with(test.IsHttpRequest(),
paginate=True) page_number=page_number)
self.mock_volume_list.assert_called_once_with(test.IsHttpRequest()) self.mock_volume_list.assert_called_once_with(test.IsHttpRequest())
self.mock_volume_snapshot_list.assert_called_once_with( self.mock_volume_snapshot_list.assert_called_once_with(
test.IsHttpRequest()) test.IsHttpRequest())

View File

@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import logging
from django.urls import reverse from django.urls import reverse
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -30,8 +32,10 @@ from openstack_dashboard.dashboards.project.backups \
from openstack_dashboard.dashboards.project.volumes \ from openstack_dashboard.dashboards.project.volumes \
import views as volume_views import views as volume_views
LOG = logging.getLogger(__name__)
class BackupsView(tables.DataTableView, tables.PagedTableMixin,
class BackupsView(tables.PagedTableWithPageMenu, tables.DataTableView,
volume_views.VolumeTableMixIn): volume_views.VolumeTableMixIn):
table_class = backup_tables.BackupsTable table_class = backup_tables.BackupsTable
page_title = _("Volume Backups") page_title = _("Volume Backups")
@ -41,11 +45,11 @@ class BackupsView(tables.DataTableView, tables.PagedTableMixin,
def get_data(self): def get_data(self):
try: try:
marker, sort_dir = self._get_marker() self._current_page = self._get_page_number()
backups, self._has_more_data, self._has_prev_data = \ (backups, self._page_size, self._total_of_entries,
api.cinder.volume_backup_list_paged( self._number_of_pages) = \
self.request, marker=marker, sort_dir=sort_dir, api.cinder.volume_backup_list_paged_with_page_menu(
paginate=True) self.request, page_number=self._current_page)
volumes = api.cinder.volume_list(self.request) volumes = api.cinder.volume_list(self.request)
volumes = dict((v.id, v) for v in volumes) volumes = dict((v.id, v) for v in volumes)
snapshots = api.cinder.volume_snapshot_list(self.request) snapshots = api.cinder.volume_snapshot_list(self.request)
@ -53,7 +57,8 @@ class BackupsView(tables.DataTableView, tables.PagedTableMixin,
for backup in backups: for backup in backups:
backup.volume = volumes.get(backup.volume_id) backup.volume = volumes.get(backup.volume_id)
backup.snapshot = snapshots.get(backup.snapshot_id) backup.snapshot = snapshots.get(backup.snapshot_id)
except Exception: except Exception as e:
LOG.exception(e)
backups = [] backups = []
exceptions.handle(self.request, _("Unable to retrieve " exceptions.handle(self.request, _("Unable to retrieve "
"volume backups.")) "volume backups."))