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 1be254c07d..e237aff3c5 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 bb5a6532b9..cbc37d91d6 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 2eedb7b9ee..45bc3c5602 100644
--- a/openstack_dashboard/dashboards/project/instances/tests.py
+++ b/openstack_dashboard/dashboards/project/instances/tests.py
@@ -870,8 +870,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()
@@ -883,17 +881,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 18f16f2df6..314f48686c 100644
--- a/openstack_dashboard/dashboards/router/nexus1000v/tables.py
+++ b/openstack_dashboard/dashboards/router/nexus1000v/tables.py
@@ -81,7 +81,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")