diff --git a/horizon/tables/__init__.py b/horizon/tables/__init__.py index 0b62dbf4f2..3cc3344d7e 100644 --- a/horizon/tables/__init__.py +++ b/horizon/tables/__init__.py @@ -31,3 +31,4 @@ from horizon.tables.views import DataTableView # noqa from horizon.tables.views import MixedDataTableView # noqa from horizon.tables.views import MultiTableMixin # noqa from horizon.tables.views import MultiTableView # noqa +from horizon.tables.views import PagedTableMixin # noqa diff --git a/horizon/tables/views.py b/horizon/tables/views.py index 6d78b5cfec..670e7f4d84 100644 --- a/horizon/tables/views.py +++ b/horizon/tables/views.py @@ -357,3 +357,30 @@ class MixedDataTableView(DataTableView): 'in table %s to use MixedDataTableView.' % self.table._meta.name) return self.table + + +class PagedTableMixin(object): + def __init__(self, *args, **kwargs): + super(PagedTableMixin, self).__init__(*args, **kwargs) + self._has_prev_data = False + self._has_more_data = False + + def has_prev_data(self, table): + return self._has_prev_data + + def has_more_data(self, table): + return self._has_more_data + + def _get_marker(self): + try: + meta = self.table_class._meta + except AttributeError: + meta = self.table_classes[0]._meta + prev_marker = self.request.GET.get(meta.prev_pagination_param, None) + if prev_marker: + return prev_marker, "asc" + else: + marker = self.request.GET.get(meta.pagination_param, None) + if marker: + return marker, "desc" + return None, "desc" diff --git a/openstack_dashboard/dashboards/admin/volumes/snapshots/tables.py b/openstack_dashboard/dashboards/admin/volumes/snapshots/tables.py index 1a5c5847ba..30855f6ceb 100644 --- a/openstack_dashboard/dashboards/admin/volumes/snapshots/tables.py +++ b/openstack_dashboard/dashboards/admin/volumes/snapshots/tables.py @@ -18,7 +18,7 @@ from horizon import tables from openstack_dashboard.api import cinder from openstack_dashboard.api import keystone -from openstack_dashboard.dashboards.project.volumes.snapshots \ +from openstack_dashboard.dashboards.project.snapshots \ import tables as snapshots_tables from openstack_dashboard.dashboards.project.volumes.volumes \ import tables as volumes_tables diff --git a/openstack_dashboard/dashboards/admin/volumes/snapshots/tabs.py b/openstack_dashboard/dashboards/admin/volumes/snapshots/tabs.py index c387f0b13b..bee6d525f3 100644 --- a/openstack_dashboard/dashboards/admin/volumes/snapshots/tabs.py +++ b/openstack_dashboard/dashboards/admin/volumes/snapshots/tabs.py @@ -15,14 +15,14 @@ from django.utils.translation import ugettext_lazy as _ from horizon import tabs -from openstack_dashboard.dashboards.project.volumes.snapshots \ +from openstack_dashboard.dashboards.project.snapshots \ import tabs as overview_tab class OverviewTab(overview_tab.OverviewTab): name = _("Overview") slug = "overview" - template_name = ("project/volumes/snapshots/_detail_overview.html") + template_name = ("project/snapshots/_detail_overview.html") def get_redirect_url(self): return reverse('horizon:admin:volumes:index') diff --git a/openstack_dashboard/dashboards/admin/volumes/snapshots/views.py b/openstack_dashboard/dashboards/admin/volumes/snapshots/views.py index 8dc0eb0533..f0189dd2a3 100644 --- a/openstack_dashboard/dashboards/admin/volumes/snapshots/views.py +++ b/openstack_dashboard/dashboards/admin/volumes/snapshots/views.py @@ -24,7 +24,7 @@ from openstack_dashboard.dashboards.admin.volumes.snapshots \ import forms as vol_snapshot_forms from openstack_dashboard.dashboards.admin.volumes.snapshots \ import tabs as vol_snapshot_tabs -from openstack_dashboard.dashboards.project.volumes.snapshots \ +from openstack_dashboard.dashboards.project.snapshots \ import views diff --git a/openstack_dashboard/dashboards/admin/volumes/tabs.py b/openstack_dashboard/dashboards/admin/volumes/tabs.py index e40dd1d021..6480741a77 100644 --- a/openstack_dashboard/dashboards/admin/volumes/tabs.py +++ b/openstack_dashboard/dashboards/admin/volumes/tabs.py @@ -33,7 +33,7 @@ from openstack_dashboard.dashboards.project.volumes \ import tabs as volumes_tabs -class VolumeTab(volumes_tabs.PagedTableMixin, tabs.TableTab, +class VolumeTab(tables.PagedTableMixin, tabs.TableTab, volumes_tabs.VolumeTableMixIn, tables.DataTableView): table_classes = (volumes_tables.VolumesTable,) name = _("Volumes") @@ -159,7 +159,7 @@ class VolumeTypesTab(tabs.TableTab, volumes_tabs.VolumeTableMixIn): return qos_specs -class SnapshotTab(volumes_tabs.PagedTableMixin, tabs.TableTab): +class SnapshotTab(tables.PagedTableMixin, tabs.TableTab): table_classes = (snapshots_tables.VolumeSnapshotsTable,) name = _("Volume Snapshots") slug = "snapshots_tab" diff --git a/openstack_dashboard/dashboards/admin/volumes/tests.py b/openstack_dashboard/dashboards/admin/volumes/tests.py index b704c464cf..c81619507e 100644 --- a/openstack_dashboard/dashboards/admin/volumes/tests.py +++ b/openstack_dashboard/dashboards/admin/volumes/tests.py @@ -24,7 +24,7 @@ from mox3.mox import IsA # noqa from openstack_dashboard import api from openstack_dashboard.api import cinder from openstack_dashboard.api import keystone -from openstack_dashboard.dashboards.project.volumes.snapshots \ +from openstack_dashboard.dashboards.project.snapshots \ import tables as snapshot_tables from openstack_dashboard.dashboards.project.volumes.volumes \ import tables as volume_tables diff --git a/openstack_dashboard/dashboards/project/volumes/snapshots/__init__.py b/openstack_dashboard/dashboards/project/snapshots/__init__.py similarity index 100% rename from openstack_dashboard/dashboards/project/volumes/snapshots/__init__.py rename to openstack_dashboard/dashboards/project/snapshots/__init__.py diff --git a/openstack_dashboard/dashboards/project/volumes/snapshots/forms.py b/openstack_dashboard/dashboards/project/snapshots/forms.py similarity index 96% rename from openstack_dashboard/dashboards/project/volumes/snapshots/forms.py rename to openstack_dashboard/dashboards/project/snapshots/forms.py index 09085b3e6b..77c2f0cc82 100644 --- a/openstack_dashboard/dashboards/project/volumes/snapshots/forms.py +++ b/openstack_dashboard/dashboards/project/snapshots/forms.py @@ -40,7 +40,7 @@ class UpdateForm(forms.SelfHandlingForm): messages.info(request, message) return True except Exception: - redirect = reverse("horizon:project:volumes:index") + redirect = reverse("horizon:project:snapshots:index") exceptions.handle(request, _('Unable to update volume snapshot.'), redirect=redirect) diff --git a/openstack_dashboard/dashboards/project/snapshots/panel.py b/openstack_dashboard/dashboards/project/snapshots/panel.py new file mode 100644 index 0000000000..a14e176c33 --- /dev/null +++ b/openstack_dashboard/dashboards/project/snapshots/panel.py @@ -0,0 +1,26 @@ +# Copyright 2017 Rackspace, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.utils.translation import ugettext_lazy as _ + +import horizon + + +class Snapshots(horizon.Panel): + name = _("Snapshots") + slug = 'snapshots' + permissions = ( + ('openstack.services.volume', 'openstack.services.volumev2'), + ) + policy_rules = (("volume", "volume:get_all_snapshots"),) diff --git a/openstack_dashboard/dashboards/project/volumes/snapshots/tables.py b/openstack_dashboard/dashboards/project/snapshots/tables.py similarity index 97% rename from openstack_dashboard/dashboards/project/volumes/snapshots/tables.py rename to openstack_dashboard/dashboards/project/snapshots/tables.py index ca565c5f2b..f0c7ecfd3a 100644 --- a/openstack_dashboard/dashboards/project/volumes/snapshots/tables.py +++ b/openstack_dashboard/dashboards/project/snapshots/tables.py @@ -52,7 +52,7 @@ class LaunchSnapshot(volume_tables.LaunchVolume): class LaunchSnapshotNG(LaunchSnapshot): name = "launch_snapshot_ng" verbose_name = _("Launch as Instance") - url = "horizon:project:volumes:snapshots_tab" + url = "horizon:project:snapshots:index" classes = ("btn-launch", ) ajax = False @@ -102,7 +102,7 @@ class DeleteVolumeSnapshot(policy.PolicyTargetMixin, tables.DeleteAction): class EditVolumeSnapshot(policy.PolicyTargetMixin, tables.LinkAction): name = "edit" verbose_name = _("Edit Snapshot") - url = "horizon:project:volumes:snapshots:update" + url = "horizon:project:snapshots:update" classes = ("ajax-modal",) icon = "pencil" policy_rules = (("volume", "volume:update_snapshot"),) @@ -189,7 +189,7 @@ class VolumeSnapshotsTable(volume_tables.VolumesTableBase): name = tables.WrappingColumn( "name", verbose_name=_("Name"), - link="horizon:project:volumes:snapshots:detail") + link="horizon:project:snapshots:detail") volume_name = SnapshotVolumeNameColumn( "name", verbose_name=_("Volume Name"), @@ -213,6 +213,6 @@ class VolumeSnapshotsTable(volume_tables.VolumesTableBase): UpdateMetadata)) row_class = UpdateRow status_columns = ("status",) - permissions = [( + permissions = [ ('openstack.services.volume', 'openstack.services.volumev2'), - )] + ] diff --git a/openstack_dashboard/dashboards/project/volumes/snapshots/tabs.py b/openstack_dashboard/dashboards/project/snapshots/tabs.py similarity index 92% rename from openstack_dashboard/dashboards/project/volumes/snapshots/tabs.py rename to openstack_dashboard/dashboards/project/snapshots/tabs.py index c3e0a018b7..7fcd8b3299 100644 --- a/openstack_dashboard/dashboards/project/volumes/snapshots/tabs.py +++ b/openstack_dashboard/dashboards/project/snapshots/tabs.py @@ -24,7 +24,7 @@ from openstack_dashboard.api import cinder class OverviewTab(tabs.Tab): name = _("Overview") slug = "overview" - template_name = ("project/volumes/snapshots/_detail_overview.html") + template_name = ("project/snapshots/_detail_overview.html") def get_context_data(self, request): try: @@ -39,7 +39,7 @@ class OverviewTab(tabs.Tab): "volume": volume} def get_redirect_url(self): - return reverse('horizon:project:volumes:index') + return reverse('horizon:project:snapshots:index') class SnapshotDetailTabs(tabs.TabGroup): diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/snapshots/_detail_overview.html b/openstack_dashboard/dashboards/project/snapshots/templates/snapshots/_detail_overview.html similarity index 100% rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/snapshots/_detail_overview.html rename to openstack_dashboard/dashboards/project/snapshots/templates/snapshots/_detail_overview.html diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/snapshots/_update.html b/openstack_dashboard/dashboards/project/snapshots/templates/snapshots/_update.html similarity index 100% rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/snapshots/_update.html rename to openstack_dashboard/dashboards/project/snapshots/templates/snapshots/_update.html diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/snapshots/update.html b/openstack_dashboard/dashboards/project/snapshots/templates/snapshots/update.html similarity index 100% rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/snapshots/update.html rename to openstack_dashboard/dashboards/project/snapshots/templates/snapshots/update.html diff --git a/openstack_dashboard/dashboards/project/volumes/snapshots/tests.py b/openstack_dashboard/dashboards/project/snapshots/tests.py similarity index 62% rename from openstack_dashboard/dashboards/project/volumes/snapshots/tests.py rename to openstack_dashboard/dashboards/project/snapshots/tests.py index d7975b5566..1169b92436 100644 --- a/openstack_dashboard/dashboards/project/volumes/snapshots/tests.py +++ b/openstack_dashboard/dashboards/project/snapshots/tests.py @@ -16,21 +16,112 @@ # License for the specific language governing permissions and limitations # under the License. +from django.conf import settings from django.core.urlresolvers import reverse from django import http +from django.test.utils import override_settings +from django.utils.http import urlunquote from mox3.mox import IsA # noqa from openstack_dashboard import api from openstack_dashboard.api import cinder +from openstack_dashboard.dashboards.project.snapshots \ + import tables as snapshot_tables from openstack_dashboard.test import helpers as test from openstack_dashboard.usage import quotas -INDEX_URL = reverse('horizon:project:volumes:index') -VOLUME_SNAPSHOTS_TAB_URL = reverse('horizon:project:volumes:snapshots_tab') +INDEX_URL = reverse('horizon:project:snapshots:index') class VolumeSnapshotsViewTests(test.TestCase): + @test.create_stubs({api.cinder: ('tenant_absolute_limits', + 'volume_snapshot_list_paged', + 'volume_list',), + api.base: ('is_service_enabled',)}) + def _test_snapshots_index_paginated(self, marker, sort_dir, snapshots, url, + has_more, has_prev): + api.base.is_service_enabled(IsA(http.HttpRequest), 'volumev2') \ + .AndReturn(True) + api.base.is_service_enabled(IsA(http.HttpRequest), 'volume') \ + .AndReturn(True) + api.cinder.volume_snapshot_list_paged( + IsA(http.HttpRequest), marker=marker, sort_dir=sort_dir, + paginate=True).AndReturn([snapshots, has_more, has_prev]) + api.cinder.volume_list(IsA(http.HttpRequest)).AndReturn( + self.cinder_volumes.list()) + self.mox.ReplayAll() + + res = self.client.get(urlunquote(url)) + self.assertEqual(res.status_code, 200) + self.assertTemplateUsed(res, 'horizon/common/_data_table_view.html') + + self.mox.UnsetStubs() + return res + + @override_settings(API_RESULT_PAGE_SIZE=1) + def test_snapshots_index_paginated(self): + mox_snapshots = self.cinder_volume_snapshots.list() + size = settings.API_RESULT_PAGE_SIZE + base_url = INDEX_URL + next = snapshot_tables.VolumeSnapshotsTable._meta.pagination_param + + # get first page + expected_snapshots = mox_snapshots[:size] + res = self._test_snapshots_index_paginated( + marker=None, sort_dir="desc", snapshots=expected_snapshots, + url=base_url, has_more=True, has_prev=False) + snapshots = res.context['volume_snapshots_table'].data + self.assertItemsEqual(snapshots, expected_snapshots) + + # get second page + expected_snapshots = mox_snapshots[size:2 * size] + marker = expected_snapshots[0].id + + url = base_url + "?%s=%s" % (next, marker) + res = self._test_snapshots_index_paginated( + marker=marker, sort_dir="desc", snapshots=expected_snapshots, + url=url, has_more=True, has_prev=True) + snapshots = res.context['volume_snapshots_table'].data + self.assertItemsEqual(snapshots, expected_snapshots) + + # get last page + expected_snapshots = mox_snapshots[-size:] + marker = expected_snapshots[0].id + url = base_url + "?%s=%s" % (next, marker) + res = self._test_snapshots_index_paginated( + marker=marker, sort_dir="desc", snapshots=expected_snapshots, + url=url, has_more=False, has_prev=True) + snapshots = res.context['volume_snapshots_table'].data + self.assertItemsEqual(snapshots, expected_snapshots) + + @override_settings(API_RESULT_PAGE_SIZE=1) + def test_snapshots_index_paginated_prev_page(self): + mox_snapshots = self.cinder_volume_snapshots.list() + size = settings.API_RESULT_PAGE_SIZE + base_url = INDEX_URL + prev = snapshot_tables.VolumeSnapshotsTable._meta.prev_pagination_param + + # prev from some page + expected_snapshots = mox_snapshots[size:2 * size] + marker = expected_snapshots[0].id + url = base_url + "?%s=%s" % (prev, marker) + res = self._test_snapshots_index_paginated( + marker=marker, sort_dir="asc", snapshots=expected_snapshots, + url=url, has_more=True, has_prev=True) + snapshots = res.context['volume_snapshots_table'].data + self.assertItemsEqual(snapshots, expected_snapshots) + + # back to first page + expected_snapshots = mox_snapshots[:size] + marker = expected_snapshots[0].id + url = base_url + "?%s=%s" % (prev, marker) + res = self._test_snapshots_index_paginated( + marker=marker, sort_dir="asc", snapshots=expected_snapshots, + url=url, has_more=True, has_prev=False) + snapshots = res.context['volume_snapshots_table'].data + self.assertItemsEqual(snapshots, expected_snapshots) + @test.create_stubs({cinder: ('volume_get',), quotas: ('tenant_limit_usages',)}) def test_create_snapshot_get(self): @@ -77,7 +168,7 @@ class VolumeSnapshotsViewTests(test.TestCase): url = reverse('horizon:project:volumes:volumes:create_snapshot', args=[volume.id]) res = self.client.post(url, formData) - self.assertRedirectsNoFollow(res, VOLUME_SNAPSHOTS_TAB_URL) + self.assertRedirectsNoFollow(res, INDEX_URL) @test.create_stubs({cinder: ('volume_get', 'volume_snapshot_create',)}) @@ -103,11 +194,10 @@ class VolumeSnapshotsViewTests(test.TestCase): url = reverse('horizon:project:volumes:volumes:create_snapshot', args=[volume.id]) res = self.client.post(url, formData) - self.assertRedirectsNoFollow(res, VOLUME_SNAPSHOTS_TAB_URL) + self.assertRedirectsNoFollow(res, INDEX_URL) @test.create_stubs({api.cinder: ('volume_snapshot_list_paged', 'volume_list', - 'volume_backup_supported', 'volume_snapshot_delete', 'tenant_absolute_limits')}) def test_delete_volume_snapshot(self): @@ -115,8 +205,6 @@ class VolumeSnapshotsViewTests(test.TestCase): volumes = self.cinder_volumes.list() snapshot = self.cinder_volume_snapshots.first() - api.cinder.volume_backup_supported(IsA(http.HttpRequest)). \ - MultipleTimes().AndReturn(True) api.cinder.volume_snapshot_list_paged( IsA(http.HttpRequest), paginate=True, marker=None, sort_dir='desc').AndReturn([vol_snapshots, False, False]) @@ -126,11 +214,10 @@ class VolumeSnapshotsViewTests(test.TestCase): api.cinder.volume_snapshot_delete(IsA(http.HttpRequest), snapshot.id) self.mox.ReplayAll() - formData = {'action': - 'volume_snapshots__delete__%s' % snapshot.id} - res = self.client.post(VOLUME_SNAPSHOTS_TAB_URL, formData) + formData = {'action': 'volume_snapshots__delete__%s' % snapshot.id} + res = self.client.post(INDEX_URL, formData) - self.assertRedirectsNoFollow(res, VOLUME_SNAPSHOTS_TAB_URL) + self.assertRedirectsNoFollow(res, INDEX_URL) self.assertMessageCount(success=1) @test.create_stubs({api.cinder: ('volume_snapshot_get', 'volume_get')}) @@ -145,7 +232,7 @@ class VolumeSnapshotsViewTests(test.TestCase): self.mox.ReplayAll() - url = reverse('horizon:project:volumes:snapshots:detail', + url = reverse('horizon:project:snapshots:detail', args=[snapshot.id]) res = self.client.get(url) @@ -161,7 +248,7 @@ class VolumeSnapshotsViewTests(test.TestCase): AndRaise(self.exceptions.cinder) self.mox.ReplayAll() - url = reverse('horizon:project:volumes:snapshots:detail', + url = reverse('horizon:project:snapshots:detail', args=[snapshot.id]) res = self.client.get(url) @@ -180,7 +267,7 @@ class VolumeSnapshotsViewTests(test.TestCase): self.mox.ReplayAll() - url = reverse('horizon:project:volumes:snapshots:detail', + url = reverse('horizon:project:snapshots:detail', args=[snapshot.id]) res = self.client.get(url) @@ -203,7 +290,7 @@ class VolumeSnapshotsViewTests(test.TestCase): formData = {'method': 'UpdateSnapshotForm', 'name': snapshot.name, 'description': snapshot.description} - url = reverse(('horizon:project:volumes:snapshots:update'), + url = reverse(('horizon:project:snapshots:update'), args=[snapshot.id]) res = self.client.post(url, formData) self.assertRedirectsNoFollow(res, INDEX_URL) diff --git a/openstack_dashboard/dashboards/project/volumes/snapshots/urls.py b/openstack_dashboard/dashboards/project/snapshots/urls.py similarity index 86% rename from openstack_dashboard/dashboards/project/volumes/snapshots/urls.py rename to openstack_dashboard/dashboards/project/snapshots/urls.py index 335f103dfa..2f0ed0c615 100644 --- a/openstack_dashboard/dashboards/project/volumes/snapshots/urls.py +++ b/openstack_dashboard/dashboards/project/snapshots/urls.py @@ -12,10 +12,11 @@ from django.conf.urls import url -from openstack_dashboard.dashboards.project.volumes.snapshots import views +from openstack_dashboard.dashboards.project.snapshots import views urlpatterns = [ + url(r'^$', views.SnapshotsView.as_view(), name='index'), url(r'^(?P[^/]+)$', views.DetailView.as_view(), name='detail'), diff --git a/openstack_dashboard/dashboards/project/volumes/snapshots/views.py b/openstack_dashboard/dashboards/project/snapshots/views.py similarity index 67% rename from openstack_dashboard/dashboards/project/volumes/snapshots/views.py rename to openstack_dashboard/dashboards/project/snapshots/views.py index 4dfe0b7993..e0924dfe81 100644 --- a/openstack_dashboard/dashboards/project/volumes/snapshots/views.py +++ b/openstack_dashboard/dashboards/project/snapshots/views.py @@ -16,26 +16,55 @@ from django.utils.translation import ugettext_lazy as _ from horizon import exceptions from horizon import forms +from horizon import tables from horizon import tabs from horizon.utils import memoized from openstack_dashboard import api -from openstack_dashboard.dashboards.project.volumes \ - .snapshots import forms as vol_snapshot_forms -from openstack_dashboard.dashboards.project.volumes \ - .snapshots import tables as vol_snapshot_tables -from openstack_dashboard.dashboards.project.volumes \ - .snapshots import tabs as vol_snapshot_tabs +from openstack_dashboard.dashboards.project.snapshots \ + import forms as vol_snapshot_forms +from openstack_dashboard.dashboards.project.snapshots \ + import tables as vol_snapshot_tables +from openstack_dashboard.dashboards.project.snapshots \ + import tabs as vol_snapshot_tabs + + +class SnapshotsView(tables.DataTableView, tables.PagedTableMixin): + table_class = vol_snapshot_tables.VolumeSnapshotsTable + page_title = _("Volume Snapshots") + + def get_data(self): + snapshots = [] + volumes = {} + if api.base.is_service_enabled(self.request, 'volumev2'): + try: + marker, sort_dir = self._get_marker() + snapshots, self._has_more_data, self._has_prev_data = \ + api.cinder.volume_snapshot_list_paged( + self.request, paginate=True, marker=marker, + sort_dir=sort_dir) + volumes = api.cinder.volume_list(self.request) + volumes = dict((v.id, v) for v in volumes) + except Exception: + raise + exceptions.handle(self.request, _("Unable to retrieve " + "volume snapshots.")) + + for snapshot in snapshots: + volume = volumes.get(snapshot.volume_id) + setattr(snapshot, '_volume', volume) + + return snapshots class UpdateView(forms.ModalFormView): form_class = vol_snapshot_forms.UpdateForm form_id = "update_snapshot_form" - template_name = 'project/volumes/snapshots/update.html' + template_name = 'project/snapshots/update.html' submit_label = _("Save Changes") - submit_url = "horizon:project:volumes:snapshots:update" - success_url = reverse_lazy("horizon:project:volumes:index") + submit_url = "horizon:project:snapshots:update" + success_url = reverse_lazy("horizon:project:snapshots:index") page_title = _("Edit Snapshot") @memoized.memoized_method @@ -46,7 +75,7 @@ class UpdateView(forms.ModalFormView): snap_id) except Exception: msg = _('Unable to retrieve volume snapshot.') - url = reverse('horizon:project:volumes:index') + url = reverse('horizon:project:snapshots:index') exceptions.handle(self.request, msg, redirect=url) return self._object @@ -96,7 +125,7 @@ class DetailView(tabs.TabView): @staticmethod def get_redirect_url(): - return reverse('horizon:project:volumes:index') + return reverse('horizon:project:snapshots:index') def get_tabs(self, request, *args, **kwargs): snapshot = self.get_data() diff --git a/openstack_dashboard/dashboards/project/volumes/tabs.py b/openstack_dashboard/dashboards/project/volumes/tabs.py index 1e0f82004b..66513d244f 100644 --- a/openstack_dashboard/dashboards/project/volumes/tabs.py +++ b/openstack_dashboard/dashboards/project/volumes/tabs.py @@ -17,6 +17,7 @@ from collections import OrderedDict from django.utils.translation import ugettext_lazy as _ from horizon import exceptions +from horizon.tables import PagedTableMixin from horizon import tabs from openstack_dashboard import api @@ -28,8 +29,6 @@ from openstack_dashboard.dashboards.project.volumes.cg_snapshots \ import tables as cg_snapshots_tables from openstack_dashboard.dashboards.project.volumes.cgroups \ import tables as cgroup_tables -from openstack_dashboard.dashboards.project.volumes.snapshots \ - import tables as vol_snapshot_tables from openstack_dashboard.dashboards.project.volumes.volumes \ import tables as volume_tables @@ -110,30 +109,6 @@ class VolumeTableMixIn(object): att['instance'] = instances.get(server_id, None) -class PagedTableMixin(object): - def __init__(self, *args, **kwargs): - super(PagedTableMixin, self).__init__(*args, **kwargs) - self._has_prev_data = False - self._has_more_data = False - - def has_prev_data(self, table): - return self._has_prev_data - - def has_more_data(self, table): - return self._has_more_data - - def _get_marker(self): - meta = self.table_classes[0]._meta - prev_marker = self.request.GET.get(meta.prev_pagination_param, None) - if prev_marker: - return prev_marker, "asc" - else: - marker = self.request.GET.get(meta.pagination_param, None) - if marker: - return marker, "desc" - return None, "desc" - - class VolumeTab(PagedTableMixin, tabs.TableTab, VolumeTableMixIn): table_classes = (volume_tables.VolumesTable,) name = _("Volumes") @@ -151,36 +126,6 @@ class VolumeTab(PagedTableMixin, tabs.TableTab, VolumeTableMixIn): return volumes -class SnapshotTab(PagedTableMixin, tabs.TableTab): - table_classes = (vol_snapshot_tables.VolumeSnapshotsTable,) - name = _("Volume Snapshots") - slug = "snapshots_tab" - template_name = ("horizon/common/_detail_table.html") - preload = False - - def get_volume_snapshots_data(self): - snapshots = [] - volumes = {} - if api.base.is_service_enabled(self.request, 'volumev2'): - try: - marker, sort_dir = self._get_marker() - snapshots, self._has_more_data, self._has_prev_data = \ - api.cinder.volume_snapshot_list_paged( - self.request, paginate=True, marker=marker, - sort_dir=sort_dir) - volumes = api.cinder.volume_list(self.request) - volumes = dict((v.id, v) for v in volumes) - except Exception: - exceptions.handle(self.request, _("Unable to retrieve " - "volume snapshots.")) - - for snapshot in snapshots: - volume = volumes.get(snapshot.volume_id) - setattr(snapshot, '_volume', volume) - - return snapshots - - class BackupsTab(PagedTableMixin, tabs.TableTab, VolumeTableMixIn): table_classes = (backups_tables.BackupsTable,) name = _("Volume Backups") @@ -261,5 +206,5 @@ class CGSnapshotsTab(tabs.TableTab): class VolumeAndSnapshotTabs(tabs.TabGroup): slug = "volumes_and_snapshots" - tabs = (VolumeTab, SnapshotTab, BackupsTab, CGroupsTab, CGSnapshotsTab) + tabs = (VolumeTab, BackupsTab, CGroupsTab, CGSnapshotsTab) sticky = True diff --git a/openstack_dashboard/dashboards/project/volumes/test.py b/openstack_dashboard/dashboards/project/volumes/test.py index edab47399f..ebb5bb08ef 100644 --- a/openstack_dashboard/dashboards/project/volumes/test.py +++ b/openstack_dashboard/dashboards/project/volumes/test.py @@ -25,16 +25,12 @@ from mox3.mox import IsA # noqa from openstack_dashboard import api from openstack_dashboard.dashboards.project.volumes.backups \ import tables as backup_tables -from openstack_dashboard.dashboards.project.volumes.snapshots \ - import tables as snapshot_tables from openstack_dashboard.dashboards.project.volumes.volumes \ import tables as volume_tables from openstack_dashboard.test import helpers as test INDEX_URL = reverse('horizon:project:volumes:index') -VOLUME_SNAPSHOTS_TAB_URL = urlunquote(reverse( - 'horizon:project:volumes:snapshots_tab')) VOLUME_BACKUPS_TAB_URL = urlunquote(reverse( 'horizon:project:volumes:backups_tab')) @@ -44,7 +40,6 @@ class VolumeAndSnapshotsAndBackupsTests(test.TestCase): 'volume_list', 'volume_list_paged', 'volume_snapshot_list', - 'volume_snapshot_list_paged', 'volume_backup_supported', 'volume_backup_list_paged', ), @@ -67,13 +62,9 @@ class VolumeAndSnapshotsAndBackupsTests(test.TestCase): api.nova.server_list(IsA(http.HttpRequest), search_opts=None, detailed=False).\ AndReturn([self.servers.list(), False]) - api.cinder.volume_snapshot_list(IsA(http.HttpRequest)).\ - AndReturn(vol_snaps) + api.cinder.volume_snapshot_list(IsA(http.HttpRequest)).\ + AndReturn(vol_snaps) - api.cinder.volume_snapshot_list_paged( - IsA(http.HttpRequest), paginate=True, marker=None, - sort_dir='desc').AndReturn([vol_snaps, False, False]) - api.cinder.volume_list(IsA(http.HttpRequest)).AndReturn(volumes) if backup_supported: api.cinder.volume_backup_list_paged( IsA(http.HttpRequest), marker=None, sort_dir='desc', @@ -87,12 +78,6 @@ class VolumeAndSnapshotsAndBackupsTests(test.TestCase): self.assertEqual(res.status_code, 200) self.assertTemplateUsed(res, 'project/volumes/index.html') - # Explicitly load the other tabs. If this doesn't work the test - # will fail due to "Expected methods never called." - res = self.client.get(VOLUME_SNAPSHOTS_TAB_URL) - self.assertEqual(res.status_code, 200) - self.assertTemplateUsed(res, 'project/volumes/index.html') - if backup_supported: res = self.client.get(VOLUME_BACKUPS_TAB_URL) self.assertTemplateUsed(res, 'project/volumes/index.html') @@ -209,95 +194,6 @@ class VolumeAndSnapshotsAndBackupsTests(test.TestCase): volumes = res.context['volumes_table'].data self.assertItemsEqual(volumes, expected_volumes) - @test.create_stubs({api.cinder: ('tenant_absolute_limits', - 'volume_snapshot_list_paged', - 'volume_list', - 'volume_backup_supported', - ), - api.nova: ('server_list',)}) - def _test_snapshots_index_paginated(self, marker, sort_dir, snapshots, url, - has_more, has_prev): - backup_supported = True - - api.cinder.volume_backup_supported(IsA(http.HttpRequest)).\ - MultipleTimes().AndReturn(backup_supported) - api.cinder.volume_snapshot_list_paged( - IsA(http.HttpRequest), marker=marker, sort_dir=sort_dir, - paginate=True).AndReturn([snapshots, has_more, has_prev]) - api.cinder.volume_list(IsA(http.HttpRequest)).AndReturn( - self.cinder_volumes.list()) - self.mox.ReplayAll() - - res = self.client.get(urlunquote(url)) - self.assertEqual(res.status_code, 200) - self.assertTemplateUsed(res, 'project/volumes/index.html') - - self.mox.UnsetStubs() - return res - - @override_settings(API_RESULT_PAGE_SIZE=1) - def test_snapshots_index_paginated(self): - mox_snapshots = self.cinder_volume_snapshots.list() - size = settings.API_RESULT_PAGE_SIZE - base_url = reverse('horizon:project:volumes:snapshots_tab') - next = snapshot_tables.VolumeSnapshotsTable._meta.pagination_param - - # get first page - expected_snapshots = mox_snapshots[:size] - res = self._test_snapshots_index_paginated( - marker=None, sort_dir="desc", snapshots=expected_snapshots, - url=base_url, has_more=True, has_prev=False) - snapshots = res.context['volume_snapshots_table'].data - self.assertItemsEqual(snapshots, expected_snapshots) - - # get second page - expected_snapshots = mox_snapshots[size:2 * size] - marker = expected_snapshots[0].id - - url = "&".join([base_url, "=".join([next, marker])]) - res = self._test_snapshots_index_paginated( - marker=marker, sort_dir="desc", snapshots=expected_snapshots, - url=url, has_more=True, has_prev=True) - snapshots = res.context['volume_snapshots_table'].data - self.assertItemsEqual(snapshots, expected_snapshots) - - # get last page - expected_snapshots = mox_snapshots[-size:] - marker = expected_snapshots[0].id - url = "&".join([base_url, "=".join([next, marker])]) - res = self._test_snapshots_index_paginated( - marker=marker, sort_dir="desc", snapshots=expected_snapshots, - url=url, has_more=False, has_prev=True) - snapshots = res.context['volume_snapshots_table'].data - self.assertItemsEqual(snapshots, expected_snapshots) - - @override_settings(API_RESULT_PAGE_SIZE=1) - def test_snapshots_index_paginated_prev_page(self): - mox_snapshots = self.cinder_volume_snapshots.list() - size = settings.API_RESULT_PAGE_SIZE - base_url = reverse('horizon:project:volumes:snapshots_tab') - prev = snapshot_tables.VolumeSnapshotsTable._meta.prev_pagination_param - - # prev from some page - expected_snapshots = mox_snapshots[size:2 * size] - marker = expected_snapshots[0].id - url = "&".join([base_url, "=".join([prev, marker])]) - res = self._test_snapshots_index_paginated( - marker=marker, sort_dir="asc", snapshots=expected_snapshots, - url=url, has_more=True, has_prev=True) - snapshots = res.context['volume_snapshots_table'].data - self.assertItemsEqual(snapshots, expected_snapshots) - - # back to first page - expected_snapshots = mox_snapshots[:size] - marker = expected_snapshots[0].id - url = "&".join([base_url, "=".join([prev, marker])]) - res = self._test_snapshots_index_paginated( - marker=marker, sort_dir="asc", snapshots=expected_snapshots, - url=url, has_more=True, has_prev=False) - snapshots = res.context['volume_snapshots_table'].data - self.assertItemsEqual(snapshots, expected_snapshots) - @test.create_stubs({api.cinder: ('tenant_absolute_limits', 'volume_backup_list_paged', 'volume_list', diff --git a/openstack_dashboard/dashboards/project/volumes/urls.py b/openstack_dashboard/dashboards/project/volumes/urls.py index ce9c70b6b5..9fd006c9bc 100644 --- a/openstack_dashboard/dashboards/project/volumes/urls.py +++ b/openstack_dashboard/dashboards/project/volumes/urls.py @@ -21,16 +21,12 @@ from openstack_dashboard.dashboards.project.volumes.cg_snapshots \ import urls as cg_snapshots_urls from openstack_dashboard.dashboards.project.volumes.cgroups \ import urls as cgroup_urls -from openstack_dashboard.dashboards.project.volumes.snapshots \ - import urls as snapshot_urls from openstack_dashboard.dashboards.project.volumes import views from openstack_dashboard.dashboards.project.volumes.volumes \ import urls as volume_urls urlpatterns = [ url(r'^$', views.IndexView.as_view(), name='index'), - url(r'^\?tab=volumes_and_snapshots__snapshots_tab$', - views.IndexView.as_view(), name='snapshots_tab'), url(r'^\?tab=volumes_and_snapshots__volumes_tab$', views.IndexView.as_view(), name='volumes_tab'), url(r'^\?tab=volumes_and_snapshots__backups_tab$', @@ -45,9 +41,6 @@ urlpatterns = [ url(r'backups/', include( backups_urls, namespace='backups')), - url(r'snapshots/', include( - snapshot_urls, - namespace='snapshots')), url(r'cgroups/', include( cgroup_urls, namespace='cgroups')), diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/views.py b/openstack_dashboard/dashboards/project/volumes/volumes/views.py index b995fba4b1..761f94fb47 100644 --- a/openstack_dashboard/dashboards/project/volumes/volumes/views.py +++ b/openstack_dashboard/dashboards/project/volumes/volumes/views.py @@ -191,7 +191,7 @@ class CreateSnapshotView(forms.ModalFormView): form_class = project_forms.CreateSnapshotForm template_name = 'project/volumes/volumes/create_snapshot.html' submit_url = "horizon:project:volumes:volumes:create_snapshot" - success_url = reverse_lazy('horizon:project:volumes:snapshots_tab') + success_url = reverse_lazy('horizon:project:snapshots:index') page_title = _("Create Volume Snapshot") def get_context_data(self, **kwargs): diff --git a/openstack_dashboard/enabled/_1310_volumes_panel_group.py b/openstack_dashboard/enabled/_1310_volumes_panel_group.py new file mode 100644 index 0000000000..0c234ec1fa --- /dev/null +++ b/openstack_dashboard/enabled/_1310_volumes_panel_group.py @@ -0,0 +1,8 @@ +from django.utils.translation import ugettext_lazy as _ + +# The slug of the panel group to be added to HORIZON_CONFIG. Required. +PANEL_GROUP = 'volumes' +# The display name of the PANEL_GROUP. Required. +PANEL_GROUP_NAME = _('Volumes') +# The slug of the dashboard the PANEL_GROUP associated with. Required. +PANEL_GROUP_DASHBOARD = 'project' diff --git a/openstack_dashboard/enabled/_1330_project_snapshots_panel.py b/openstack_dashboard/enabled/_1330_project_snapshots_panel.py new file mode 100644 index 0000000000..7f150d0ab1 --- /dev/null +++ b/openstack_dashboard/enabled/_1330_project_snapshots_panel.py @@ -0,0 +1,9 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'snapshots' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'project' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'volumes' + +# Python panel class of the PANEL to be added. +ADD_PANEL = 'openstack_dashboard.dashboards.project.snapshots.panel.Snapshots'