From 3d25f1d5951beca1a73683567725ed26620ab373 Mon Sep 17 00:00:00 2001 From: Zhenguo Niu Date: Tue, 18 Feb 2014 10:14:00 +0800 Subject: [PATCH] Move volume snapshots table to volumes panel Now that instance snapshots are just images, the images & snapshots page only has images and volume snapshots. This is an odd combination. I think we should now move volume snapshots to the volumes panel, since a volume snapshot is tied to a volume. Closes-Bug: #1190843 Change-Id: I3e12cec8b859a30db9f131e9889a92277e064086 --- .../dashboards/admin/images/forms.py | 3 +- .../dashboards/admin/images/tables.py | 2 +- .../dashboards/admin/images/views.py | 3 +- .../dashboards/admin/volumes/tables.py | 2 +- .../dashboards/admin/volumes/views.py | 9 +- .../dashboards/project/dashboard.py | 2 +- .../__init__.py | 0 .../images/__init__.py | 0 .../images/forms.py | 0 .../images/tables.py | 9 +- .../images/tabs.py | 2 +- .../images/tests.py | 38 ++- .../images/urls.py | 6 +- .../images/views.py | 18 +- .../{images_and_snapshots => images}/panel.py | 8 +- .../snapshots/__init__.py | 0 .../snapshots/forms.py | 0 .../snapshots/tests.py | 12 +- .../snapshots/urls.py | 3 +- .../snapshots/views.py | 6 +- .../templates/images}/images/_create.html | 4 +- .../images}/images/_detail_overview.html | 0 .../templates/images}/images/_update.html | 4 +- .../templates/images}/images/create.html | 2 +- .../templates/images}/images/detail.html | 0 .../templates/images}/images/update.html | 2 +- .../images/templates/images/index.html | 11 + .../templates/images}/snapshots/_create.html | 4 +- .../templates/images}/snapshots/create.html | 2 +- .../{images_and_snapshots => images}/tests.py | 56 +--- .../{images_and_snapshots => images}/urls.py | 9 +- .../{images_and_snapshots => images}/utils.py | 0 .../dashboards/project/images/views.py | 54 ++++ .../templates/images_and_snapshots/index.html | 16 -- .../project/images_and_snapshots/views.py | 108 -------- .../dashboards/project/instances/forms.py | 2 +- .../dashboards/project/instances/tables.py | 2 +- .../templates/instances/_detail_overview.html | 2 +- .../dashboards/project/instances/tests.py | 8 +- .../instances/workflows/create_instance.py | 2 +- .../snapshots}/__init__.py | 0 .../snapshots}/tables.py | 13 +- .../snapshots}/tabs.py | 5 +- .../snapshots}/tests.py | 37 +-- .../dashboards/project/volumes/tabs.py | 101 ++++++- .../volumes/templates/volumes/index.html | 10 +- .../volumes}/snapshots/_detail_overview.html | 2 +- .../templates/volumes}/snapshots/detail.html | 0 .../volumes/{ => volumes}/_attach.html | 2 +- .../volumes/{ => volumes}/_create.html | 4 +- .../{ => volumes}/_create_snapshot.html | 4 +- .../{ => volumes}/_detail_overview.html | 0 .../volumes/{ => volumes}/_extend.html | 4 +- .../volumes/{ => volumes}/_extend_limits.html | 0 .../volumes/{ => volumes}/_limits.html | 0 .../volumes/{ => volumes}/_update.html | 2 +- .../volumes/{ => volumes}/attach.html | 2 +- .../volumes/{ => volumes}/create.html | 2 +- .../{ => volumes}/create_snapshot.html | 2 +- .../volumes/{ => volumes}/detail.html | 0 .../volumes/{ => volumes}/extend.html | 2 +- .../volumes/{ => volumes}/update.html | 2 +- .../dashboards/project/volumes/test.py | 48 ++++ .../dashboards/project/volumes/urls.py | 21 +- .../dashboards/project/volumes/views.py | 258 +----------------- .../project/volumes/volumes/__init__.py | 0 .../project/volumes/{ => volumes}/forms.py | 4 +- .../project/volumes/{ => volumes}/tables.py | 14 +- .../project/volumes/volumes/tabs.py | 33 +++ .../project/volumes/{ => volumes}/tests.py | 46 ++-- .../project/volumes/volumes/urls.py | 43 +++ .../project/volumes/volumes/views.py | 244 +++++++++++++++++ .../dashboards/router/nexus1000v/tables.py | 2 +- 73 files changed, 710 insertions(+), 608 deletions(-) rename openstack_dashboard/dashboards/project/{images_and_snapshots => images}/__init__.py (100%) rename openstack_dashboard/dashboards/project/{images_and_snapshots => images}/images/__init__.py (100%) rename openstack_dashboard/dashboards/project/{images_and_snapshots => images}/images/forms.py (100%) rename openstack_dashboard/dashboards/project/{images_and_snapshots => images}/images/tables.py (96%) rename openstack_dashboard/dashboards/project/{images_and_snapshots => images}/images/tabs.py (95%) rename openstack_dashboard/dashboards/project/{images_and_snapshots => images}/images/tests.py (89%) rename openstack_dashboard/dashboards/project/{images_and_snapshots => images}/images/urls.py (85%) rename openstack_dashboard/dashboards/project/{images_and_snapshots => images}/images/views.py (83%) rename openstack_dashboard/dashboards/project/{images_and_snapshots => images}/panel.py (84%) rename openstack_dashboard/dashboards/project/{images_and_snapshots => images}/snapshots/__init__.py (100%) rename openstack_dashboard/dashboards/project/{images_and_snapshots => images}/snapshots/forms.py (100%) rename openstack_dashboard/dashboards/project/{images_and_snapshots => images}/snapshots/tests.py (88%) rename openstack_dashboard/dashboards/project/{images_and_snapshots => images}/snapshots/urls.py (91%) rename openstack_dashboard/dashboards/project/{images_and_snapshots => images}/snapshots/views.py (89%) rename openstack_dashboard/dashboards/project/{images_and_snapshots/templates/images_and_snapshots => images/templates/images}/images/_create.html (83%) rename openstack_dashboard/dashboards/project/{images_and_snapshots/templates/images_and_snapshots => images/templates/images}/images/_detail_overview.html (100%) rename openstack_dashboard/dashboards/project/{images_and_snapshots/templates/images_and_snapshots => images/templates/images}/images/_update.html (73%) rename openstack_dashboard/dashboards/project/{images_and_snapshots/templates/images_and_snapshots => images/templates/images}/images/create.html (80%) rename openstack_dashboard/dashboards/project/{images_and_snapshots/templates/images_and_snapshots => images/templates/images}/images/detail.html (100%) rename openstack_dashboard/dashboards/project/{images_and_snapshots/templates/images_and_snapshots => images/templates/images}/images/update.html (79%) create mode 100644 openstack_dashboard/dashboards/project/images/templates/images/index.html rename openstack_dashboard/dashboards/project/{images_and_snapshots/templates/images_and_snapshots => images/templates/images}/snapshots/_create.html (74%) rename openstack_dashboard/dashboards/project/{images_and_snapshots/templates/images_and_snapshots => images/templates/images}/snapshots/create.html (79%) rename openstack_dashboard/dashboards/project/{images_and_snapshots => images}/tests.py (84%) rename openstack_dashboard/dashboards/project/{images_and_snapshots => images}/urls.py (78%) rename openstack_dashboard/dashboards/project/{images_and_snapshots => images}/utils.py (100%) create mode 100644 openstack_dashboard/dashboards/project/images/views.py delete mode 100644 openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/index.html delete mode 100644 openstack_dashboard/dashboards/project/images_and_snapshots/views.py rename openstack_dashboard/dashboards/project/{images_and_snapshots/volume_snapshots => volumes/snapshots}/__init__.py (100%) rename openstack_dashboard/dashboards/project/{images_and_snapshots/volume_snapshots => volumes/snapshots}/tables.py (91%) rename openstack_dashboard/dashboards/project/{images_and_snapshots/volume_snapshots => volumes/snapshots}/tabs.py (88%) rename openstack_dashboard/dashboards/project/{images_and_snapshots/volume_snapshots => volumes/snapshots}/tests.py (86%) rename openstack_dashboard/dashboards/project/{images_and_snapshots/templates/images_and_snapshots => volumes/templates/volumes}/snapshots/_detail_overview.html (92%) rename openstack_dashboard/dashboards/project/{images_and_snapshots/templates/images_and_snapshots => volumes/templates/volumes}/snapshots/detail.html (100%) rename openstack_dashboard/dashboards/project/volumes/templates/volumes/{ => volumes}/_attach.html (90%) rename openstack_dashboard/dashboards/project/volumes/templates/volumes/{ => volumes}/_create.html (78%) rename openstack_dashboard/dashboards/project/volumes/templates/volumes/{ => volumes}/_create_snapshot.html (81%) rename openstack_dashboard/dashboards/project/volumes/templates/volumes/{ => volumes}/_detail_overview.html (100%) rename openstack_dashboard/dashboards/project/volumes/templates/volumes/{ => volumes}/_extend.html (79%) rename openstack_dashboard/dashboards/project/volumes/templates/volumes/{ => volumes}/_extend_limits.html (100%) rename openstack_dashboard/dashboards/project/volumes/templates/volumes/{ => volumes}/_limits.html (100%) rename openstack_dashboard/dashboards/project/volumes/templates/volumes/{ => volumes}/_update.html (89%) rename openstack_dashboard/dashboards/project/volumes/templates/volumes/{ => volumes}/attach.html (83%) rename openstack_dashboard/dashboards/project/volumes/templates/volumes/{ => volumes}/create.html (82%) rename openstack_dashboard/dashboards/project/volumes/templates/volumes/{ => volumes}/create_snapshot.html (81%) rename openstack_dashboard/dashboards/project/volumes/templates/volumes/{ => volumes}/detail.html (100%) rename openstack_dashboard/dashboards/project/volumes/templates/volumes/{ => volumes}/extend.html (82%) rename openstack_dashboard/dashboards/project/volumes/templates/volumes/{ => volumes}/update.html (82%) create mode 100644 openstack_dashboard/dashboards/project/volumes/test.py create mode 100644 openstack_dashboard/dashboards/project/volumes/volumes/__init__.py rename openstack_dashboard/dashboards/project/volumes/{ => volumes}/forms.py (99%) rename openstack_dashboard/dashboards/project/volumes/{ => volumes}/tables.py (96%) create mode 100644 openstack_dashboard/dashboards/project/volumes/volumes/tabs.py rename openstack_dashboard/dashboards/project/volumes/{ => volumes}/tests.py (96%) create mode 100644 openstack_dashboard/dashboards/project/volumes/volumes/urls.py create mode 100644 openstack_dashboard/dashboards/project/volumes/volumes/views.py diff --git a/openstack_dashboard/dashboards/admin/images/forms.py b/openstack_dashboard/dashboards/admin/images/forms.py index f962af64d6..7de1887aca 100644 --- a/openstack_dashboard/dashboards/admin/images/forms.py +++ b/openstack_dashboard/dashboards/admin/images/forms.py @@ -18,8 +18,7 @@ # License for the specific language governing permissions and limitations # under the License. -from openstack_dashboard.dashboards.project.images_and_snapshots \ - .images import forms +from openstack_dashboard.dashboards.project.images.images import forms class AdminCreateImageForm(forms.CreateImageForm): diff --git a/openstack_dashboard/dashboards/admin/images/tables.py b/openstack_dashboard/dashboards/admin/images/tables.py index fb6b28e017..eec15ddfcf 100644 --- a/openstack_dashboard/dashboards/admin/images/tables.py +++ b/openstack_dashboard/dashboards/admin/images/tables.py @@ -19,7 +19,7 @@ from django.utils.translation import ugettext_lazy as _ from horizon import tables from openstack_dashboard import api -from openstack_dashboard.dashboards.project.images_and_snapshots.images \ +from openstack_dashboard.dashboards.project.images.images \ import tables as project_tables diff --git a/openstack_dashboard/dashboards/admin/images/views.py b/openstack_dashboard/dashboards/admin/images/views.py index 9602b4ee27..76f51e13e0 100644 --- a/openstack_dashboard/dashboards/admin/images/views.py +++ b/openstack_dashboard/dashboards/admin/images/views.py @@ -25,8 +25,7 @@ from horizon import exceptions from horizon import tables from openstack_dashboard import api -from openstack_dashboard.dashboards.project \ - .images_and_snapshots.images import views +from openstack_dashboard.dashboards.project.images.images import views from openstack_dashboard.dashboards.admin.images import forms from openstack_dashboard.dashboards.admin.images \ diff --git a/openstack_dashboard/dashboards/admin/volumes/tables.py b/openstack_dashboard/dashboards/admin/volumes/tables.py index b704501f1c..9befae86a8 100644 --- a/openstack_dashboard/dashboards/admin/volumes/tables.py +++ b/openstack_dashboard/dashboards/admin/volumes/tables.py @@ -15,7 +15,7 @@ from django.utils.translation import ugettext_lazy as _ from horizon import tables from openstack_dashboard.api import cinder from openstack_dashboard.dashboards.project.volumes \ - import tables as project_tables + .volumes import tables as project_tables class CreateVolumeType(tables.LinkAction): diff --git a/openstack_dashboard/dashboards/admin/volumes/views.py b/openstack_dashboard/dashboards/admin/volumes/views.py index f42f4cfcf9..858befb10d 100644 --- a/openstack_dashboard/dashboards/admin/volumes/views.py +++ b/openstack_dashboard/dashboards/admin/volumes/views.py @@ -34,10 +34,13 @@ from openstack_dashboard.dashboards.admin.volumes \ from openstack_dashboard.dashboards.admin.volumes \ import tables as project_tables -from openstack_dashboard.dashboards.project.volumes import views +from openstack_dashboard.dashboards.project.volumes \ + import tabs as project_tabs +from openstack_dashboard.dashboards.project.volumes \ + .volumes import views as volume_views -class IndexView(tables.MultiTableView, views.VolumeTableMixIn): +class IndexView(tables.MultiTableView, project_tabs.VolumeTableMixIn): table_classes = (project_tables.VolumesTable, project_tables.VolumeTypesTable) template_name = "admin/volumes/index.html" @@ -74,7 +77,7 @@ class IndexView(tables.MultiTableView, views.VolumeTableMixIn): return volume_types -class DetailView(views.DetailView): +class DetailView(volume_views.DetailView): template_name = "admin/volumes/detail.html" diff --git a/openstack_dashboard/dashboards/project/dashboard.py b/openstack_dashboard/dashboards/project/dashboard.py index e29ef6f836..35320ee575 100644 --- a/openstack_dashboard/dashboards/project/dashboard.py +++ b/openstack_dashboard/dashboards/project/dashboard.py @@ -25,7 +25,7 @@ class BasePanels(horizon.PanelGroup): panels = ('overview', 'instances', 'volumes', - 'images_and_snapshots', + 'images', 'access_and_security',) diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/__init__.py b/openstack_dashboard/dashboards/project/images/__init__.py similarity index 100% rename from openstack_dashboard/dashboards/project/images_and_snapshots/__init__.py rename to openstack_dashboard/dashboards/project/images/__init__.py diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/images/__init__.py b/openstack_dashboard/dashboards/project/images/images/__init__.py similarity index 100% rename from openstack_dashboard/dashboards/project/images_and_snapshots/images/__init__.py rename to openstack_dashboard/dashboards/project/images/images/__init__.py diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/images/forms.py b/openstack_dashboard/dashboards/project/images/images/forms.py similarity index 100% rename from openstack_dashboard/dashboards/project/images_and_snapshots/images/forms.py rename to openstack_dashboard/dashboards/project/images/images/forms.py diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/images/tables.py b/openstack_dashboard/dashboards/project/images/images/tables.py similarity index 96% rename from openstack_dashboard/dashboards/project/images_and_snapshots/images/tables.py rename to openstack_dashboard/dashboards/project/images/images/tables.py index 9ec01b561b..ba2c6c2e3e 100644 --- a/openstack_dashboard/dashboards/project/images_and_snapshots/images/tables.py +++ b/openstack_dashboard/dashboards/project/images/images/tables.py @@ -77,7 +77,7 @@ class DeleteImage(tables.DeleteAction): class CreateImage(tables.LinkAction): name = "create" verbose_name = _("Create Image") - url = "horizon:project:images_and_snapshots:images:create" + url = "horizon:project:images:images:create" classes = ("ajax-modal", "btn-create") policy_rules = (("image", "add_image"),) @@ -85,7 +85,7 @@ class CreateImage(tables.LinkAction): class EditImage(tables.LinkAction): name = "edit" verbose_name = _("Edit") - url = "horizon:project:images_and_snapshots:images:update" + url = "horizon:project:images:images:update" classes = ("ajax-modal", "btn-edit") policy_rules = (("image", "modify_image"),) @@ -101,7 +101,7 @@ class EditImage(tables.LinkAction): class CreateVolumeFromImage(tables.LinkAction): name = "create_volume_from_image" verbose_name = _("Create Volume") - url = "horizon:project:volumes:create" + url = "horizon:project:volumes:volumes:create" classes = ("ajax-modal", "btn-camera") policy_rules = (("volume", "volume:create"),) @@ -206,8 +206,7 @@ class ImagesTable(tables.DataTable): ("deleted", False), ) name = tables.Column(get_image_name, - link=("horizon:project:images_and_snapshots:" - "images:detail"), + link=("horizon:project:images:images:detail"), verbose_name=_("Image Name")) image_type = tables.Column(get_image_type, verbose_name=_("Type"), diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/images/tabs.py b/openstack_dashboard/dashboards/project/images/images/tabs.py similarity index 95% rename from openstack_dashboard/dashboards/project/images_and_snapshots/images/tabs.py rename to openstack_dashboard/dashboards/project/images/images/tabs.py index 8f76857efd..11757da678 100644 --- a/openstack_dashboard/dashboards/project/images_and_snapshots/images/tabs.py +++ b/openstack_dashboard/dashboards/project/images/images/tabs.py @@ -23,7 +23,7 @@ from horizon import tabs class OverviewTab(tabs.Tab): name = _("Overview") slug = "overview" - template_name = "project/images_and_snapshots/images/_detail_overview.html" + template_name = "project/images/images/_detail_overview.html" def get_context_data(self, request): image = self.tab_group.kwargs['image'] diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/images/tests.py b/openstack_dashboard/dashboards/project/images/images/tests.py similarity index 89% rename from openstack_dashboard/dashboards/project/images_and_snapshots/images/tests.py rename to openstack_dashboard/dashboards/project/images/images/tests.py index b7ec37b243..9458a5c165 100644 --- a/openstack_dashboard/dashboards/project/images_and_snapshots/images/tests.py +++ b/openstack_dashboard/dashboards/project/images/images/tests.py @@ -33,13 +33,11 @@ from horizon import tables as horizon_tables from openstack_dashboard import api from openstack_dashboard.test import helpers as test -from openstack_dashboard.dashboards.project.images_and_snapshots.images \ - import forms -from openstack_dashboard.dashboards.project.images_and_snapshots.images \ - import tables +from openstack_dashboard.dashboards.project.images.images import forms +from openstack_dashboard.dashboards.project.images.images import tables -IMAGES_INDEX_URL = reverse('horizon:project:images_and_snapshots:index') +IMAGES_INDEX_URL = reverse('horizon:project:images:index') class CreateImageFormTests(test.TestCase): @@ -74,10 +72,10 @@ class CreateImageFormTests(test.TestCase): class ImageViewTests(test.TestCase): def test_image_create_get(self): - url = reverse('horizon:project:images_and_snapshots:images:create') + url = reverse('horizon:project:images:images:create') res = self.client.get(url) self.assertTemplateUsed(res, - 'project/images_and_snapshots/images/create.html') + 'project/images/images/create.html') @test.create_stubs({api.glance: ('image_create',)}) def test_image_create_post_copy_from(self): @@ -111,7 +109,7 @@ class ImageViewTests(test.TestCase): AndReturn(self.images.first()) self.mox.ReplayAll() - url = reverse('horizon:project:images_and_snapshots:images:create') + url = reverse('horizon:project:images:images:create') res = self.client.post(url, data) self.assertNoFormErrors(res) @@ -151,7 +149,7 @@ class ImageViewTests(test.TestCase): AndReturn(self.images.first()) self.mox.ReplayAll() - url = reverse('horizon:project:images_and_snapshots:images:create') + url = reverse('horizon:project:images:images:create') res = self.client.post(url, data) self.assertNoFormErrors(res) @@ -165,12 +163,11 @@ class ImageViewTests(test.TestCase): .AndReturn(self.images.first()) self.mox.ReplayAll() - res = self.client.get( - reverse('horizon:project:images_and_snapshots:images:detail', - args=[image.id])) + res = self.client.get(reverse('horizon:project:images:images:detail', + args=[image.id])) self.assertTemplateUsed(res, - 'project/images_and_snapshots/images/detail.html') + 'project/images/images/detail.html') self.assertEqual(res.context['image'].name, image.name) self.assertEqual(res.context['image'].protected, image.protected) self.assertContains(res, "

Image Details: %s

" % image.name, @@ -184,9 +181,8 @@ class ImageViewTests(test.TestCase): .AndReturn(image) self.mox.ReplayAll() - res = self.client.get( - reverse('horizon:project:images_and_snapshots:images:detail', - args=[image.id])) + res = self.client.get(reverse('horizon:project:images:images:detail', + args=[image.id])) image_props = res.context['image_props'] @@ -213,10 +209,10 @@ class ImageViewTests(test.TestCase): self.mox.ReplayAll() res = self.client.get( - reverse('horizon:project:images_and_snapshots:images:detail', + reverse('horizon:project:images:images:detail', args=[image.id])) self.assertTemplateUsed(res, - 'project/images_and_snapshots/images/detail.html') + 'project/images/images/detail.html') self.assertEqual(res.context['image'].protected, image.protected) @test.create_stubs({api.glance: ('image_get',)}) @@ -227,7 +223,7 @@ class ImageViewTests(test.TestCase): .AndRaise(self.exceptions.glance) self.mox.ReplayAll() - url = reverse('horizon:project:images_and_snapshots:images:detail', + url = reverse('horizon:project:images:images:detail', args=[image.id]) res = self.client.get(url) self.assertRedirectsNoFollow(res, IMAGES_INDEX_URL) @@ -242,11 +238,11 @@ class ImageViewTests(test.TestCase): self.mox.ReplayAll() res = self.client.get( - reverse('horizon:project:images_and_snapshots:images:update', + reverse('horizon:project:images:images:update', args=[image.id])) self.assertTemplateUsed(res, - 'project/images_and_snapshots/images/_update.html') + 'project/images/images/_update.html') self.assertEqual(res.context['image'].name, image.name) # Bug 1076216 - is_public checkbox not being set correctly self.assertContains(res, " - {% trans "Cancel" %} + {% trans "Cancel" %} {% endblock %} diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/images/_detail_overview.html b/openstack_dashboard/dashboards/project/images/templates/images/images/_detail_overview.html similarity index 100% rename from openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/images/_detail_overview.html rename to openstack_dashboard/dashboards/project/images/templates/images/images/_detail_overview.html diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/images/_update.html b/openstack_dashboard/dashboards/project/images/templates/images/images/_update.html similarity index 73% rename from openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/images/_update.html rename to openstack_dashboard/dashboards/project/images/templates/images/images/_update.html index 24e0c162f8..aaeb356451 100644 --- a/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/images/_update.html +++ b/openstack_dashboard/dashboards/project/images/templates/images/images/_update.html @@ -3,7 +3,7 @@ {% load url from future %} {% block form_id %}update_image_form{% endblock %} -{% block form_action %}{% url 'horizon:project:images_and_snapshots:images:update' image.id %}{% endblock %} +{% block form_action %}{% url 'horizon:project:images:images:update' image.id %}{% endblock %} {% block modal-header %}{% trans "Update Image" %}{% endblock %} @@ -21,5 +21,5 @@ {% block modal-footer %} - {% trans "Cancel" %} + {% trans "Cancel" %} {% endblock %} diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/images/create.html b/openstack_dashboard/dashboards/project/images/templates/images/images/create.html similarity index 80% rename from openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/images/create.html rename to openstack_dashboard/dashboards/project/images/templates/images/images/create.html index e8f92f7f2d..2a6bc8eeae 100644 --- a/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/images/create.html +++ b/openstack_dashboard/dashboards/project/images/templates/images/images/create.html @@ -7,5 +7,5 @@ {% endblock page_header %} {% block main %} - {% include 'project/images_and_snapshots/images/_create.html' %} + {% include 'project/images/images/_create.html' %} {% endblock %} diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/images/detail.html b/openstack_dashboard/dashboards/project/images/templates/images/images/detail.html similarity index 100% rename from openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/images/detail.html rename to openstack_dashboard/dashboards/project/images/templates/images/images/detail.html diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/images/update.html b/openstack_dashboard/dashboards/project/images/templates/images/images/update.html similarity index 79% rename from openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/images/update.html rename to openstack_dashboard/dashboards/project/images/templates/images/images/update.html index 604898b89c..f692f9fa24 100644 --- a/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/images/update.html +++ b/openstack_dashboard/dashboards/project/images/templates/images/images/update.html @@ -7,5 +7,5 @@ {% endblock page_header %} {% block main %} - {% include 'project/images_and_snapshots/images/_update.html' %} + {% include 'project/images/images/_update.html' %} {% endblock %} diff --git a/openstack_dashboard/dashboards/project/images/templates/images/index.html b/openstack_dashboard/dashboards/project/images/templates/images/index.html new file mode 100644 index 0000000000..b82d337752 --- /dev/null +++ b/openstack_dashboard/dashboards/project/images/templates/images/index.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Images" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Images") %} +{% endblock page_header %} + +{% block main %} + {{ table.render }} +{% endblock %} diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/snapshots/_create.html b/openstack_dashboard/dashboards/project/images/templates/images/snapshots/_create.html similarity index 74% rename from openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/snapshots/_create.html rename to openstack_dashboard/dashboards/project/images/templates/images/snapshots/_create.html index 99d89488d9..862cfc48c7 100644 --- a/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/snapshots/_create.html +++ b/openstack_dashboard/dashboards/project/images/templates/images/snapshots/_create.html @@ -3,7 +3,7 @@ {% load url from future %} {% block form_id %}create_snapshot_form{% endblock %} -{% block form_action %}{% url 'horizon:project:images_and_snapshots:snapshots:create' instance.id %}{% endblock %} +{% block form_action %}{% url 'horizon:project:images:snapshots:create' instance.id %}{% endblock %} {% block modal_id %}create_snapshot_modal{% endblock %} {% block modal-header %}{% trans "Create Snapshot" %}{% endblock %} @@ -22,5 +22,5 @@ {% block modal-footer %} - {% trans "Cancel" %} + {% trans "Cancel" %} {% endblock %} diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/snapshots/create.html b/openstack_dashboard/dashboards/project/images/templates/images/snapshots/create.html similarity index 79% rename from openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/snapshots/create.html rename to openstack_dashboard/dashboards/project/images/templates/images/snapshots/create.html index 51c7d4b5cc..ae25b3a7f4 100644 --- a/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/snapshots/create.html +++ b/openstack_dashboard/dashboards/project/images/templates/images/snapshots/create.html @@ -7,5 +7,5 @@ {% endblock page_header %} {% block main %} - {% include 'project/images_and_snapshots/snapshots/_create.html' %} + {% include 'project/images/snapshots/_create.html' %} {% endblock %} diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/tests.py b/openstack_dashboard/dashboards/project/images/tests.py similarity index 84% rename from openstack_dashboard/dashboards/project/images_and_snapshots/tests.py rename to openstack_dashboard/dashboards/project/images/tests.py index 324991b7e6..d95369243d 100644 --- a/openstack_dashboard/dashboards/project/images_and_snapshots/tests.py +++ b/openstack_dashboard/dashboards/project/images/tests.py @@ -27,32 +27,23 @@ from mox import IsA # noqa from horizon import exceptions from openstack_dashboard import api -from openstack_dashboard.dashboards.project.images_and_snapshots import utils +from openstack_dashboard.dashboards.project.images import utils from openstack_dashboard.test import helpers as test -INDEX_URL = reverse('horizon:project:images_and_snapshots:index') +INDEX_URL = reverse('horizon:project:images:index') class ImagesAndSnapshotsTests(test.TestCase): - @test.create_stubs({api.glance: ('image_list_detailed',), - api.cinder: ('volume_snapshot_list', - 'volume_list',)}) + @test.create_stubs({api.glance: ('image_list_detailed',)}) def test_index(self): images = self.images.list() - vol_snaps = self.volume_snapshots.list() - volumes = self.volumes.list() - - api.cinder.volume_snapshot_list(IsA(http.HttpRequest)) \ - .AndReturn(vol_snaps) - api.cinder.volume_list(IsA(http.HttpRequest)) \ - .AndReturn(volumes) api.glance.image_list_detailed(IsA(http.HttpRequest), marker=None).AndReturn([images, False]) self.mox.ReplayAll() res = self.client.get(INDEX_URL) - self.assertTemplateUsed(res, 'project/images_and_snapshots/index.html') + self.assertTemplateUsed(res, 'project/images/index.html') self.assertIn('images_table', res.context) images_table = res.context['images_table'] images = images_table.data @@ -67,61 +58,34 @@ class ImagesAndSnapshotsTests(test.TestCase): row_actions = images_table.get_row_actions(images[2]) self.assertTrue(len(row_actions), 3) - @test.create_stubs({api.glance: ('image_list_detailed',), - api.cinder: ('volume_snapshot_list', - 'volume_list',)}) + @test.create_stubs({api.glance: ('image_list_detailed',)}) def test_index_no_images(self): - vol_snaps = self.volume_snapshots.list() - volumes = self.volumes.list() - - api.cinder.volume_snapshot_list(IsA(http.HttpRequest)) \ - .AndReturn(vol_snaps) - api.cinder.volume_list(IsA(http.HttpRequest)) \ - .AndReturn(volumes) api.glance.image_list_detailed(IsA(http.HttpRequest), marker=None).AndReturn([(), False]) self.mox.ReplayAll() res = self.client.get(INDEX_URL) - self.assertTemplateUsed(res, 'project/images_and_snapshots/index.html') + self.assertTemplateUsed(res, 'project/images/index.html') - @test.create_stubs({api.glance: ('image_list_detailed',), - api.cinder: ('volume_snapshot_list', - 'volume_list',)}) + @test.create_stubs({api.glance: ('image_list_detailed',)}) def test_index_error(self): - vol_snaps = self.volume_snapshots.list() - volumes = self.volumes.list() - - api.cinder.volume_snapshot_list(IsA(http.HttpRequest)) \ - .AndReturn(vol_snaps) - api.cinder.volume_list(IsA(http.HttpRequest)) \ - .AndReturn(volumes) api.glance.image_list_detailed(IsA(http.HttpRequest), marker=None) \ .AndRaise(self.exceptions.glance) self.mox.ReplayAll() res = self.client.get(INDEX_URL) - self.assertTemplateUsed(res, 'project/images_and_snapshots/index.html') + self.assertTemplateUsed(res, 'project/images/index.html') - @test.create_stubs({api.glance: ('image_list_detailed',), - api.cinder: ('volume_snapshot_list', - 'volume_list',)}) + @test.create_stubs({api.glance: ('image_list_detailed',)}) def test_snapshot_actions(self): snapshots = self.snapshots.list() - vol_snaps = self.volume_snapshots.list() - volumes = self.volumes.list() - - api.cinder.volume_snapshot_list(IsA(http.HttpRequest)) \ - .AndReturn(vol_snaps) - api.cinder.volume_list(IsA(http.HttpRequest)) \ - .AndReturn(volumes) api.glance.image_list_detailed(IsA(http.HttpRequest), marker=None) \ .AndReturn([snapshots, False]) self.mox.ReplayAll() res = self.client.get(INDEX_URL) - self.assertTemplateUsed(res, 'project/images_and_snapshots/index.html') + self.assertTemplateUsed(res, 'project/images/index.html') self.assertIn('images_table', res.context) snaps = res.context['images_table'] self.assertEqual(len(snaps.get_rows()), 3) diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/urls.py b/openstack_dashboard/dashboards/project/images/urls.py similarity index 78% rename from openstack_dashboard/dashboards/project/images_and_snapshots/urls.py rename to openstack_dashboard/dashboards/project/images/urls.py index 36d276c9a9..613fe95fd8 100644 --- a/openstack_dashboard/dashboards/project/images_and_snapshots/urls.py +++ b/openstack_dashboard/dashboards/project/images/urls.py @@ -22,18 +22,15 @@ from django.conf.urls import include # noqa from django.conf.urls import patterns # noqa from django.conf.urls import url # noqa -from openstack_dashboard.dashboards.project.images_and_snapshots.images \ +from openstack_dashboard.dashboards.project.images.images \ import urls as image_urls -from openstack_dashboard.dashboards.project.images_and_snapshots.snapshots \ +from openstack_dashboard.dashboards.project.images.snapshots \ import urls as snapshot_urls -from openstack_dashboard.dashboards.project.images_and_snapshots import views +from openstack_dashboard.dashboards.project.images import views urlpatterns = patterns('', url(r'^$', views.IndexView.as_view(), name='index'), url(r'', include(image_urls, namespace='images')), url(r'', include(snapshot_urls, namespace='snapshots')), - url(r'^snapshots/(?P[^/]+)/$', - views.DetailView.as_view(), - name='detail'), ) diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/utils.py b/openstack_dashboard/dashboards/project/images/utils.py similarity index 100% rename from openstack_dashboard/dashboards/project/images_and_snapshots/utils.py rename to openstack_dashboard/dashboards/project/images/utils.py diff --git a/openstack_dashboard/dashboards/project/images/views.py b/openstack_dashboard/dashboards/project/images/views.py new file mode 100644 index 0000000000..07611cd02b --- /dev/null +++ b/openstack_dashboard/dashboards/project/images/views.py @@ -0,0 +1,54 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2012 Nebula, Inc. +# Copyright 2012 OpenStack Foundation +# +# 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. + +""" +Views for managing Images and Snapshots. +""" + +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import tables + +from openstack_dashboard import api + +from openstack_dashboard.dashboards.project.images.images \ + import tables as images_tables + + +class IndexView(tables.DataTableView): + table_class = images_tables.ImagesTable + template_name = 'project/images/index.html' + + def has_more_data(self, table): + return getattr(self, "_more_%s" % table.name, False) + + def get_data(self): + marker = self.request.GET.get( + images_tables.ImagesTable._meta.pagination_param, None) + try: + (images, + self._more_images) = api.glance.image_list_detailed(self.request, + marker=marker) + except Exception: + images = [] + exceptions.handle(self.request, _("Unable to retrieve images.")) + return images diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/index.html b/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/index.html deleted file mode 100644 index 6e9667eb1d..0000000000 --- a/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/index.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block title %}{% trans "Images & Snapshots" %}{% endblock %} - -{% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("Images & Snapshots") %} -{% endblock page_header %} - -{% block main %} -
- {{ images_table.render }} -
-
- {{ volume_snapshots_table.render }} -
-{% endblock %} diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/views.py b/openstack_dashboard/dashboards/project/images_and_snapshots/views.py deleted file mode 100644 index 417f0aea62..0000000000 --- a/openstack_dashboard/dashboards/project/images_and_snapshots/views.py +++ /dev/null @@ -1,108 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Copyright 2012 Nebula, Inc. -# Copyright 2012 OpenStack Foundation -# -# 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. - -""" -Views for managing Images and Snapshots. -""" - -from django.core.urlresolvers import reverse -from django.utils.translation import ugettext_lazy as _ - -from horizon import exceptions -from horizon import tables -from horizon import tabs -from horizon.utils import memoized - -from openstack_dashboard import api -from openstack_dashboard.api import base - -from openstack_dashboard.dashboards.project.images_and_snapshots.images \ - import tables as images_tables -from openstack_dashboard.dashboards.project.images_and_snapshots.\ - volume_snapshots import tables as vol_snsh_tables -from openstack_dashboard.dashboards.project.images_and_snapshots.\ - volume_snapshots import tabs as vol_snsh_tabs - - -class IndexView(tables.MultiTableView): - table_classes = (images_tables.ImagesTable, - vol_snsh_tables.VolumeSnapshotsTable) - template_name = 'project/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( - images_tables.ImagesTable._meta.pagination_param, None) - try: - (images, - self._more_images) = api.glance.image_list_detailed(self.request, - marker=marker) - except Exception: - images = [] - exceptions.handle(self.request, _("Unable to retrieve images.")) - return images - - def get_volume_snapshots_data(self): - if base.is_service_enabled(self.request, 'volume'): - try: - snapshots = api.cinder.volume_snapshot_list(self.request) - volumes = api.cinder.volume_list(self.request) - volumes = dict((v.id, v) for v in volumes) - except Exception: - snapshots = [] - volumes = {} - exceptions.handle(self.request, _("Unable to retrieve " - "volume snapshots.")) - - for snapshot in snapshots: - volume = volumes.get(snapshot.volume_id) - setattr(snapshot, '_volume', volume) - - else: - snapshots = [] - return snapshots - - -class DetailView(tabs.TabView): - tab_group_class = vol_snsh_tabs.SnapshotDetailTabs - template_name = 'project/images_and_snapshots/snapshots/detail.html' - - def get_context_data(self, **kwargs): - context = super(DetailView, self).get_context_data(**kwargs) - context["snapshot"] = self.get_data() - return context - - @memoized.memoized_method - def get_data(self): - try: - snapshot_id = self.kwargs['snapshot_id'] - return api.cinder.volume_snapshot_get(self.request, snapshot_id) - except Exception: - url = reverse('horizon:project:images_and_snapshots:index') - exceptions.handle(self.request, - _('Unable to retrieve snapshot details.'), - redirect=url) - - def get_tabs(self, request, *args, **kwargs): - snapshot = self.get_data() - return self.tab_group_class(request, snapshot=snapshot, **kwargs) diff --git a/openstack_dashboard/dashboards/project/instances/forms.py b/openstack_dashboard/dashboards/project/instances/forms.py index 87a249b315..691fce54d9 100644 --- a/openstack_dashboard/dashboards/project/instances/forms.py +++ b/openstack_dashboard/dashboards/project/instances/forms.py @@ -27,7 +27,7 @@ from horizon.utils import fields from horizon.utils import validators from openstack_dashboard import api -from openstack_dashboard.dashboards.project.images_and_snapshots import utils +from openstack_dashboard.dashboards.project.images import utils def _image_choice_title(img): diff --git a/openstack_dashboard/dashboards/project/instances/tables.py b/openstack_dashboard/dashboards/project/instances/tables.py index d0d9cded3e..a9bfff7405 100644 --- a/openstack_dashboard/dashboards/project/instances/tables.py +++ b/openstack_dashboard/dashboards/project/instances/tables.py @@ -290,7 +290,7 @@ class EditInstanceSecurityGroups(EditInstance): class CreateSnapshot(tables.LinkAction): name = "snapshot" verbose_name = _("Create Snapshot") - url = "horizon:project:images_and_snapshots:snapshots:create" + url = "horizon:project:images:snapshots:create" classes = ("ajax-modal", "btn-camera") policy_rules = (("compute", "compute:snapshot"),) diff --git a/openstack_dashboard/dashboards/project/instances/templates/instances/_detail_overview.html b/openstack_dashboard/dashboards/project/instances/templates/instances/_detail_overview.html index 6377e88d77..01463f1677 100644 --- a/openstack_dashboard/dashboards/project/instances/templates/instances/_detail_overview.html +++ b/openstack_dashboard/dashboards/project/instances/templates/instances/_detail_overview.html @@ -100,7 +100,7 @@ {% with default_key_name=""|add:_("None")|add:"" %}
{{ instance.key_name|default:default_key_name }}
{% endwith %} - {% url 'horizon:project:images_and_snapshots:images:detail' instance.image.id as image_url %} + {% url 'horizon:project:images:images:detail' instance.image.id as image_url %}
{% trans "Image Name" %}
{{ instance.image_name }}
{% with default_item_value=""|add:_("N/A")|add:"" %} diff --git a/openstack_dashboard/dashboards/project/instances/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py index 6b70185ef9..ce646cd49d 100644 --- a/openstack_dashboard/dashboards/project/instances/tests.py +++ b/openstack_dashboard/dashboards/project/instances/tests.py @@ -855,8 +855,6 @@ class InstanceTests(test.TestCase): 'server_list', 'flavor_list', 'server_delete'), - cinder: ('volume_snapshot_list', - 'volume_list',), api.glance: ('image_list_detailed',)}) def test_create_instance_snapshot(self): server = self.servers.first() @@ -868,17 +866,15 @@ class InstanceTests(test.TestCase): api.glance.image_list_detailed(IsA(http.HttpRequest), marker=None).AndReturn([[], False]) - cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([]) - cinder.volume_list(IsA(http.HttpRequest)).AndReturn([]) self.mox.ReplayAll() formData = {'instance_id': server.id, 'method': 'CreateSnapshot', 'name': 'snapshot1'} - url = reverse('horizon:project:images_and_snapshots:snapshots:create', + url = reverse('horizon:project:images:snapshots:create', args=[server.id]) - redir_url = reverse('horizon:project:images_and_snapshots:index') + redir_url = reverse('horizon:project:images:index') res = self.client.post(url, formData) self.assertRedirects(res, redir_url) diff --git a/openstack_dashboard/dashboards/project/instances/workflows/create_instance.py b/openstack_dashboard/dashboards/project/instances/workflows/create_instance.py index 3adf3e915f..aa84ae270e 100644 --- a/openstack_dashboard/dashboards/project/instances/workflows/create_instance.py +++ b/openstack_dashboard/dashboards/project/instances/workflows/create_instance.py @@ -40,7 +40,7 @@ from openstack_dashboard.api import base from openstack_dashboard.api import cinder from openstack_dashboard.usage import quotas -from openstack_dashboard.dashboards.project.images_and_snapshots import utils +from openstack_dashboard.dashboards.project.images import utils LOG = logging.getLogger(__name__) diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/__init__.py b/openstack_dashboard/dashboards/project/volumes/snapshots/__init__.py similarity index 100% rename from openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/__init__.py rename to openstack_dashboard/dashboards/project/volumes/snapshots/__init__.py diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tables.py b/openstack_dashboard/dashboards/project/volumes/snapshots/tables.py similarity index 91% rename from openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tables.py rename to openstack_dashboard/dashboards/project/volumes/snapshots/tables.py index ec6d7eedbb..1993e1e7ca 100644 --- a/openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tables.py +++ b/openstack_dashboard/dashboards/project/volumes/snapshots/tables.py @@ -27,7 +27,7 @@ from openstack_dashboard.api import base from openstack_dashboard.api import cinder from openstack_dashboard.dashboards.project.volumes \ - import tables as volume_tables + .volumes import tables as volume_tables class DeleteVolumeSnapshot(tables.DeleteAction): @@ -51,7 +51,7 @@ class DeleteVolumeSnapshot(tables.DeleteAction): class CreateVolumeFromSnapshot(tables.LinkAction): name = "create_from_snapshot" verbose_name = _("Create Volume") - url = "horizon:project:volumes:create" + url = "horizon:project:volumes:volumes:create" classes = ("ajax-modal", "btn-camera") policy_rules = (("volume", "volume:create"),) @@ -95,10 +95,11 @@ class SnapshotVolumeNameColumn(tables.Column): class VolumeSnapshotsTable(volume_tables.VolumesTableBase): name = tables.Column("display_name", verbose_name=_("Name"), - link="horizon:project:images_and_snapshots:detail") - volume_name = SnapshotVolumeNameColumn("display_name", - verbose_name=_("Volume Name"), - link="horizon:project:volumes:detail") + link="horizon:project:volumes:detail") + volume_name = SnapshotVolumeNameColumn( + "display_name", + verbose_name=_("Volume Name"), + link="horizon:project:volumes:volumes:detail") class Meta: name = "volume_snapshots" diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tabs.py b/openstack_dashboard/dashboards/project/volumes/snapshots/tabs.py similarity index 88% rename from openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tabs.py rename to openstack_dashboard/dashboards/project/volumes/snapshots/tabs.py index 84e2a920c4..46cf555b63 100644 --- a/openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tabs.py +++ b/openstack_dashboard/dashboards/project/volumes/snapshots/tabs.py @@ -26,15 +26,14 @@ from openstack_dashboard.api import cinder class OverviewTab(tabs.Tab): name = _("Overview") slug = "overview" - template_name = ("project/images_and_snapshots/snapshots/" - "_detail_overview.html") + template_name = ("project/volumes/snapshots/_detail_overview.html") def get_context_data(self, request): try: snapshot = self.tab_group.kwargs['snapshot'] volume = cinder.volume_get(request, snapshot.volume_id) except Exception: - redirect = reverse('horizon:project:images_and_snapshots:index') + redirect = reverse('horizon:project:volumes:index') exceptions.handle(self.request, _('Unable to retrieve snapshot details.'), redirect=redirect) diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tests.py b/openstack_dashboard/dashboards/project/volumes/snapshots/tests.py similarity index 86% rename from openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tests.py rename to openstack_dashboard/dashboards/project/volumes/snapshots/tests.py index 73fa60f02c..5e56fa51cd 100644 --- a/openstack_dashboard/dashboards/project/images_and_snapshots/volume_snapshots/tests.py +++ b/openstack_dashboard/dashboards/project/volumes/snapshots/tests.py @@ -28,7 +28,7 @@ from openstack_dashboard.test import helpers as test from openstack_dashboard.usage import quotas -INDEX_URL = reverse('horizon:project:images_and_snapshots:index') +INDEX_URL = reverse('horizon:project:volumes:index') class VolumeSnapshotsViewTests(test.TestCase): @@ -46,11 +46,12 @@ class VolumeSnapshotsViewTests(test.TestCase): AndReturn(usage_limit) self.mox.ReplayAll() - url = reverse('horizon:project:volumes:create_snapshot', - args=[volume.id]) + url = reverse('horizon:project:volumes:' + 'volumes:create_snapshot', args=[volume.id]) res = self.client.get(url) - self.assertTemplateUsed(res, 'project/volumes/create_snapshot.html') + self.assertTemplateUsed(res, 'project/volumes/volumes/' + 'create_snapshot.html') @test.create_stubs({cinder: ('volume_get', 'volume_snapshot_create',)}) @@ -73,7 +74,7 @@ class VolumeSnapshotsViewTests(test.TestCase): 'volume_id': volume.id, 'name': snapshot.display_name, 'description': snapshot.display_description} - url = reverse('horizon:project:volumes:create_snapshot', + url = reverse('horizon:project:volumes:volumes:create_snapshot', args=[volume.id]) res = self.client.post(url, formData) self.assertRedirectsNoFollow(res, INDEX_URL) @@ -99,12 +100,12 @@ class VolumeSnapshotsViewTests(test.TestCase): 'volume_id': volume.id, 'name': snapshot.display_name, 'description': snapshot.display_description} - url = reverse('horizon:project:volumes:create_snapshot', + url = reverse('horizon:project:volumes:volumes:create_snapshot', args=[volume.id]) res = self.client.post(url, formData) self.assertRedirectsNoFollow(res, INDEX_URL) - @test.create_stubs({api.glance: ('image_list_detailed',), + @test.create_stubs({api.nova: ('server_list',), api.cinder: ('volume_snapshot_list', 'volume_list', 'volume_snapshot_delete')}) @@ -113,20 +114,20 @@ class VolumeSnapshotsViewTests(test.TestCase): volumes = self.volumes.list() snapshot = self.volume_snapshots.first() - api.glance.image_list_detailed(IsA(http.HttpRequest), - marker=None).AndReturn(([], False)) api.cinder.volume_snapshot_list(IsA(http.HttpRequest)). \ AndReturn(vol_snapshots) - api.cinder.volume_list(IsA(http.HttpRequest)) \ - .AndReturn(volumes) + api.cinder.volume_list(IsA(http.HttpRequest)). \ + AndReturn(volumes) api.cinder.volume_snapshot_delete(IsA(http.HttpRequest), snapshot.id) - api.glance.image_list_detailed(IsA(http.HttpRequest), - marker=None).AndReturn(([], False)) + api.cinder.volume_list(IsA(http.HttpRequest), search_opts=None). \ + AndReturn(volumes) + api.nova.server_list(IsA(http.HttpRequest), search_opts=None). \ + AndReturn([self.servers.list(), False]) api.cinder.volume_snapshot_list(IsA(http.HttpRequest)). \ AndReturn([]) - api.cinder.volume_list(IsA(http.HttpRequest)) \ - .AndReturn(volumes) + api.cinder.volume_list(IsA(http.HttpRequest)). \ + AndReturn(volumes) self.mox.ReplayAll() formData = {'action': @@ -148,7 +149,7 @@ class VolumeSnapshotsViewTests(test.TestCase): self.mox.ReplayAll() - url = reverse('horizon:project:images_and_snapshots:detail', + url = reverse('horizon:project:volumes:detail', args=[snapshot.id]) res = self.client.get(url) @@ -172,7 +173,7 @@ class VolumeSnapshotsViewTests(test.TestCase): AndRaise(self.exceptions.cinder) self.mox.ReplayAll() - url = reverse('horizon:project:images_and_snapshots:detail', + url = reverse('horizon:project:volumes:detail', args=[snapshot.id]) res = self.client.get(url) @@ -191,7 +192,7 @@ class VolumeSnapshotsViewTests(test.TestCase): self.mox.ReplayAll() - url = reverse('horizon:project:images_and_snapshots:detail', + url = reverse('horizon:project:volumes:detail', args=[snapshot.id]) res = self.client.get(url) diff --git a/openstack_dashboard/dashboards/project/volumes/tabs.py b/openstack_dashboard/dashboards/project/volumes/tabs.py index 5310654d02..45c1166d10 100644 --- a/openstack_dashboard/dashboards/project/volumes/tabs.py +++ b/openstack_dashboard/dashboards/project/volumes/tabs.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2012 Nebula, Inc. +# Copyright 2013 Nebula, 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 @@ -14,21 +14,98 @@ # License for the specific language governing permissions and limitations # under the License. +from django.utils.datastructures import SortedDict from django.utils.translation import ugettext_lazy as _ +from horizon import exceptions from horizon import tabs +from openstack_dashboard import api -class OverviewTab(tabs.Tab): - name = _("Overview") - slug = "overview" - template_name = ("project/volumes/" - "_detail_overview.html") - - def get_context_data(self, request): - return {"volume": self.tab_group.kwargs['volume']} +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 -class VolumeDetailTabs(tabs.TabGroup): - slug = "volume_details" - tabs = (OverviewTab,) +class VolumeTableMixIn(object): + def _get_volumes(self, search_opts=None): + try: + return api.cinder.volume_list(self.request, + search_opts=search_opts) + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve volume list.')) + return [] + + def _get_instances(self, search_opts=None): + try: + instances, has_more = api.nova.server_list(self.request, + search_opts=search_opts) + return instances + except Exception: + exceptions.handle(self.request, + _("Unable to retrieve volume/instance " + "attachment information")) + return [] + + def _set_id_if_nameless(self, volumes): + for volume in volumes: + # It is possible to create a volume with no name through the + # EC2 API, use the ID in those cases. + if not volume.display_name: + volume.display_name = volume.id + + def _set_attachments_string(self, volumes, instances): + instances = SortedDict([(inst.id, inst) for inst in instances]) + for volume in volumes: + for att in volume.attachments: + server_id = att.get('server_id', None) + att['instance'] = instances.get(server_id, None) + + +class VolumeTab(tabs.TableTab, VolumeTableMixIn): + table_classes = (volume_tables.VolumesTable,) + name = _("Volumes") + slug = "volumes_tab" + template_name = ("horizon/common/_detail_table.html") + + def get_volumes_data(self): + volumes = self._get_volumes() + instances = self._get_instances() + self._set_id_if_nameless(volumes) + self._set_attachments_string(volumes, instances) + return volumes + + +class SnapshotTab(tabs.TableTab): + table_classes = (vol_snapshot_tables.VolumeSnapshotsTable,) + name = _("Volume Snapshots") + slug = "snapshots_tab" + template_name = ("horizon/common/_detail_table.html") + + def get_volume_snapshots_data(self): + if api.base.is_service_enabled(self.request, 'volume'): + try: + snapshots = api.cinder.volume_snapshot_list(self.request) + volumes = api.cinder.volume_list(self.request) + volumes = dict((v.id, v) for v in volumes) + except Exception: + snapshots = [] + volumes = {} + exceptions.handle(self.request, _("Unable to retrieve " + "volume snapshots.")) + + for snapshot in snapshots: + volume = volumes.get(snapshot.volume_id) + setattr(snapshot, '_volume', volume) + + else: + snapshots = [] + return snapshots + + +class VolumeAndSnapshotTabs(tabs.TabGroup): + slug = "volumes_and_snapshots" + tabs = (VolumeTab, SnapshotTab,) + sticky = True diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/index.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/index.html index 201e774263..5df0fcaae4 100644 --- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/index.html +++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/index.html @@ -1,11 +1,15 @@ {% extends 'base.html' %} {% load i18n %} -{% block title %}{% trans "Volumes" %}{% endblock %} +{% block title %}{% trans "Volumes & Snapshots" %}{% endblock %} {% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("Volumes") %} + {% include "horizon/common/_page_header.html" with title=_("Volumes & Snapshots")%} {% endblock page_header %} {% block main %} - {{ table.render }} +
+
+ {{ tab_group.render }} +
+
{% endblock %} diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/snapshots/_detail_overview.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/snapshots/_detail_overview.html similarity index 92% rename from openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/snapshots/_detail_overview.html rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/snapshots/_detail_overview.html index 079c02be72..11fb958be4 100644 --- a/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/snapshots/_detail_overview.html +++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/snapshots/_detail_overview.html @@ -19,7 +19,7 @@
{{ snapshot.status|capfirst }}
{% trans "Volume" %}
- + {% if volume.display_name %} {{ volume.display_name }} {% else %} diff --git a/openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/snapshots/detail.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/snapshots/detail.html similarity index 100% rename from openstack_dashboard/dashboards/project/images_and_snapshots/templates/images_and_snapshots/snapshots/detail.html rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/snapshots/detail.html diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/_attach.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_attach.html similarity index 90% rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/_attach.html rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_attach.html index 33db6f44d9..b86babe7e0 100644 --- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/_attach.html +++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_attach.html @@ -3,7 +3,7 @@ {% load url from future %} {% block form_id %}attach_volume_form{% endblock %} -{% block form_action %}{% url 'horizon:project:volumes:attach' volume.id %}{% endblock %} +{% block form_action %}{% url 'horizon:project:volumes:volumes:attach' volume.id %}{% endblock %} {% block form_class %}{{ block.super }} horizontal {% if show_attach %}split_half{% else %} no_split{% endif %}{% endblock %} {% block modal_id %}attach_volume_modal{% endblock %} diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/_create.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_create.html similarity index 78% rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/_create.html rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_create.html index 8966bf8500..30b73c6daa 100644 --- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/_create.html +++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_create.html @@ -3,7 +3,7 @@ {% load url from future %} {% block form_id %}{% endblock %} -{% block form_action %}{% url 'horizon:project:volumes:create' %}?{{ request.GET.urlencode }}{% endblock %} +{% block form_action %}{% url 'horizon:project:volumes:volumes:create' %}?{{ request.GET.urlencode }}{% endblock %} {% block modal_id %}create_volume_modal{% endblock %} {% block modal-header %}{% trans "Create Volume" %}{% endblock %} @@ -16,7 +16,7 @@
- {% include "project/volumes/_limits.html" with usages=usages %} + {% include "project/volumes/volumes/_limits.html" with usages=usages %}
{% endblock %} diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/_create_snapshot.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_create_snapshot.html similarity index 81% rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/_create_snapshot.html rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_create_snapshot.html index c99a8792a2..700372e09f 100644 --- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/_create_snapshot.html +++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_create_snapshot.html @@ -3,7 +3,7 @@ {% load url from future %} {% block form_id %}{% endblock %} -{% block form_action %}{% url 'horizon:project:volumes:create_snapshot' volume_id %}{% endblock %} +{% block form_action %}{% url 'horizon:project:volumes:volumes:create_snapshot' volume_id %}{% endblock %} {% block modal_id %}create_volume_snapshot_modal{% endblock %} {% block modal-header %}{% trans "Create Volume Snapshot" %}{% endblock %} @@ -15,7 +15,7 @@
- {% include "project/volumes/_limits.html" with usages=usages snapshot_quota=True %} + {% include "project/volumes/volumes/_limits.html" with usages=usages snapshot_quota=True %}
{% endblock %} diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/_detail_overview.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_detail_overview.html similarity index 100% rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/_detail_overview.html rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_detail_overview.html diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/_extend.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_extend.html similarity index 79% rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/_extend.html rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_extend.html index afe299e33c..0cd1bc1a2a 100644 --- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/_extend.html +++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_extend.html @@ -3,7 +3,7 @@ {% load url from future %} {% block form_id %}{% endblock %} -{% block form_action %}{% url 'horizon:project:volumes:extend' volume.id%}{% endblock %} +{% block form_action %}{% url 'horizon:project:volumes:volumes:extend' volume.id%}{% endblock %} {% block modal_id %}extend_volume_modal{% endblock %} {% block modal-header %}{% trans "Extend Volume" %}{% endblock %} @@ -16,7 +16,7 @@
- {% include "project/volumes/_extend_limits.html" with usages=usages %} + {% include "project/volumes/volumes/_extend_limits.html" with usages=usages %}
{% endblock %} diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/_extend_limits.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_extend_limits.html similarity index 100% rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/_extend_limits.html rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_extend_limits.html diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/_limits.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_limits.html similarity index 100% rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/_limits.html rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_limits.html diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/_update.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_update.html similarity index 89% rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/_update.html rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_update.html index b1da294c16..f485deb97d 100644 --- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/_update.html +++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/_update.html @@ -3,7 +3,7 @@ {% load url from future %} {% block form_id %}{% endblock %} -{% block form_action %}{% url 'horizon:project:volumes:update' volume.id %}{% endblock %} +{% block form_action %}{% url 'horizon:project:volumes:volumes:update' volume.id %}{% endblock %} {% block modal_id %}update_volume_modal{% endblock %} {% block modal-header %}{% trans "Edit Volume" %}{% endblock %} diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/attach.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/attach.html similarity index 83% rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/attach.html rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/attach.html index 4b4dad8663..d2ec655f14 100644 --- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/attach.html +++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/attach.html @@ -7,5 +7,5 @@ {% endblock page_header %} {% block main %} - {% include 'project/volumes/_attach.html' %} + {% include 'project/volumes/volumes/_attach.html' %} {% endblock %} diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/create.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/create.html similarity index 82% rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/create.html rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/create.html index 08710c7baa..b4989f2e2c 100644 --- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/create.html +++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/create.html @@ -7,5 +7,5 @@ {% endblock page_header %} {% block main %} - {% include 'project/volumes/_create.html' %} + {% include 'project/volumes/volumes/_create.html' %} {% endblock %} diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/create_snapshot.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/create_snapshot.html similarity index 81% rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/create_snapshot.html rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/create_snapshot.html index 4aa6562eb0..c8c07cdd51 100644 --- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/create_snapshot.html +++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/create_snapshot.html @@ -7,5 +7,5 @@ {% endblock page_header %} {% block main %} - {% include 'project/volumes/_create_snapshot.html' %} + {% include 'project/volumes/volumes/_create_snapshot.html' %} {% endblock %} diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/detail.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/detail.html similarity index 100% rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/detail.html rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/detail.html diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/extend.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/extend.html similarity index 82% rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/extend.html rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/extend.html index 35275fafcb..635fcaa08d 100644 --- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/extend.html +++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/extend.html @@ -7,5 +7,5 @@ {% endblock page_header %} {% block main %} - {% include 'project/volumes/_extend.html' %} + {% include 'project/volumes/volumes/_extend.html' %} {% endblock %} diff --git a/openstack_dashboard/dashboards/project/volumes/templates/volumes/update.html b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/update.html similarity index 82% rename from openstack_dashboard/dashboards/project/volumes/templates/volumes/update.html rename to openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/update.html index e23545393b..88239ec75e 100644 --- a/openstack_dashboard/dashboards/project/volumes/templates/volumes/update.html +++ b/openstack_dashboard/dashboards/project/volumes/templates/volumes/volumes/update.html @@ -7,5 +7,5 @@ {% endblock page_header %} {% block main %} - {% include 'project/volumes/_update.html' %} + {% include 'project/volumes/volumes/_update.html' %} {% endblock %} diff --git a/openstack_dashboard/dashboards/project/volumes/test.py b/openstack_dashboard/dashboards/project/volumes/test.py new file mode 100644 index 0000000000..5d203fa333 --- /dev/null +++ b/openstack_dashboard/dashboards/project/volumes/test.py @@ -0,0 +1,48 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Nebula, 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.core.urlresolvers import reverse +from django import http + +from mox import IsA # noqa + +from openstack_dashboard import api +from openstack_dashboard.test import helpers as test + + +INDEX_URL = reverse('horizon:project:volumes:index') + + +class VolumeAndSnapshotsTests(test.TestCase): + @test.create_stubs({api.cinder: ('volume_list', + 'volume_snapshot_list',), + api.nova: ('server_list',)}) + def test_index(self): + vol_snaps = self.volume_snapshots.list() + volumes = self.volumes.list() + + api.cinder.volume_list(IsA(http.HttpRequest), search_opts=None).\ + AndReturn(volumes) + api.nova.server_list(IsA(http.HttpRequest), search_opts=None).\ + AndReturn([self.servers.list(), False]) + api.cinder.volume_snapshot_list(IsA(http.HttpRequest)).\ + AndReturn(vol_snaps) + api.cinder.volume_list(IsA(http.HttpRequest)).AndReturn(volumes) + + self.mox.ReplayAll() + + res = self.client.get(INDEX_URL) + self.assertTemplateUsed(res, 'project/volumes/index.html') diff --git a/openstack_dashboard/dashboards/project/volumes/urls.py b/openstack_dashboard/dashboards/project/volumes/urls.py index ba2aecc064..3aefa61b8b 100644 --- a/openstack_dashboard/dashboards/project/volumes/urls.py +++ b/openstack_dashboard/dashboards/project/volumes/urls.py @@ -14,28 +14,19 @@ # License for the specific language governing permissions and limitations # under the License. +from django.conf.urls import include # noqa from django.conf.urls import patterns # noqa from django.conf.urls import url # noqa from openstack_dashboard.dashboards.project.volumes import views +from openstack_dashboard.dashboards.project.volumes.volumes \ + import urls as volume_urls -urlpatterns = patterns('openstack_dashboard.dashboards.project.volumes.views', +urlpatterns = patterns('', url(r'^$', views.IndexView.as_view(), name='index'), - url(r'^create/$', views.CreateView.as_view(), name='create'), - url(r'^(?P[^/]+)/extend/$', - views.ExtendView.as_view(), - name='extend'), - url(r'^(?P[^/]+)/attach/$', - views.EditAttachmentsView.as_view(), - name='attach'), - url(r'^(?P[^/]+)/create_snapshot/$', - views.CreateSnapshotView.as_view(), - name='create_snapshot'), - url(r'^(?P[^/]+)/$', + url(r'', include(volume_urls, namespace='volumes')), + url(r'^snapshots/(?P[^/]+)/$', views.DetailView.as_view(), name='detail'), - url(r'^(?P[^/]+)/update/$', - views.UpdateView.as_view(), - name='update'), ) diff --git a/openstack_dashboard/dashboards/project/volumes/views.py b/openstack_dashboard/dashboards/project/volumes/views.py index 66084d8a98..5fa09a63f3 100644 --- a/openstack_dashboard/dashboards/project/volumes/views.py +++ b/openstack_dashboard/dashboards/project/volumes/views.py @@ -14,279 +14,47 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Views for managing volumes. -""" - from django.core.urlresolvers import reverse -from django.core.urlresolvers import reverse_lazy -from django.utils.datastructures import SortedDict 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.api import cinder -from openstack_dashboard.usage import quotas -from openstack_dashboard.dashboards.project.volumes \ - import forms as project_forms - -from openstack_dashboard.dashboards.project.volumes \ - import tables as project_tables from openstack_dashboard.dashboards.project.volumes \ import tabs as project_tabs +from openstack_dashboard.dashboards.project.volumes \ + .snapshots import tabs as vol_snapshot_tabs -class VolumeTableMixIn(object): - def _get_volumes(self, search_opts=None): - try: - return cinder.volume_list(self.request, search_opts=search_opts) - except Exception: - exceptions.handle(self.request, - _('Unable to retrieve volume list.')) - return [] - - def _get_instances(self, search_opts=None): - try: - instances, has_more = api.nova.server_list(self.request, - search_opts=search_opts) - return instances - except Exception: - exceptions.handle(self.request, - _("Unable to retrieve volume/instance " - "attachment information")) - return [] - - def _set_id_if_nameless(self, volumes): - for volume in volumes: - # It is possible to create a volume with no name through the - # EC2 API, use the ID in those cases. - if not volume.display_name: - volume.display_name = volume.id - - def _set_attachments_string(self, volumes, instances): - instances = SortedDict([(inst.id, inst) for inst in instances]) - for volume in volumes: - for att in volume.attachments: - server_id = att.get('server_id', None) - att['instance'] = instances.get(server_id, None) - - -class IndexView(tables.DataTableView, VolumeTableMixIn): - table_class = project_tables.VolumesTable +class IndexView(tabs.TabbedTableView): + tab_group_class = project_tabs.VolumeAndSnapshotTabs template_name = 'project/volumes/index.html' - def get_data(self): - volumes = self._get_volumes() - instances = self._get_instances() - self._set_id_if_nameless(volumes) - self._set_attachments_string(volumes, instances) - return volumes - class DetailView(tabs.TabView): - tab_group_class = project_tabs.VolumeDetailTabs - template_name = 'project/volumes/detail.html' + tab_group_class = vol_snapshot_tabs.SnapshotDetailTabs + template_name = 'project/volumes/snapshots/detail.html' def get_context_data(self, **kwargs): context = super(DetailView, self).get_context_data(**kwargs) - context["volume"] = self.get_data() + context["snapshot"] = self.get_data() return context @memoized.memoized_method def get_data(self): try: - volume_id = self.kwargs['volume_id'] - volume = cinder.volume_get(self.request, volume_id) - for att in volume.attachments: - att['instance'] = api.nova.server_get(self.request, - att['server_id']) + snapshot_id = self.kwargs['snapshot_id'] + snapshot = cinder.volume_snapshot_get(self.request, snapshot_id) except Exception: redirect = reverse('horizon:project:volumes:index') exceptions.handle(self.request, - _('Unable to retrieve volume details.'), + _('Unable to retrieve snapshot details.'), redirect=redirect) - return volume + return snapshot def get_tabs(self, request, *args, **kwargs): - volume = self.get_data() - return self.tab_group_class(request, volume=volume, **kwargs) - - -class CreateView(forms.ModalFormView): - form_class = project_forms.CreateForm - template_name = 'project/volumes/create.html' - success_url = reverse_lazy("horizon:project:volumes:index") - - def get_context_data(self, **kwargs): - context = super(CreateView, self).get_context_data(**kwargs) - try: - context['usages'] = quotas.tenant_limit_usages(self.request) - except Exception: - exceptions.handle(self.request) - return context - - -class ExtendView(forms.ModalFormView): - form_class = project_forms.ExtendForm - template_name = 'project/volumes/extend.html' - success_url = reverse_lazy("horizon:project:volumes:index") - - def get_object(self): - if not hasattr(self, "_object"): - volume_id = self.kwargs['volume_id'] - try: - self._object = cinder.volume_get(self.request, volume_id) - except Exception: - self._object = None - exceptions.handle(self.request, - _('Unable to retrieve volume information.')) - return self._object - - def get_context_data(self, **kwargs): - context = super(ExtendView, self).get_context_data(**kwargs) - context['volume'] = self.get_object() - try: - usages = quotas.tenant_limit_usages(self.request) - usages['gigabytesUsed'] = (usages['gigabytesUsed'] - - context['volume'].size) - context['usages'] = usages - except Exception: - exceptions.handle(self.request) - return context - - def get_initial(self): - volume = self.get_object() - return {'id': self.kwargs['volume_id'], - 'name': volume.display_name, - 'orig_size': volume.size} - - -class CreateSnapshotView(forms.ModalFormView): - form_class = project_forms.CreateSnapshotForm - template_name = 'project/volumes/create_snapshot.html' - success_url = reverse_lazy("horizon:project:images_and_snapshots:index") - - def get_context_data(self, **kwargs): - context = super(CreateSnapshotView, self).get_context_data(**kwargs) - context['volume_id'] = self.kwargs['volume_id'] - try: - volume = cinder.volume_get(self.request, context['volume_id']) - if (volume.status == 'in-use'): - context['attached'] = True - context['form'].set_warning(_("This volume is currently " - "attached to an instance. " - "In some cases, creating a " - "snapshot from an attached " - "volume can result in a " - "corrupted snapshot.")) - context['usages'] = quotas.tenant_limit_usages(self.request) - except Exception: - exceptions.handle(self.request, - _('Unable to retrieve volume information.')) - return context - - def get_initial(self): - return {'volume_id': self.kwargs["volume_id"]} - - -class UpdateView(forms.ModalFormView): - form_class = project_forms.UpdateForm - template_name = 'project/volumes/update.html' - success_url = reverse_lazy("horizon:project:volumes:index") - - def get_object(self): - if not hasattr(self, "_object"): - vol_id = self.kwargs['volume_id'] - try: - self._object = cinder.volume_get(self.request, vol_id) - except Exception: - msg = _('Unable to retrieve volume.') - url = reverse('horizon:project:volumes:index') - exceptions.handle(self.request, msg, redirect=url) - return self._object - - def get_context_data(self, **kwargs): - context = super(UpdateView, self).get_context_data(**kwargs) - context['volume'] = self.get_object() - return context - - def get_initial(self): - volume = self.get_object() - return {'volume_id': self.kwargs["volume_id"], - 'name': volume.display_name, - 'description': volume.display_description} - - -class EditAttachmentsView(tables.DataTableView, forms.ModalFormView): - table_class = project_tables.AttachmentsTable - form_class = project_forms.AttachForm - template_name = 'project/volumes/attach.html' - success_url = reverse_lazy("horizon:project:volumes:index") - - @memoized.memoized_method - def get_object(self): - volume_id = self.kwargs['volume_id'] - try: - return cinder.volume_get(self.request, volume_id) - except Exception: - self._object = None - exceptions.handle(self.request, - _('Unable to retrieve volume information.')) - - def get_data(self): - try: - volumes = self.get_object() - attachments = [att for att in volumes.attachments if att] - except Exception: - attachments = [] - exceptions.handle(self.request, - _('Unable to retrieve volume information.')) - return attachments - - def get_initial(self): - try: - instances, has_more = api.nova.server_list(self.request) - except Exception: - instances = [] - exceptions.handle(self.request, - _("Unable to retrieve attachment information.")) - return {'volume': self.get_object(), - 'instances': instances} - - @memoized.memoized_method - def get_form(self): - form_class = self.get_form_class() - return super(EditAttachmentsView, self).get_form(form_class) - - def get_context_data(self, **kwargs): - context = super(EditAttachmentsView, self).get_context_data(**kwargs) - context['form'] = self.get_form() - volume = self.get_object() - if volume and volume.status == 'available': - context['show_attach'] = True - else: - context['show_attach'] = False - context['volume'] = volume - if self.request.is_ajax(): - context['hide'] = True - return context - - def get(self, request, *args, **kwargs): - # Table action handling - handled = self.construct_tables() - if handled: - return handled - return self.render_to_response(self.get_context_data(**kwargs)) - - def post(self, request, *args, **kwargs): - form = self.get_form() - if form.is_valid(): - return self.form_valid(form) - else: - return self.get(request, *args, **kwargs) + snapshot = self.get_data() + return self.tab_group_class(request, snapshot=snapshot, **kwargs) diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/__init__.py b/openstack_dashboard/dashboards/project/volumes/volumes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/dashboards/project/volumes/forms.py b/openstack_dashboard/dashboards/project/volumes/volumes/forms.py similarity index 99% rename from openstack_dashboard/dashboards/project/volumes/forms.py rename to openstack_dashboard/dashboards/project/volumes/volumes/forms.py index 0e94e0d92f..4d80238663 100644 --- a/openstack_dashboard/dashboards/project/volumes/forms.py +++ b/openstack_dashboard/dashboards/project/volumes/volumes/forms.py @@ -35,7 +35,7 @@ from horizon.utils.memoized import memoized # noqa from openstack_dashboard import api from openstack_dashboard.api import cinder from openstack_dashboard.api import glance -from openstack_dashboard.dashboards.project.images_and_snapshots import utils +from openstack_dashboard.dashboards.project.images import utils from openstack_dashboard.dashboards.project.instances import tables from openstack_dashboard.usage import quotas @@ -466,7 +466,7 @@ class CreateSnapshotForm(forms.SelfHandlingForm): messages.info(request, message) return snapshot except Exception: - redirect = reverse("horizon:project:images_and_snapshots:index") + redirect = reverse("horizon:project:volumes:index") exceptions.handle(request, _('Unable to create volume snapshot.'), redirect=redirect) diff --git a/openstack_dashboard/dashboards/project/volumes/tables.py b/openstack_dashboard/dashboards/project/volumes/volumes/tables.py similarity index 96% rename from openstack_dashboard/dashboards/project/volumes/tables.py rename to openstack_dashboard/dashboards/project/volumes/volumes/tables.py index a0831806d5..f2969df8de 100644 --- a/openstack_dashboard/dashboards/project/volumes/tables.py +++ b/openstack_dashboard/dashboards/project/volumes/volumes/tables.py @@ -67,7 +67,7 @@ class DeleteVolume(tables.DeleteAction): class CreateVolume(tables.LinkAction): name = "create" verbose_name = _("Create Volume") - url = "horizon:project:volumes:create" + url = "horizon:project:volumes:volumes:create" classes = ("ajax-modal", "btn-create") policy_rules = (("volume", "volume:create"),) @@ -89,7 +89,7 @@ class CreateVolume(tables.LinkAction): class ExtendVolume(tables.LinkAction): name = "extend" verbose_name = _("Extend Volume") - url = "horizon:project:volumes:extend" + url = "horizon:project:volumes:volumes:extend" classes = ("ajax-modal", "btn-extend") policy_rules = (("volume", "volume:extend"),) @@ -106,7 +106,7 @@ class ExtendVolume(tables.LinkAction): class EditAttachments(tables.LinkAction): name = "attachments" verbose_name = _("Edit Attachments") - url = "horizon:project:volumes:attach" + url = "horizon:project:volumes:volumes:attach" classes = ("ajax-modal", "btn-edit") def allowed(self, request, volume=None): @@ -129,7 +129,7 @@ class EditAttachments(tables.LinkAction): class CreateSnapshot(tables.LinkAction): name = "snapshots" verbose_name = _("Create Snapshot") - url = "horizon:project:volumes:create_snapshot" + url = "horizon:project:volumes:volumes:create_snapshot" classes = ("ajax-modal", "btn-camera") policy_rules = (("volume", "volume:create_snapshot"),) @@ -146,7 +146,7 @@ class CreateSnapshot(tables.LinkAction): class EditVolume(tables.LinkAction): name = "edit" verbose_name = _("Edit Volume") - url = "horizon:project:volumes:update" + url = "horizon:project:volumes:volumes:update" classes = ("ajax-modal", "btn-edit") policy_rules = (("volume", "volume:update"),) @@ -228,7 +228,7 @@ class VolumesTableBase(tables.DataTable): ) name = tables.Column("display_name", verbose_name=_("Name"), - link="horizon:project:volumes:detail") + link="horizon:project:volumes:volumes:detail") description = tables.Column("display_description", verbose_name=_("Description"), truncate=40) @@ -257,7 +257,7 @@ class VolumesFilterAction(tables.FilterAction): class VolumesTable(VolumesTableBase): name = tables.Column("display_name", verbose_name=_("Name"), - link="horizon:project:volumes:detail") + link="horizon:project:volumes:volumes:detail") volume_type = tables.Column(get_volume_type, verbose_name=_("Type"), empty_value="-") diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/tabs.py b/openstack_dashboard/dashboards/project/volumes/volumes/tabs.py new file mode 100644 index 0000000000..a758343c21 --- /dev/null +++ b/openstack_dashboard/dashboards/project/volumes/volumes/tabs.py @@ -0,0 +1,33 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Nebula, 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 _ + +from horizon import tabs + + +class OverviewTab(tabs.Tab): + name = _("Overview") + slug = "overview" + template_name = ("project/volumes/volumes/_detail_overview.html") + + def get_context_data(self, request): + return {"volume": self.tab_group.kwargs['volume']} + + +class VolumeDetailTabs(tabs.TabGroup): + slug = "volume_details" + tabs = (OverviewTab,) diff --git a/openstack_dashboard/dashboards/project/volumes/tests.py b/openstack_dashboard/dashboards/project/volumes/volumes/tests.py similarity index 96% rename from openstack_dashboard/dashboards/project/volumes/tests.py rename to openstack_dashboard/dashboards/project/volumes/volumes/tests.py index b7959dedb7..d693ba3e81 100644 --- a/openstack_dashboard/dashboards/project/volumes/tests.py +++ b/openstack_dashboard/dashboards/project/volumes/volumes/tests.py @@ -27,7 +27,8 @@ from mox import IsA # noqa from openstack_dashboard import api from openstack_dashboard.api import cinder -from openstack_dashboard.dashboards.project.volumes import tables +from openstack_dashboard.dashboards.project.volumes \ + .volumes import tables from openstack_dashboard.test import helpers as test from openstack_dashboard.usage import quotas @@ -96,7 +97,7 @@ class VolumeViewTests(test.TestCase): self.mox.ReplayAll() - url = reverse('horizon:project:volumes:create') + url = reverse('horizon:project:volumes:volumes:create') res = self.client.post(url, formData) redirect_url = reverse('horizon:project:volumes:index') @@ -160,7 +161,7 @@ class VolumeViewTests(test.TestCase): self.mox.ReplayAll() - url = reverse('horizon:project:volumes:create') + url = reverse('horizon:project:volumes:volumes:create') res = self.client.post(url, formData) redirect_url = reverse('horizon:project:volumes:index') @@ -207,7 +208,7 @@ class VolumeViewTests(test.TestCase): self.mox.ReplayAll() # get snapshot from url - url = reverse('horizon:project:volumes:create') + url = reverse('horizon:project:volumes:volumes:create') res = self.client.post("?".join([url, "snapshot_id=" + str(snapshot.id)]), formData) @@ -276,7 +277,7 @@ class VolumeViewTests(test.TestCase): source_volid=volume.id).AndReturn(volume) self.mox.ReplayAll() - url = reverse('horizon:project:volumes:create') + url = reverse('horizon:project:volumes:volumes:create') redirect_url = reverse('horizon:project:volumes:index') res = self.client.post(url, formData) self.assertNoFormErrors(res) @@ -346,7 +347,7 @@ class VolumeViewTests(test.TestCase): self.mox.ReplayAll() # get snapshot from dropdown list - url = reverse('horizon:project:volumes:create') + url = reverse('horizon:project:volumes:volumes:create') res = self.client.post(url, formData) redirect_url = reverse('horizon:project:volumes:index') @@ -382,7 +383,7 @@ class VolumeViewTests(test.TestCase): self.mox.ReplayAll() - url = reverse('horizon:project:volumes:create') + url = reverse('horizon:project:volumes:volumes:create') res = self.client.post("?".join([url, "snapshot_id=" + str(snapshot.id)]), formData, follow=True) @@ -437,7 +438,7 @@ class VolumeViewTests(test.TestCase): self.mox.ReplayAll() # get image from url - url = reverse('horizon:project:volumes:create') + url = reverse('horizon:project:volumes:volumes:create') res = self.client.post("?".join([url, "image_id=" + str(image.id)]), formData) @@ -508,7 +509,7 @@ class VolumeViewTests(test.TestCase): self.mox.ReplayAll() # get image from dropdown list - url = reverse('horizon:project:volumes:create') + url = reverse('horizon:project:volumes:volumes:create') res = self.client.post(url, formData) redirect_url = reverse('horizon:project:volumes:index') @@ -546,7 +547,7 @@ class VolumeViewTests(test.TestCase): self.mox.ReplayAll() - url = reverse('horizon:project:volumes:create') + url = reverse('horizon:project:volumes:volumes:create') res = self.client.post("?".join([url, "image_id=" + str(image.id)]), formData, follow=True) @@ -588,7 +589,7 @@ class VolumeViewTests(test.TestCase): self.mox.ReplayAll() - url = reverse('horizon:project:volumes:create') + url = reverse('horizon:project:volumes:volumes:create') res = self.client.post("?".join([url, "image_id=" + str(image.id)]), formData, follow=True) @@ -639,7 +640,7 @@ class VolumeViewTests(test.TestCase): self.mox.ReplayAll() - url = reverse('horizon:project:volumes:create') + url = reverse('horizon:project:volumes:volumes:create') res = self.client.post(url, formData) expected_error = [u'A volume of 5000GB cannot be created as you only' @@ -688,7 +689,7 @@ class VolumeViewTests(test.TestCase): self.mox.ReplayAll() - url = reverse('horizon:project:volumes:create') + url = reverse('horizon:project:volumes:volumes:create') res = self.client.post(url, formData) expected_error = [u'You are already using all of your available' @@ -750,7 +751,6 @@ class VolumeViewTests(test.TestCase): url = reverse('horizon:project:volumes:index') res = self.client.post(url, formData, follow=True) - self.assertMessageCount(res, error=1) self.assertEqual(list(res.context['messages'])[0].message, u'Unable to delete volume "%s". ' u'One or more snapshots depend on it.' % @@ -769,7 +769,8 @@ class VolumeViewTests(test.TestCase): api.nova.server_list(IsA(http.HttpRequest)).AndReturn([servers, False]) self.mox.ReplayAll() - url = reverse('horizon:project:volumes:attach', args=[volume.id]) + url = reverse('horizon:project:volumes:volumes:attach', + args=[volume.id]) res = self.client.get(url) # Asserting length of 2 accounts for the one instance option, # and the one 'Choose Instance' option. @@ -792,7 +793,8 @@ class VolumeViewTests(test.TestCase): api.nova.server_list(IsA(http.HttpRequest)).AndReturn([servers, False]) self.mox.ReplayAll() - url = reverse('horizon:project:volumes:attach', args=[volume.id]) + url = reverse('horizon:project:volumes:volumes:attach', + args=[volume.id]) res = self.client.get(url) # Assert the device field is hidden. form = res.context['form'] @@ -815,7 +817,7 @@ class VolumeViewTests(test.TestCase): self.mox.ReplayAll() - url = reverse('horizon:project:volumes:attach', + url = reverse('horizon:project:volumes:volumes:attach', args=[volume.id]) res = self.client.get(url) @@ -874,7 +876,7 @@ class VolumeViewTests(test.TestCase): self.mox.ReplayAll() - url = reverse('horizon:project:volumes:detail', + url = reverse('horizon:project:volumes:volumes:detail', args=[volume.id]) res = self.client.get(url) @@ -925,7 +927,7 @@ class VolumeViewTests(test.TestCase): self.mox.ReplayAll() - url = reverse('horizon:project:volumes:detail', + url = reverse('horizon:project:volumes:volumes:detail', args=[volume.id]) res = self.client.get(url) @@ -948,7 +950,7 @@ class VolumeViewTests(test.TestCase): 'name': volume.display_name, 'description': volume.display_description} - url = reverse('horizon:project:volumes:update', + url = reverse('horizon:project:volumes:volumes:update', args=[volume.id]) res = self.client.post(url, formData) self.assertRedirectsNoFollow(res, VOLUME_INDEX_URL) @@ -970,7 +972,7 @@ class VolumeViewTests(test.TestCase): self.mox.ReplayAll() - url = reverse('horizon:project:volumes:extend', + url = reverse('horizon:project:volumes:volumes:extend', args=[volume.id]) res = self.client.post(url, formData) @@ -996,7 +998,7 @@ class VolumeViewTests(test.TestCase): self.mox.ReplayAll() - url = reverse('horizon:project:volumes:extend', + url = reverse('horizon:project:volumes:volumes:extend', args=[volume.id]) res = self.client.post(url, formData) self.assertFormError(res, 'form', None, diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/urls.py b/openstack_dashboard/dashboards/project/volumes/volumes/urls.py new file mode 100644 index 0000000000..a5517c532c --- /dev/null +++ b/openstack_dashboard/dashboards/project/volumes/volumes/urls.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Nebula, 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.conf.urls import patterns # noqa +from django.conf.urls import url # noqa + +from openstack_dashboard.dashboards.project.volumes \ + .volumes import views + + +VIEWS_MOD = ('openstack_dashboard.dashboards.project.volumes.volumes.views') + +urlpatterns = patterns(VIEWS_MOD, + url(r'^create/$', views.CreateView.as_view(), name='create'), + url(r'^(?P[^/]+)/extend/$', + views.ExtendView.as_view(), + name='extend'), + url(r'^(?P[^/]+)/attach/$', + views.EditAttachmentsView.as_view(), + name='attach'), + url(r'^(?P[^/]+)/create_snapshot/$', + views.CreateSnapshotView.as_view(), + name='create_snapshot'), + url(r'^(?P[^/]+)/$', + views.DetailView.as_view(), + name='detail'), + url(r'^(?P[^/]+)/update/$', + views.UpdateView.as_view(), + name='update'), +) diff --git a/openstack_dashboard/dashboards/project/volumes/volumes/views.py b/openstack_dashboard/dashboards/project/volumes/volumes/views.py new file mode 100644 index 0000000000..7ab620a290 --- /dev/null +++ b/openstack_dashboard/dashboards/project/volumes/volumes/views.py @@ -0,0 +1,244 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Nebula, 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. + +""" +Views for managing volumes. +""" + +from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse_lazy +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.api import cinder +from openstack_dashboard.usage import quotas + +from openstack_dashboard.dashboards.project.volumes \ + .volumes import forms as project_forms + +from openstack_dashboard.dashboards.project.volumes \ + .volumes import tables as project_tables +from openstack_dashboard.dashboards.project.volumes \ + .volumes import tabs as project_tabs + + +class DetailView(tabs.TabView): + tab_group_class = project_tabs.VolumeDetailTabs + template_name = 'project/volumes/volumes/detail.html' + + def get_context_data(self, **kwargs): + context = super(DetailView, self).get_context_data(**kwargs) + context["volume"] = self.get_data() + return context + + @memoized.memoized_method + def get_data(self): + try: + volume_id = self.kwargs['volume_id'] + volume = cinder.volume_get(self.request, volume_id) + for att in volume.attachments: + att['instance'] = api.nova.server_get(self.request, + att['server_id']) + except Exception: + redirect = reverse('horizon:project:volumes:index') + exceptions.handle(self.request, + _('Unable to retrieve volume details.'), + redirect=redirect) + return volume + + def get_tabs(self, request, *args, **kwargs): + volume = self.get_data() + return self.tab_group_class(request, volume=volume, **kwargs) + + +class CreateView(forms.ModalFormView): + form_class = project_forms.CreateForm + template_name = 'project/volumes/volumes/create.html' + success_url = reverse_lazy("horizon:project:volumes:index") + + def get_context_data(self, **kwargs): + context = super(CreateView, self).get_context_data(**kwargs) + try: + context['usages'] = quotas.tenant_limit_usages(self.request) + except Exception: + exceptions.handle(self.request) + return context + + +class ExtendView(forms.ModalFormView): + form_class = project_forms.ExtendForm + template_name = 'project/volumes/volumes/extend.html' + success_url = reverse_lazy("horizon:project:volumes:index") + + def get_object(self): + if not hasattr(self, "_object"): + volume_id = self.kwargs['volume_id'] + try: + self._object = cinder.volume_get(self.request, volume_id) + except Exception: + self._object = None + exceptions.handle(self.request, + _('Unable to retrieve volume information.')) + return self._object + + def get_context_data(self, **kwargs): + context = super(ExtendView, self).get_context_data(**kwargs) + context['volume'] = self.get_object() + try: + usages = quotas.tenant_limit_usages(self.request) + usages['gigabytesUsed'] = (usages['gigabytesUsed'] + - context['volume'].size) + context['usages'] = usages + except Exception: + exceptions.handle(self.request) + return context + + def get_initial(self): + volume = self.get_object() + return {'id': self.kwargs['volume_id'], + 'name': volume.display_name, + 'orig_size': volume.size} + + +class CreateSnapshotView(forms.ModalFormView): + form_class = project_forms.CreateSnapshotForm + template_name = 'project/volumes/volumes/create_snapshot.html' + success_url = reverse_lazy("horizon:project:volumes:index") + + def get_context_data(self, **kwargs): + context = super(CreateSnapshotView, self).get_context_data(**kwargs) + context['volume_id'] = self.kwargs['volume_id'] + try: + volume = cinder.volume_get(self.request, context['volume_id']) + if (volume.status == 'in-use'): + context['attached'] = True + context['form'].set_warning(_("This volume is currently " + "attached to an instance. " + "In some cases, creating a " + "snapshot from an attached " + "volume can result in a " + "corrupted snapshot.")) + context['usages'] = quotas.tenant_limit_usages(self.request) + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve volume information.')) + return context + + def get_initial(self): + return {'volume_id': self.kwargs["volume_id"]} + + +class UpdateView(forms.ModalFormView): + form_class = project_forms.UpdateForm + template_name = 'project/volumes/volumes/update.html' + success_url = reverse_lazy("horizon:project:volumes:index") + + def get_object(self): + if not hasattr(self, "_object"): + vol_id = self.kwargs['volume_id'] + try: + self._object = cinder.volume_get(self.request, vol_id) + except Exception: + msg = _('Unable to retrieve volume.') + url = reverse('horizon:project:volumes:index') + exceptions.handle(self.request, msg, redirect=url) + return self._object + + def get_context_data(self, **kwargs): + context = super(UpdateView, self).get_context_data(**kwargs) + context['volume'] = self.get_object() + return context + + def get_initial(self): + volume = self.get_object() + return {'volume_id': self.kwargs["volume_id"], + 'name': volume.display_name, + 'description': volume.display_description} + + +class EditAttachmentsView(tables.DataTableView, forms.ModalFormView): + table_class = project_tables.AttachmentsTable + form_class = project_forms.AttachForm + template_name = 'project/volumes/volumes/attach.html' + success_url = reverse_lazy("horizon:project:volumes:index") + + @memoized.memoized_method + def get_object(self): + volume_id = self.kwargs['volume_id'] + try: + return cinder.volume_get(self.request, volume_id) + except Exception: + self._object = None + exceptions.handle(self.request, + _('Unable to retrieve volume information.')) + + def get_data(self): + try: + volumes = self.get_object() + attachments = [att for att in volumes.attachments if att] + except Exception: + attachments = [] + exceptions.handle(self.request, + _('Unable to retrieve volume information.')) + return attachments + + def get_initial(self): + try: + instances, has_more = api.nova.server_list(self.request) + except Exception: + instances = [] + exceptions.handle(self.request, + _("Unable to retrieve attachment information.")) + return {'volume': self.get_object(), + 'instances': instances} + + @memoized.memoized_method + def get_form(self): + form_class = self.get_form_class() + return super(EditAttachmentsView, self).get_form(form_class) + + def get_context_data(self, **kwargs): + context = super(EditAttachmentsView, self).get_context_data(**kwargs) + context['form'] = self.get_form() + volume = self.get_object() + if volume and volume.status == 'available': + context['show_attach'] = True + else: + context['show_attach'] = False + context['volume'] = volume + if self.request.is_ajax(): + context['hide'] = True + return context + + def get(self, request, *args, **kwargs): + # Table action handling + handled = self.construct_tables() + if handled: + return handled + return self.render_to_response(self.get_context_data(**kwargs)) + + def post(self, request, *args, **kwargs): + form = self.get_form() + if form.is_valid(): + return self.form_valid(form) + else: + return self.get(request, *args, **kwargs) diff --git a/openstack_dashboard/dashboards/router/nexus1000v/tables.py b/openstack_dashboard/dashboards/router/nexus1000v/tables.py index c4cf6406ca..d1fd8faf85 100644 --- a/openstack_dashboard/dashboards/router/nexus1000v/tables.py +++ b/openstack_dashboard/dashboards/router/nexus1000v/tables.py @@ -79,7 +79,7 @@ class NetworkProfile(tables.DataTable): class EditPolicyProfile(tables.LinkAction): name = "edit" verbose_name = _("Edit Policy Profile") - url = "horizon:project:images_and_snapshots:images:update" + url = "horizon:project:images:images:update" classes = ("ajax-modal", "btn-edit")