Merge "Move volume snapshots table to volumes panel"
This commit is contained in:
commit
c03e9ff05e
@ -18,8 +18,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.images_and_snapshots \
|
from openstack_dashboard.dashboards.project.images.images import forms
|
||||||
.images import forms
|
|
||||||
|
|
||||||
|
|
||||||
class AdminCreateImageForm(forms.CreateImageForm):
|
class AdminCreateImageForm(forms.CreateImageForm):
|
||||||
|
@ -19,7 +19,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from horizon import tables
|
from horizon import tables
|
||||||
|
|
||||||
from openstack_dashboard import api
|
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
|
import tables as project_tables
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,8 +25,7 @@ from horizon import exceptions
|
|||||||
from horizon import tables
|
from horizon import tables
|
||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
from openstack_dashboard.dashboards.project \
|
from openstack_dashboard.dashboards.project.images.images import views
|
||||||
.images_and_snapshots.images import views
|
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.admin.images import forms
|
from openstack_dashboard.dashboards.admin.images import forms
|
||||||
from openstack_dashboard.dashboards.admin.images \
|
from openstack_dashboard.dashboards.admin.images \
|
||||||
|
@ -15,7 +15,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from horizon import tables
|
from horizon import tables
|
||||||
from openstack_dashboard.api import cinder
|
from openstack_dashboard.api import cinder
|
||||||
from openstack_dashboard.dashboards.project.volumes \
|
from openstack_dashboard.dashboards.project.volumes \
|
||||||
import tables as project_tables
|
.volumes import tables as project_tables
|
||||||
|
|
||||||
|
|
||||||
class CreateVolumeType(tables.LinkAction):
|
class CreateVolumeType(tables.LinkAction):
|
||||||
|
@ -34,10 +34,13 @@ from openstack_dashboard.dashboards.admin.volumes \
|
|||||||
from openstack_dashboard.dashboards.admin.volumes \
|
from openstack_dashboard.dashboards.admin.volumes \
|
||||||
import tables as project_tables
|
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,
|
table_classes = (project_tables.VolumesTable,
|
||||||
project_tables.VolumeTypesTable)
|
project_tables.VolumeTypesTable)
|
||||||
template_name = "admin/volumes/index.html"
|
template_name = "admin/volumes/index.html"
|
||||||
@ -74,7 +77,7 @@ class IndexView(tables.MultiTableView, views.VolumeTableMixIn):
|
|||||||
return volume_types
|
return volume_types
|
||||||
|
|
||||||
|
|
||||||
class DetailView(views.DetailView):
|
class DetailView(volume_views.DetailView):
|
||||||
template_name = "admin/volumes/detail.html"
|
template_name = "admin/volumes/detail.html"
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ class BasePanels(horizon.PanelGroup):
|
|||||||
panels = ('overview',
|
panels = ('overview',
|
||||||
'instances',
|
'instances',
|
||||||
'volumes',
|
'volumes',
|
||||||
'images_and_snapshots',
|
'images',
|
||||||
'access_and_security',)
|
'access_and_security',)
|
||||||
|
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ class DeleteImage(tables.DeleteAction):
|
|||||||
class CreateImage(tables.LinkAction):
|
class CreateImage(tables.LinkAction):
|
||||||
name = "create"
|
name = "create"
|
||||||
verbose_name = _("Create Image")
|
verbose_name = _("Create Image")
|
||||||
url = "horizon:project:images_and_snapshots:images:create"
|
url = "horizon:project:images:images:create"
|
||||||
classes = ("ajax-modal", "btn-create")
|
classes = ("ajax-modal", "btn-create")
|
||||||
policy_rules = (("image", "add_image"),)
|
policy_rules = (("image", "add_image"),)
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ class CreateImage(tables.LinkAction):
|
|||||||
class EditImage(tables.LinkAction):
|
class EditImage(tables.LinkAction):
|
||||||
name = "edit"
|
name = "edit"
|
||||||
verbose_name = _("Edit")
|
verbose_name = _("Edit")
|
||||||
url = "horizon:project:images_and_snapshots:images:update"
|
url = "horizon:project:images:images:update"
|
||||||
classes = ("ajax-modal", "btn-edit")
|
classes = ("ajax-modal", "btn-edit")
|
||||||
policy_rules = (("image", "modify_image"),)
|
policy_rules = (("image", "modify_image"),)
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ class EditImage(tables.LinkAction):
|
|||||||
class CreateVolumeFromImage(tables.LinkAction):
|
class CreateVolumeFromImage(tables.LinkAction):
|
||||||
name = "create_volume_from_image"
|
name = "create_volume_from_image"
|
||||||
verbose_name = _("Create Volume")
|
verbose_name = _("Create Volume")
|
||||||
url = "horizon:project:volumes:create"
|
url = "horizon:project:volumes:volumes:create"
|
||||||
classes = ("ajax-modal", "btn-camera")
|
classes = ("ajax-modal", "btn-camera")
|
||||||
policy_rules = (("volume", "volume:create"),)
|
policy_rules = (("volume", "volume:create"),)
|
||||||
|
|
||||||
@ -206,8 +206,7 @@ class ImagesTable(tables.DataTable):
|
|||||||
("deleted", False),
|
("deleted", False),
|
||||||
)
|
)
|
||||||
name = tables.Column(get_image_name,
|
name = tables.Column(get_image_name,
|
||||||
link=("horizon:project:images_and_snapshots:"
|
link=("horizon:project:images:images:detail"),
|
||||||
"images:detail"),
|
|
||||||
verbose_name=_("Image Name"))
|
verbose_name=_("Image Name"))
|
||||||
image_type = tables.Column(get_image_type,
|
image_type = tables.Column(get_image_type,
|
||||||
verbose_name=_("Type"),
|
verbose_name=_("Type"),
|
@ -23,7 +23,7 @@ from horizon import tabs
|
|||||||
class OverviewTab(tabs.Tab):
|
class OverviewTab(tabs.Tab):
|
||||||
name = _("Overview")
|
name = _("Overview")
|
||||||
slug = "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):
|
def get_context_data(self, request):
|
||||||
image = self.tab_group.kwargs['image']
|
image = self.tab_group.kwargs['image']
|
@ -33,13 +33,11 @@ from horizon import tables as horizon_tables
|
|||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
from openstack_dashboard.test import helpers as test
|
from openstack_dashboard.test import helpers as test
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.images_and_snapshots.images \
|
from openstack_dashboard.dashboards.project.images.images import forms
|
||||||
import forms
|
from openstack_dashboard.dashboards.project.images.images import tables
|
||||||
from openstack_dashboard.dashboards.project.images_and_snapshots.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):
|
class CreateImageFormTests(test.TestCase):
|
||||||
@ -74,10 +72,10 @@ class CreateImageFormTests(test.TestCase):
|
|||||||
|
|
||||||
class ImageViewTests(test.TestCase):
|
class ImageViewTests(test.TestCase):
|
||||||
def test_image_create_get(self):
|
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)
|
res = self.client.get(url)
|
||||||
self.assertTemplateUsed(res,
|
self.assertTemplateUsed(res,
|
||||||
'project/images_and_snapshots/images/create.html')
|
'project/images/images/create.html')
|
||||||
|
|
||||||
@test.create_stubs({api.glance: ('image_create',)})
|
@test.create_stubs({api.glance: ('image_create',)})
|
||||||
def test_image_create_post_copy_from(self):
|
def test_image_create_post_copy_from(self):
|
||||||
@ -111,7 +109,7 @@ class ImageViewTests(test.TestCase):
|
|||||||
AndReturn(self.images.first())
|
AndReturn(self.images.first())
|
||||||
self.mox.ReplayAll()
|
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)
|
res = self.client.post(url, data)
|
||||||
|
|
||||||
self.assertNoFormErrors(res)
|
self.assertNoFormErrors(res)
|
||||||
@ -151,7 +149,7 @@ class ImageViewTests(test.TestCase):
|
|||||||
AndReturn(self.images.first())
|
AndReturn(self.images.first())
|
||||||
self.mox.ReplayAll()
|
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)
|
res = self.client.post(url, data)
|
||||||
|
|
||||||
self.assertNoFormErrors(res)
|
self.assertNoFormErrors(res)
|
||||||
@ -165,12 +163,11 @@ class ImageViewTests(test.TestCase):
|
|||||||
.AndReturn(self.images.first())
|
.AndReturn(self.images.first())
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
res = self.client.get(
|
res = self.client.get(reverse('horizon:project:images:images:detail',
|
||||||
reverse('horizon:project:images_and_snapshots:images:detail',
|
args=[image.id]))
|
||||||
args=[image.id]))
|
|
||||||
|
|
||||||
self.assertTemplateUsed(res,
|
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'].name, image.name)
|
||||||
self.assertEqual(res.context['image'].protected, image.protected)
|
self.assertEqual(res.context['image'].protected, image.protected)
|
||||||
self.assertContains(res, "<h2>Image Details: %s</h2>" % image.name,
|
self.assertContains(res, "<h2>Image Details: %s</h2>" % image.name,
|
||||||
@ -184,9 +181,8 @@ class ImageViewTests(test.TestCase):
|
|||||||
.AndReturn(image)
|
.AndReturn(image)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
res = self.client.get(
|
res = self.client.get(reverse('horizon:project:images:images:detail',
|
||||||
reverse('horizon:project:images_and_snapshots:images:detail',
|
args=[image.id]))
|
||||||
args=[image.id]))
|
|
||||||
|
|
||||||
image_props = res.context['image_props']
|
image_props = res.context['image_props']
|
||||||
|
|
||||||
@ -213,10 +209,10 @@ class ImageViewTests(test.TestCase):
|
|||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
res = self.client.get(
|
res = self.client.get(
|
||||||
reverse('horizon:project:images_and_snapshots:images:detail',
|
reverse('horizon:project:images:images:detail',
|
||||||
args=[image.id]))
|
args=[image.id]))
|
||||||
self.assertTemplateUsed(res,
|
self.assertTemplateUsed(res,
|
||||||
'project/images_and_snapshots/images/detail.html')
|
'project/images/images/detail.html')
|
||||||
self.assertEqual(res.context['image'].protected, image.protected)
|
self.assertEqual(res.context['image'].protected, image.protected)
|
||||||
|
|
||||||
@test.create_stubs({api.glance: ('image_get',)})
|
@test.create_stubs({api.glance: ('image_get',)})
|
||||||
@ -227,7 +223,7 @@ class ImageViewTests(test.TestCase):
|
|||||||
.AndRaise(self.exceptions.glance)
|
.AndRaise(self.exceptions.glance)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:images_and_snapshots:images:detail',
|
url = reverse('horizon:project:images:images:detail',
|
||||||
args=[image.id])
|
args=[image.id])
|
||||||
res = self.client.get(url)
|
res = self.client.get(url)
|
||||||
self.assertRedirectsNoFollow(res, IMAGES_INDEX_URL)
|
self.assertRedirectsNoFollow(res, IMAGES_INDEX_URL)
|
||||||
@ -242,11 +238,11 @@ class ImageViewTests(test.TestCase):
|
|||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
res = self.client.get(
|
res = self.client.get(
|
||||||
reverse('horizon:project:images_and_snapshots:images:update',
|
reverse('horizon:project:images:images:update',
|
||||||
args=[image.id]))
|
args=[image.id]))
|
||||||
|
|
||||||
self.assertTemplateUsed(res,
|
self.assertTemplateUsed(res,
|
||||||
'project/images_and_snapshots/images/_update.html')
|
'project/images/images/_update.html')
|
||||||
self.assertEqual(res.context['image'].name, image.name)
|
self.assertEqual(res.context['image'].name, image.name)
|
||||||
# Bug 1076216 - is_public checkbox not being set correctly
|
# Bug 1076216 - is_public checkbox not being set correctly
|
||||||
self.assertContains(res, "<input type='checkbox' id='id_public'"
|
self.assertContains(res, "<input type='checkbox' id='id_public'"
|
@ -21,12 +21,10 @@
|
|||||||
from django.conf.urls import patterns # noqa
|
from django.conf.urls import patterns # noqa
|
||||||
from django.conf.urls import url # 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 views
|
||||||
import views
|
|
||||||
|
|
||||||
|
|
||||||
VIEWS_MOD = ('openstack_dashboard.dashboards.project'
|
VIEWS_MOD = 'openstack_dashboard.dashboards.project.images.images.views'
|
||||||
'.images_and_snapshots.images.views')
|
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns(VIEWS_MOD,
|
urlpatterns = patterns(VIEWS_MOD,
|
@ -32,23 +32,23 @@ from horizon.utils import memoized
|
|||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.images_and_snapshots.images \
|
from openstack_dashboard.dashboards.project.images.images \
|
||||||
import forms as project_forms
|
import forms as project_forms
|
||||||
from openstack_dashboard.dashboards.project.images_and_snapshots.images \
|
from openstack_dashboard.dashboards.project.images.images \
|
||||||
import tabs as project_tabs
|
import tabs as project_tabs
|
||||||
|
|
||||||
|
|
||||||
class CreateView(forms.ModalFormView):
|
class CreateView(forms.ModalFormView):
|
||||||
form_class = project_forms.CreateImageForm
|
form_class = project_forms.CreateImageForm
|
||||||
template_name = 'project/images_and_snapshots/images/create.html'
|
template_name = 'project/images/images/create.html'
|
||||||
context_object_name = 'image'
|
context_object_name = 'image'
|
||||||
success_url = reverse_lazy("horizon:project:images_and_snapshots:index")
|
success_url = reverse_lazy("horizon:project:images:index")
|
||||||
|
|
||||||
|
|
||||||
class UpdateView(forms.ModalFormView):
|
class UpdateView(forms.ModalFormView):
|
||||||
form_class = project_forms.UpdateImageForm
|
form_class = project_forms.UpdateImageForm
|
||||||
template_name = 'project/images_and_snapshots/images/update.html'
|
template_name = 'project/images/images/update.html'
|
||||||
success_url = reverse_lazy("horizon:project:images_and_snapshots:index")
|
success_url = reverse_lazy("horizon:project:images:index")
|
||||||
|
|
||||||
@memoized.memoized_method
|
@memoized.memoized_method
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
@ -56,7 +56,7 @@ class UpdateView(forms.ModalFormView):
|
|||||||
return api.glance.image_get(self.request, self.kwargs['image_id'])
|
return api.glance.image_get(self.request, self.kwargs['image_id'])
|
||||||
except Exception:
|
except Exception:
|
||||||
msg = _('Unable to retrieve image.')
|
msg = _('Unable to retrieve image.')
|
||||||
url = reverse('horizon:project:images_and_snapshots:index')
|
url = reverse('horizon:project:images:index')
|
||||||
exceptions.handle(self.request, msg, redirect=url)
|
exceptions.handle(self.request, msg, redirect=url)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
@ -80,7 +80,7 @@ class UpdateView(forms.ModalFormView):
|
|||||||
|
|
||||||
class DetailView(tabs.TabView):
|
class DetailView(tabs.TabView):
|
||||||
tab_group_class = project_tabs.ImageDetailTabs
|
tab_group_class = project_tabs.ImageDetailTabs
|
||||||
template_name = 'project/images_and_snapshots/images/detail.html'
|
template_name = 'project/images/images/detail.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(DetailView, self).get_context_data(**kwargs)
|
context = super(DetailView, self).get_context_data(**kwargs)
|
||||||
@ -92,7 +92,7 @@ class DetailView(tabs.TabView):
|
|||||||
try:
|
try:
|
||||||
return api.glance.image_get(self.request, self.kwargs['image_id'])
|
return api.glance.image_get(self.request, self.kwargs['image_id'])
|
||||||
except Exception:
|
except Exception:
|
||||||
url = reverse('horizon:project:images_and_snapshots:index')
|
url = reverse('horizon:project:images:index')
|
||||||
exceptions.handle(self.request,
|
exceptions.handle(self.request,
|
||||||
_('Unable to retrieve image details.'),
|
_('Unable to retrieve image details.'),
|
||||||
redirect=url)
|
redirect=url)
|
@ -22,9 +22,9 @@ import horizon
|
|||||||
from openstack_dashboard.dashboards.project import dashboard
|
from openstack_dashboard.dashboards.project import dashboard
|
||||||
|
|
||||||
|
|
||||||
class ImagesAndSnapshots(horizon.Panel):
|
class Images(horizon.Panel):
|
||||||
name = _("Images & Snapshots")
|
name = _("Images")
|
||||||
slug = 'images_and_snapshots'
|
slug = 'images'
|
||||||
|
|
||||||
|
|
||||||
dashboard.Project.register(ImagesAndSnapshots)
|
dashboard.Project.register(Images)
|
@ -27,7 +27,7 @@ from openstack_dashboard import api
|
|||||||
from openstack_dashboard.test import helpers as test
|
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 SnapshotsViewTests(test.TestCase):
|
class SnapshotsViewTests(test.TestCase):
|
||||||
@ -37,11 +37,11 @@ class SnapshotsViewTests(test.TestCase):
|
|||||||
api.nova.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
|
api.nova.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:images_and_snapshots:snapshots:create',
|
url = reverse('horizon:project:images:snapshots:create',
|
||||||
args=[server.id])
|
args=[server.id])
|
||||||
res = self.client.get(url)
|
res = self.client.get(url)
|
||||||
self.assertTemplateUsed(res,
|
self.assertTemplateUsed(res,
|
||||||
'project/images_and_snapshots/snapshots/create.html')
|
'project/images/snapshots/create.html')
|
||||||
|
|
||||||
def test_create_get_server_exception(self):
|
def test_create_get_server_exception(self):
|
||||||
server = self.servers.first()
|
server = self.servers.first()
|
||||||
@ -50,7 +50,7 @@ class SnapshotsViewTests(test.TestCase):
|
|||||||
.AndRaise(self.exceptions.nova)
|
.AndRaise(self.exceptions.nova)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:images_and_snapshots:snapshots:create',
|
url = reverse('horizon:project:images:snapshots:create',
|
||||||
args=[server.id])
|
args=[server.id])
|
||||||
res = self.client.get(url)
|
res = self.client.get(url)
|
||||||
redirect = reverse("horizon:project:instances:index")
|
redirect = reverse("horizon:project:instances:index")
|
||||||
@ -71,7 +71,7 @@ class SnapshotsViewTests(test.TestCase):
|
|||||||
'tenant_id': self.tenant.id,
|
'tenant_id': self.tenant.id,
|
||||||
'instance_id': server.id,
|
'instance_id': server.id,
|
||||||
'name': snapshot.name}
|
'name': snapshot.name}
|
||||||
url = reverse('horizon:project:images_and_snapshots:snapshots:create',
|
url = reverse('horizon:project:images:snapshots:create',
|
||||||
args=[server.id])
|
args=[server.id])
|
||||||
res = self.client.post(url, formData)
|
res = self.client.post(url, formData)
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ class SnapshotsViewTests(test.TestCase):
|
|||||||
'tenant_id': self.tenant.id,
|
'tenant_id': self.tenant.id,
|
||||||
'instance_id': server.id,
|
'instance_id': server.id,
|
||||||
'name': snapshot.name}
|
'name': snapshot.name}
|
||||||
url = reverse('horizon:project:images_and_snapshots:snapshots:create',
|
url = reverse('horizon:project:images:snapshots:create',
|
||||||
args=[server.id])
|
args=[server.id])
|
||||||
res = self.client.post(url, formData)
|
res = self.client.post(url, formData)
|
||||||
redirect = reverse("horizon:project:instances:index")
|
redirect = reverse("horizon:project:instances:index")
|
@ -21,8 +21,7 @@
|
|||||||
from django.conf.urls import patterns # noqa
|
from django.conf.urls import patterns # noqa
|
||||||
from django.conf.urls import url # noqa
|
from django.conf.urls import url # noqa
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.images_and_snapshots.snapshots \
|
from openstack_dashboard.dashboards.project.images.snapshots import views
|
||||||
import views
|
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
@ -31,14 +31,14 @@ from horizon.utils import memoized
|
|||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.images_and_snapshots.snapshots \
|
from openstack_dashboard.dashboards.project.images.snapshots \
|
||||||
import forms as project_forms
|
import forms as project_forms
|
||||||
|
|
||||||
|
|
||||||
class CreateView(forms.ModalFormView):
|
class CreateView(forms.ModalFormView):
|
||||||
form_class = project_forms.CreateSnapshot
|
form_class = project_forms.CreateSnapshot
|
||||||
template_name = 'project/images_and_snapshots/snapshots/create.html'
|
template_name = 'project/images/snapshots/create.html'
|
||||||
success_url = reverse_lazy("horizon:project:images_and_snapshots:index")
|
success_url = reverse_lazy("horizon:project:images:index")
|
||||||
|
|
||||||
@memoized.memoized_method
|
@memoized.memoized_method
|
||||||
def get_object(self):
|
def get_object(self):
|
@ -3,7 +3,7 @@
|
|||||||
{% load url from future %}
|
{% load url from future %}
|
||||||
|
|
||||||
{% block form_id %}create_image_form{% endblock %}
|
{% block form_id %}create_image_form{% endblock %}
|
||||||
{% block form_action %}{% url 'horizon:project:images_and_snapshots:images:create' %}{% endblock %}
|
{% block form_action %}{% url 'horizon:project:images:images:create' %}{% endblock %}
|
||||||
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
|
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
|
||||||
|
|
||||||
{% block modal-header %}{% trans "Create An Image" %}{% endblock %}
|
{% block modal-header %}{% trans "Create An Image" %}{% endblock %}
|
||||||
@ -31,5 +31,5 @@
|
|||||||
|
|
||||||
{% block modal-footer %}
|
{% block modal-footer %}
|
||||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Image" %}" />
|
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Image" %}" />
|
||||||
<a href="{% url 'horizon:project:images_and_snapshots:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
<a href="{% url 'horizon:project:images:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -3,7 +3,7 @@
|
|||||||
{% load url from future %}
|
{% load url from future %}
|
||||||
|
|
||||||
{% block form_id %}update_image_form{% endblock %}
|
{% 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 %}
|
{% block modal-header %}{% trans "Update Image" %}{% endblock %}
|
||||||
|
|
||||||
@ -21,5 +21,5 @@
|
|||||||
|
|
||||||
{% block modal-footer %}
|
{% block modal-footer %}
|
||||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Update Image" %}" />
|
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Update Image" %}" />
|
||||||
<a href="{% url 'horizon:project:images_and_snapshots:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
<a href="{% url 'horizon:project:images:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -7,5 +7,5 @@
|
|||||||
{% endblock page_header %}
|
{% endblock page_header %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
{% include 'project/images_and_snapshots/images/_create.html' %}
|
{% include 'project/images/images/_create.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -7,5 +7,5 @@
|
|||||||
{% endblock page_header %}
|
{% endblock page_header %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
{% include 'project/images_and_snapshots/images/_update.html' %}
|
{% include 'project/images/images/_update.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -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 %}
|
@ -3,7 +3,7 @@
|
|||||||
{% load url from future %}
|
{% load url from future %}
|
||||||
|
|
||||||
{% block form_id %}create_snapshot_form{% endblock %}
|
{% 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_id %}create_snapshot_modal{% endblock %}
|
||||||
{% block modal-header %}{% trans "Create Snapshot" %}{% endblock %}
|
{% block modal-header %}{% trans "Create Snapshot" %}{% endblock %}
|
||||||
@ -22,5 +22,5 @@
|
|||||||
|
|
||||||
{% block modal-footer %}
|
{% block modal-footer %}
|
||||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Snapshot" %}" />
|
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Snapshot" %}" />
|
||||||
<a href="{% url 'horizon:project:images_and_snapshots:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
<a href="{% url 'horizon:project:images:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -7,5 +7,5 @@
|
|||||||
{% endblock page_header %}
|
{% endblock page_header %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
{% include 'project/images_and_snapshots/snapshots/_create.html' %}
|
{% include 'project/images/snapshots/_create.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -27,32 +27,23 @@ from mox import IsA # noqa
|
|||||||
from horizon import exceptions
|
from horizon import exceptions
|
||||||
|
|
||||||
from openstack_dashboard import api
|
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
|
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):
|
class ImagesAndSnapshotsTests(test.TestCase):
|
||||||
@test.create_stubs({api.glance: ('image_list_detailed',),
|
@test.create_stubs({api.glance: ('image_list_detailed',)})
|
||||||
api.cinder: ('volume_snapshot_list',
|
|
||||||
'volume_list',)})
|
|
||||||
def test_index(self):
|
def test_index(self):
|
||||||
images = self.images.list()
|
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),
|
api.glance.image_list_detailed(IsA(http.HttpRequest),
|
||||||
marker=None).AndReturn([images, False])
|
marker=None).AndReturn([images, False])
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
res = self.client.get(INDEX_URL)
|
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)
|
self.assertIn('images_table', res.context)
|
||||||
images_table = res.context['images_table']
|
images_table = res.context['images_table']
|
||||||
images = images_table.data
|
images = images_table.data
|
||||||
@ -67,61 +58,34 @@ class ImagesAndSnapshotsTests(test.TestCase):
|
|||||||
row_actions = images_table.get_row_actions(images[2])
|
row_actions = images_table.get_row_actions(images[2])
|
||||||
self.assertTrue(len(row_actions), 3)
|
self.assertTrue(len(row_actions), 3)
|
||||||
|
|
||||||
@test.create_stubs({api.glance: ('image_list_detailed',),
|
@test.create_stubs({api.glance: ('image_list_detailed',)})
|
||||||
api.cinder: ('volume_snapshot_list',
|
|
||||||
'volume_list',)})
|
|
||||||
def test_index_no_images(self):
|
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),
|
api.glance.image_list_detailed(IsA(http.HttpRequest),
|
||||||
marker=None).AndReturn([(), False])
|
marker=None).AndReturn([(), False])
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
res = self.client.get(INDEX_URL)
|
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',),
|
@test.create_stubs({api.glance: ('image_list_detailed',)})
|
||||||
api.cinder: ('volume_snapshot_list',
|
|
||||||
'volume_list',)})
|
|
||||||
def test_index_error(self):
|
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),
|
api.glance.image_list_detailed(IsA(http.HttpRequest),
|
||||||
marker=None) \
|
marker=None) \
|
||||||
.AndRaise(self.exceptions.glance)
|
.AndRaise(self.exceptions.glance)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
res = self.client.get(INDEX_URL)
|
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',),
|
@test.create_stubs({api.glance: ('image_list_detailed',)})
|
||||||
api.cinder: ('volume_snapshot_list',
|
|
||||||
'volume_list',)})
|
|
||||||
def test_snapshot_actions(self):
|
def test_snapshot_actions(self):
|
||||||
snapshots = self.snapshots.list()
|
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) \
|
api.glance.image_list_detailed(IsA(http.HttpRequest), marker=None) \
|
||||||
.AndReturn([snapshots, False])
|
.AndReturn([snapshots, False])
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
res = self.client.get(INDEX_URL)
|
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)
|
self.assertIn('images_table', res.context)
|
||||||
snaps = res.context['images_table']
|
snaps = res.context['images_table']
|
||||||
self.assertEqual(len(snaps.get_rows()), 3)
|
self.assertEqual(len(snaps.get_rows()), 3)
|
@ -22,18 +22,15 @@ from django.conf.urls import include # noqa
|
|||||||
from django.conf.urls import patterns # noqa
|
from django.conf.urls import patterns # noqa
|
||||||
from django.conf.urls import url # 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
|
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
|
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('',
|
urlpatterns = patterns('',
|
||||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||||
url(r'', include(image_urls, namespace='images')),
|
url(r'', include(image_urls, namespace='images')),
|
||||||
url(r'', include(snapshot_urls, namespace='snapshots')),
|
url(r'', include(snapshot_urls, namespace='snapshots')),
|
||||||
url(r'^snapshots/(?P<snapshot_id>[^/]+)/$',
|
|
||||||
views.DetailView.as_view(),
|
|
||||||
name='detail'),
|
|
||||||
)
|
)
|
54
openstack_dashboard/dashboards/project/images/views.py
Normal file
54
openstack_dashboard/dashboards/project/images/views.py
Normal file
@ -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
|
@ -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 %}
|
|
||||||
<div class="images">
|
|
||||||
{{ images_table.render }}
|
|
||||||
</div>
|
|
||||||
<div class="volume_snapshots">
|
|
||||||
{{ volume_snapshots_table.render }}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -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)
|
|
@ -27,7 +27,7 @@ from horizon.utils import fields
|
|||||||
from horizon.utils import validators
|
from horizon.utils import validators
|
||||||
|
|
||||||
from openstack_dashboard import api
|
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):
|
def _image_choice_title(img):
|
||||||
|
@ -290,7 +290,7 @@ class EditInstanceSecurityGroups(EditInstance):
|
|||||||
class CreateSnapshot(tables.LinkAction):
|
class CreateSnapshot(tables.LinkAction):
|
||||||
name = "snapshot"
|
name = "snapshot"
|
||||||
verbose_name = _("Create Snapshot")
|
verbose_name = _("Create Snapshot")
|
||||||
url = "horizon:project:images_and_snapshots:snapshots:create"
|
url = "horizon:project:images:snapshots:create"
|
||||||
classes = ("ajax-modal", "btn-camera")
|
classes = ("ajax-modal", "btn-camera")
|
||||||
policy_rules = (("compute", "compute:snapshot"),)
|
policy_rules = (("compute", "compute:snapshot"),)
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@
|
|||||||
{% with default_key_name="<em>"|add:_("None")|add:"</em>" %}
|
{% with default_key_name="<em>"|add:_("None")|add:"</em>" %}
|
||||||
<dd>{{ instance.key_name|default:default_key_name }}</dd>
|
<dd>{{ instance.key_name|default:default_key_name }}</dd>
|
||||||
{% endwith %}
|
{% 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 %}
|
||||||
<dt>{% trans "Image Name" %}</dt>
|
<dt>{% trans "Image Name" %}</dt>
|
||||||
<dd><a href="{{ image_url }}">{{ instance.image_name }}</a></dd>
|
<dd><a href="{{ image_url }}">{{ instance.image_name }}</a></dd>
|
||||||
{% with default_item_value="<em>"|add:_("N/A")|add:"</em>" %}
|
{% with default_item_value="<em>"|add:_("N/A")|add:"</em>" %}
|
||||||
|
@ -870,8 +870,6 @@ class InstanceTests(test.TestCase):
|
|||||||
'server_list',
|
'server_list',
|
||||||
'flavor_list',
|
'flavor_list',
|
||||||
'server_delete'),
|
'server_delete'),
|
||||||
cinder: ('volume_snapshot_list',
|
|
||||||
'volume_list',),
|
|
||||||
api.glance: ('image_list_detailed',)})
|
api.glance: ('image_list_detailed',)})
|
||||||
def test_create_instance_snapshot(self):
|
def test_create_instance_snapshot(self):
|
||||||
server = self.servers.first()
|
server = self.servers.first()
|
||||||
@ -883,17 +881,15 @@ class InstanceTests(test.TestCase):
|
|||||||
|
|
||||||
api.glance.image_list_detailed(IsA(http.HttpRequest),
|
api.glance.image_list_detailed(IsA(http.HttpRequest),
|
||||||
marker=None).AndReturn([[], False])
|
marker=None).AndReturn([[], False])
|
||||||
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
|
|
||||||
cinder.volume_list(IsA(http.HttpRequest)).AndReturn([])
|
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
formData = {'instance_id': server.id,
|
formData = {'instance_id': server.id,
|
||||||
'method': 'CreateSnapshot',
|
'method': 'CreateSnapshot',
|
||||||
'name': 'snapshot1'}
|
'name': 'snapshot1'}
|
||||||
url = reverse('horizon:project:images_and_snapshots:snapshots:create',
|
url = reverse('horizon:project:images:snapshots:create',
|
||||||
args=[server.id])
|
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)
|
res = self.client.post(url, formData)
|
||||||
self.assertRedirects(res, redir_url)
|
self.assertRedirects(res, redir_url)
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ from openstack_dashboard.api import base
|
|||||||
from openstack_dashboard.api import cinder
|
from openstack_dashboard.api import cinder
|
||||||
from openstack_dashboard.usage import quotas
|
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__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
@ -27,7 +27,7 @@ from openstack_dashboard.api import base
|
|||||||
from openstack_dashboard.api import cinder
|
from openstack_dashboard.api import cinder
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.volumes \
|
from openstack_dashboard.dashboards.project.volumes \
|
||||||
import tables as volume_tables
|
.volumes import tables as volume_tables
|
||||||
|
|
||||||
|
|
||||||
class DeleteVolumeSnapshot(tables.DeleteAction):
|
class DeleteVolumeSnapshot(tables.DeleteAction):
|
||||||
@ -51,7 +51,7 @@ class DeleteVolumeSnapshot(tables.DeleteAction):
|
|||||||
class CreateVolumeFromSnapshot(tables.LinkAction):
|
class CreateVolumeFromSnapshot(tables.LinkAction):
|
||||||
name = "create_from_snapshot"
|
name = "create_from_snapshot"
|
||||||
verbose_name = _("Create Volume")
|
verbose_name = _("Create Volume")
|
||||||
url = "horizon:project:volumes:create"
|
url = "horizon:project:volumes:volumes:create"
|
||||||
classes = ("ajax-modal", "btn-camera")
|
classes = ("ajax-modal", "btn-camera")
|
||||||
policy_rules = (("volume", "volume:create"),)
|
policy_rules = (("volume", "volume:create"),)
|
||||||
|
|
||||||
@ -95,10 +95,11 @@ class SnapshotVolumeNameColumn(tables.Column):
|
|||||||
class VolumeSnapshotsTable(volume_tables.VolumesTableBase):
|
class VolumeSnapshotsTable(volume_tables.VolumesTableBase):
|
||||||
name = tables.Column("display_name",
|
name = tables.Column("display_name",
|
||||||
verbose_name=_("Name"),
|
verbose_name=_("Name"),
|
||||||
link="horizon:project:images_and_snapshots:detail")
|
link="horizon:project:volumes:detail")
|
||||||
volume_name = SnapshotVolumeNameColumn("display_name",
|
volume_name = SnapshotVolumeNameColumn(
|
||||||
verbose_name=_("Volume Name"),
|
"display_name",
|
||||||
link="horizon:project:volumes:detail")
|
verbose_name=_("Volume Name"),
|
||||||
|
link="horizon:project:volumes:volumes:detail")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
name = "volume_snapshots"
|
name = "volume_snapshots"
|
@ -26,15 +26,14 @@ from openstack_dashboard.api import cinder
|
|||||||
class OverviewTab(tabs.Tab):
|
class OverviewTab(tabs.Tab):
|
||||||
name = _("Overview")
|
name = _("Overview")
|
||||||
slug = "overview"
|
slug = "overview"
|
||||||
template_name = ("project/images_and_snapshots/snapshots/"
|
template_name = ("project/volumes/snapshots/_detail_overview.html")
|
||||||
"_detail_overview.html")
|
|
||||||
|
|
||||||
def get_context_data(self, request):
|
def get_context_data(self, request):
|
||||||
try:
|
try:
|
||||||
snapshot = self.tab_group.kwargs['snapshot']
|
snapshot = self.tab_group.kwargs['snapshot']
|
||||||
volume = cinder.volume_get(request, snapshot.volume_id)
|
volume = cinder.volume_get(request, snapshot.volume_id)
|
||||||
except Exception:
|
except Exception:
|
||||||
redirect = reverse('horizon:project:images_and_snapshots:index')
|
redirect = reverse('horizon:project:volumes:index')
|
||||||
exceptions.handle(self.request,
|
exceptions.handle(self.request,
|
||||||
_('Unable to retrieve snapshot details.'),
|
_('Unable to retrieve snapshot details.'),
|
||||||
redirect=redirect)
|
redirect=redirect)
|
@ -28,7 +28,7 @@ from openstack_dashboard.test import helpers as test
|
|||||||
from openstack_dashboard.usage import quotas
|
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):
|
class VolumeSnapshotsViewTests(test.TestCase):
|
||||||
@ -46,11 +46,12 @@ class VolumeSnapshotsViewTests(test.TestCase):
|
|||||||
AndReturn(usage_limit)
|
AndReturn(usage_limit)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:create_snapshot',
|
url = reverse('horizon:project:volumes:'
|
||||||
args=[volume.id])
|
'volumes:create_snapshot', args=[volume.id])
|
||||||
res = self.client.get(url)
|
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',
|
@test.create_stubs({cinder: ('volume_get',
|
||||||
'volume_snapshot_create',)})
|
'volume_snapshot_create',)})
|
||||||
@ -73,7 +74,7 @@ class VolumeSnapshotsViewTests(test.TestCase):
|
|||||||
'volume_id': volume.id,
|
'volume_id': volume.id,
|
||||||
'name': snapshot.display_name,
|
'name': snapshot.display_name,
|
||||||
'description': snapshot.display_description}
|
'description': snapshot.display_description}
|
||||||
url = reverse('horizon:project:volumes:create_snapshot',
|
url = reverse('horizon:project:volumes:volumes:create_snapshot',
|
||||||
args=[volume.id])
|
args=[volume.id])
|
||||||
res = self.client.post(url, formData)
|
res = self.client.post(url, formData)
|
||||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||||
@ -99,12 +100,12 @@ class VolumeSnapshotsViewTests(test.TestCase):
|
|||||||
'volume_id': volume.id,
|
'volume_id': volume.id,
|
||||||
'name': snapshot.display_name,
|
'name': snapshot.display_name,
|
||||||
'description': snapshot.display_description}
|
'description': snapshot.display_description}
|
||||||
url = reverse('horizon:project:volumes:create_snapshot',
|
url = reverse('horizon:project:volumes:volumes:create_snapshot',
|
||||||
args=[volume.id])
|
args=[volume.id])
|
||||||
res = self.client.post(url, formData)
|
res = self.client.post(url, formData)
|
||||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
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',
|
api.cinder: ('volume_snapshot_list',
|
||||||
'volume_list',
|
'volume_list',
|
||||||
'volume_snapshot_delete')})
|
'volume_snapshot_delete')})
|
||||||
@ -113,20 +114,20 @@ class VolumeSnapshotsViewTests(test.TestCase):
|
|||||||
volumes = self.volumes.list()
|
volumes = self.volumes.list()
|
||||||
snapshot = self.volume_snapshots.first()
|
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)). \
|
api.cinder.volume_snapshot_list(IsA(http.HttpRequest)). \
|
||||||
AndReturn(vol_snapshots)
|
AndReturn(vol_snapshots)
|
||||||
api.cinder.volume_list(IsA(http.HttpRequest)) \
|
api.cinder.volume_list(IsA(http.HttpRequest)). \
|
||||||
.AndReturn(volumes)
|
AndReturn(volumes)
|
||||||
|
|
||||||
api.cinder.volume_snapshot_delete(IsA(http.HttpRequest), snapshot.id)
|
api.cinder.volume_snapshot_delete(IsA(http.HttpRequest), snapshot.id)
|
||||||
api.glance.image_list_detailed(IsA(http.HttpRequest),
|
api.cinder.volume_list(IsA(http.HttpRequest), search_opts=None). \
|
||||||
marker=None).AndReturn(([], False))
|
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)). \
|
api.cinder.volume_snapshot_list(IsA(http.HttpRequest)). \
|
||||||
AndReturn([])
|
AndReturn([])
|
||||||
api.cinder.volume_list(IsA(http.HttpRequest)) \
|
api.cinder.volume_list(IsA(http.HttpRequest)). \
|
||||||
.AndReturn(volumes)
|
AndReturn(volumes)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
formData = {'action':
|
formData = {'action':
|
||||||
@ -148,7 +149,7 @@ class VolumeSnapshotsViewTests(test.TestCase):
|
|||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:images_and_snapshots:detail',
|
url = reverse('horizon:project:volumes:detail',
|
||||||
args=[snapshot.id])
|
args=[snapshot.id])
|
||||||
res = self.client.get(url)
|
res = self.client.get(url)
|
||||||
|
|
||||||
@ -172,7 +173,7 @@ class VolumeSnapshotsViewTests(test.TestCase):
|
|||||||
AndRaise(self.exceptions.cinder)
|
AndRaise(self.exceptions.cinder)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:images_and_snapshots:detail',
|
url = reverse('horizon:project:volumes:detail',
|
||||||
args=[snapshot.id])
|
args=[snapshot.id])
|
||||||
res = self.client.get(url)
|
res = self.client.get(url)
|
||||||
|
|
||||||
@ -191,7 +192,7 @@ class VolumeSnapshotsViewTests(test.TestCase):
|
|||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:images_and_snapshots:detail',
|
url = reverse('horizon:project:volumes:detail',
|
||||||
args=[snapshot.id])
|
args=[snapshot.id])
|
||||||
res = self.client.get(url)
|
res = self.client.get(url)
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
# 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
|
# 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
|
# 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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from django.utils.datastructures import SortedDict
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import exceptions
|
||||||
from horizon import tabs
|
from horizon import tabs
|
||||||
|
|
||||||
|
from openstack_dashboard import api
|
||||||
|
|
||||||
class OverviewTab(tabs.Tab):
|
from openstack_dashboard.dashboards.project.volumes.snapshots \
|
||||||
name = _("Overview")
|
import tables as vol_snapshot_tables
|
||||||
slug = "overview"
|
from openstack_dashboard.dashboards.project.volumes.volumes \
|
||||||
template_name = ("project/volumes/"
|
import tables as volume_tables
|
||||||
"_detail_overview.html")
|
|
||||||
|
|
||||||
def get_context_data(self, request):
|
|
||||||
return {"volume": self.tab_group.kwargs['volume']}
|
|
||||||
|
|
||||||
|
|
||||||
class VolumeDetailTabs(tabs.TabGroup):
|
class VolumeTableMixIn(object):
|
||||||
slug = "volume_details"
|
def _get_volumes(self, search_opts=None):
|
||||||
tabs = (OverviewTab,)
|
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
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block title %}{% trans "Volumes" %}{% endblock %}
|
{% block title %}{% trans "Volumes & Snapshots" %}{% endblock %}
|
||||||
|
|
||||||
{% block page_header %}
|
{% 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 %}
|
{% endblock page_header %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
{{ table.render }}
|
<div class="row-fluid">
|
||||||
|
<div class="span12">
|
||||||
|
{{ tab_group.render }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<dd>{{ snapshot.status|capfirst }}</dd>
|
<dd>{{ snapshot.status|capfirst }}</dd>
|
||||||
<dt>{% trans "Volume" %}</dt>
|
<dt>{% trans "Volume" %}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<a href="{% url 'horizon:project:volumes:detail' snapshot.volume_id %}">
|
<a href="{% url 'horizon:project:volumes:volumes:detail' snapshot.volume_id %}">
|
||||||
{% if volume.display_name %}
|
{% if volume.display_name %}
|
||||||
{{ volume.display_name }}
|
{{ volume.display_name }}
|
||||||
{% else %}
|
{% else %}
|
@ -3,7 +3,7 @@
|
|||||||
{% load url from future %}
|
{% load url from future %}
|
||||||
|
|
||||||
{% block form_id %}attach_volume_form{% endblock %}
|
{% 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 form_class %}{{ block.super }} horizontal {% if show_attach %}split_half{% else %} no_split{% endif %}{% endblock %}
|
||||||
|
|
||||||
{% block modal_id %}attach_volume_modal{% endblock %}
|
{% block modal_id %}attach_volume_modal{% endblock %}
|
@ -3,7 +3,7 @@
|
|||||||
{% load url from future %}
|
{% load url from future %}
|
||||||
|
|
||||||
{% block form_id %}{% endblock %}
|
{% 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_id %}create_volume_modal{% endblock %}
|
||||||
{% block modal-header %}{% trans "Create Volume" %}{% endblock %}
|
{% block modal-header %}{% trans "Create Volume" %}{% endblock %}
|
||||||
@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="right quota-dynamic">
|
<div class="right quota-dynamic">
|
||||||
{% include "project/volumes/_limits.html" with usages=usages %}
|
{% include "project/volumes/volumes/_limits.html" with usages=usages %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
@ -3,7 +3,7 @@
|
|||||||
{% load url from future %}
|
{% load url from future %}
|
||||||
|
|
||||||
{% block form_id %}{% endblock %}
|
{% 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_id %}create_volume_snapshot_modal{% endblock %}
|
||||||
{% block modal-header %}{% trans "Create Volume Snapshot" %}{% endblock %}
|
{% block modal-header %}{% trans "Create Volume Snapshot" %}{% endblock %}
|
||||||
@ -15,7 +15,7 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<div class="right quota-dynamic">
|
<div class="right quota-dynamic">
|
||||||
{% include "project/volumes/_limits.html" with usages=usages snapshot_quota=True %}
|
{% include "project/volumes/volumes/_limits.html" with usages=usages snapshot_quota=True %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
@ -3,7 +3,7 @@
|
|||||||
{% load url from future %}
|
{% load url from future %}
|
||||||
|
|
||||||
{% block form_id %}{% endblock %}
|
{% 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_id %}extend_volume_modal{% endblock %}
|
||||||
{% block modal-header %}{% trans "Extend Volume" %}{% endblock %}
|
{% block modal-header %}{% trans "Extend Volume" %}{% endblock %}
|
||||||
@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="right quota-dynamic">
|
<div class="right quota-dynamic">
|
||||||
{% include "project/volumes/_extend_limits.html" with usages=usages %}
|
{% include "project/volumes/volumes/_extend_limits.html" with usages=usages %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
@ -3,7 +3,7 @@
|
|||||||
{% load url from future %}
|
{% load url from future %}
|
||||||
|
|
||||||
{% block form_id %}{% endblock %}
|
{% 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_id %}update_volume_modal{% endblock %}
|
||||||
{% block modal-header %}{% trans "Edit Volume" %}{% endblock %}
|
{% block modal-header %}{% trans "Edit Volume" %}{% endblock %}
|
@ -7,5 +7,5 @@
|
|||||||
{% endblock page_header %}
|
{% endblock page_header %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
{% include 'project/volumes/_attach.html' %}
|
{% include 'project/volumes/volumes/_attach.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -7,5 +7,5 @@
|
|||||||
{% endblock page_header %}
|
{% endblock page_header %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
{% include 'project/volumes/_create.html' %}
|
{% include 'project/volumes/volumes/_create.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -7,5 +7,5 @@
|
|||||||
{% endblock page_header %}
|
{% endblock page_header %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
{% include 'project/volumes/_create_snapshot.html' %}
|
{% include 'project/volumes/volumes/_create_snapshot.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -7,5 +7,5 @@
|
|||||||
{% endblock page_header %}
|
{% endblock page_header %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
{% include 'project/volumes/_extend.html' %}
|
{% include 'project/volumes/volumes/_extend.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -7,5 +7,5 @@
|
|||||||
{% endblock page_header %}
|
{% endblock page_header %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
{% include 'project/volumes/_update.html' %}
|
{% include 'project/volumes/volumes/_update.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
48
openstack_dashboard/dashboards/project/volumes/test.py
Normal file
48
openstack_dashboard/dashboards/project/volumes/test.py
Normal file
@ -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')
|
@ -14,28 +14,19 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from django.conf.urls import include # noqa
|
||||||
from django.conf.urls import patterns # noqa
|
from django.conf.urls import patterns # noqa
|
||||||
from django.conf.urls import url # noqa
|
from django.conf.urls import url # noqa
|
||||||
|
|
||||||
from openstack_dashboard.dashboards.project.volumes import views
|
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'^$', views.IndexView.as_view(), name='index'),
|
||||||
url(r'^create/$', views.CreateView.as_view(), name='create'),
|
url(r'', include(volume_urls, namespace='volumes')),
|
||||||
url(r'^(?P<volume_id>[^/]+)/extend/$',
|
url(r'^snapshots/(?P<snapshot_id>[^/]+)/$',
|
||||||
views.ExtendView.as_view(),
|
|
||||||
name='extend'),
|
|
||||||
url(r'^(?P<volume_id>[^/]+)/attach/$',
|
|
||||||
views.EditAttachmentsView.as_view(),
|
|
||||||
name='attach'),
|
|
||||||
url(r'^(?P<volume_id>[^/]+)/create_snapshot/$',
|
|
||||||
views.CreateSnapshotView.as_view(),
|
|
||||||
name='create_snapshot'),
|
|
||||||
url(r'^(?P<volume_id>[^/]+)/$',
|
|
||||||
views.DetailView.as_view(),
|
views.DetailView.as_view(),
|
||||||
name='detail'),
|
name='detail'),
|
||||||
url(r'^(?P<volume_id>[^/]+)/update/$',
|
|
||||||
views.UpdateView.as_view(),
|
|
||||||
name='update'),
|
|
||||||
)
|
)
|
||||||
|
@ -14,279 +14,47 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
"""
|
|
||||||
Views for managing volumes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
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 django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from horizon import exceptions
|
from horizon import exceptions
|
||||||
from horizon import forms
|
|
||||||
from horizon import tables
|
|
||||||
from horizon import tabs
|
from horizon import tabs
|
||||||
from horizon.utils import memoized
|
from horizon.utils import memoized
|
||||||
|
|
||||||
from openstack_dashboard import api
|
|
||||||
from openstack_dashboard.api import cinder
|
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 \
|
from openstack_dashboard.dashboards.project.volumes \
|
||||||
import tabs as project_tabs
|
import tabs as project_tabs
|
||||||
|
from openstack_dashboard.dashboards.project.volumes \
|
||||||
|
.snapshots import tabs as vol_snapshot_tabs
|
||||||
|
|
||||||
|
|
||||||
class VolumeTableMixIn(object):
|
class IndexView(tabs.TabbedTableView):
|
||||||
def _get_volumes(self, search_opts=None):
|
tab_group_class = project_tabs.VolumeAndSnapshotTabs
|
||||||
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
|
|
||||||
template_name = 'project/volumes/index.html'
|
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):
|
class DetailView(tabs.TabView):
|
||||||
tab_group_class = project_tabs.VolumeDetailTabs
|
tab_group_class = vol_snapshot_tabs.SnapshotDetailTabs
|
||||||
template_name = 'project/volumes/detail.html'
|
template_name = 'project/volumes/snapshots/detail.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(DetailView, self).get_context_data(**kwargs)
|
context = super(DetailView, self).get_context_data(**kwargs)
|
||||||
context["volume"] = self.get_data()
|
context["snapshot"] = self.get_data()
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@memoized.memoized_method
|
@memoized.memoized_method
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
try:
|
try:
|
||||||
volume_id = self.kwargs['volume_id']
|
snapshot_id = self.kwargs['snapshot_id']
|
||||||
volume = cinder.volume_get(self.request, volume_id)
|
snapshot = cinder.volume_snapshot_get(self.request, snapshot_id)
|
||||||
for att in volume.attachments:
|
|
||||||
att['instance'] = api.nova.server_get(self.request,
|
|
||||||
att['server_id'])
|
|
||||||
except Exception:
|
except Exception:
|
||||||
redirect = reverse('horizon:project:volumes:index')
|
redirect = reverse('horizon:project:volumes:index')
|
||||||
exceptions.handle(self.request,
|
exceptions.handle(self.request,
|
||||||
_('Unable to retrieve volume details.'),
|
_('Unable to retrieve snapshot details.'),
|
||||||
redirect=redirect)
|
redirect=redirect)
|
||||||
return volume
|
return snapshot
|
||||||
|
|
||||||
def get_tabs(self, request, *args, **kwargs):
|
def get_tabs(self, request, *args, **kwargs):
|
||||||
volume = self.get_data()
|
snapshot = self.get_data()
|
||||||
return self.tab_group_class(request, volume=volume, **kwargs)
|
return self.tab_group_class(request, snapshot=snapshot, **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)
|
|
||||||
|
@ -35,7 +35,7 @@ from horizon.utils.memoized import memoized # noqa
|
|||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
from openstack_dashboard.api import cinder
|
from openstack_dashboard.api import cinder
|
||||||
from openstack_dashboard.api import glance
|
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.dashboards.project.instances import tables
|
||||||
from openstack_dashboard.usage import quotas
|
from openstack_dashboard.usage import quotas
|
||||||
|
|
||||||
@ -466,7 +466,7 @@ class CreateSnapshotForm(forms.SelfHandlingForm):
|
|||||||
messages.info(request, message)
|
messages.info(request, message)
|
||||||
return snapshot
|
return snapshot
|
||||||
except Exception:
|
except Exception:
|
||||||
redirect = reverse("horizon:project:images_and_snapshots:index")
|
redirect = reverse("horizon:project:volumes:index")
|
||||||
exceptions.handle(request,
|
exceptions.handle(request,
|
||||||
_('Unable to create volume snapshot.'),
|
_('Unable to create volume snapshot.'),
|
||||||
redirect=redirect)
|
redirect=redirect)
|
@ -67,7 +67,7 @@ class DeleteVolume(tables.DeleteAction):
|
|||||||
class CreateVolume(tables.LinkAction):
|
class CreateVolume(tables.LinkAction):
|
||||||
name = "create"
|
name = "create"
|
||||||
verbose_name = _("Create Volume")
|
verbose_name = _("Create Volume")
|
||||||
url = "horizon:project:volumes:create"
|
url = "horizon:project:volumes:volumes:create"
|
||||||
classes = ("ajax-modal", "btn-create")
|
classes = ("ajax-modal", "btn-create")
|
||||||
policy_rules = (("volume", "volume:create"),)
|
policy_rules = (("volume", "volume:create"),)
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ class CreateVolume(tables.LinkAction):
|
|||||||
class ExtendVolume(tables.LinkAction):
|
class ExtendVolume(tables.LinkAction):
|
||||||
name = "extend"
|
name = "extend"
|
||||||
verbose_name = _("Extend Volume")
|
verbose_name = _("Extend Volume")
|
||||||
url = "horizon:project:volumes:extend"
|
url = "horizon:project:volumes:volumes:extend"
|
||||||
classes = ("ajax-modal", "btn-extend")
|
classes = ("ajax-modal", "btn-extend")
|
||||||
policy_rules = (("volume", "volume:extend"),)
|
policy_rules = (("volume", "volume:extend"),)
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ class ExtendVolume(tables.LinkAction):
|
|||||||
class EditAttachments(tables.LinkAction):
|
class EditAttachments(tables.LinkAction):
|
||||||
name = "attachments"
|
name = "attachments"
|
||||||
verbose_name = _("Edit Attachments")
|
verbose_name = _("Edit Attachments")
|
||||||
url = "horizon:project:volumes:attach"
|
url = "horizon:project:volumes:volumes:attach"
|
||||||
classes = ("ajax-modal", "btn-edit")
|
classes = ("ajax-modal", "btn-edit")
|
||||||
|
|
||||||
def allowed(self, request, volume=None):
|
def allowed(self, request, volume=None):
|
||||||
@ -129,7 +129,7 @@ class EditAttachments(tables.LinkAction):
|
|||||||
class CreateSnapshot(tables.LinkAction):
|
class CreateSnapshot(tables.LinkAction):
|
||||||
name = "snapshots"
|
name = "snapshots"
|
||||||
verbose_name = _("Create Snapshot")
|
verbose_name = _("Create Snapshot")
|
||||||
url = "horizon:project:volumes:create_snapshot"
|
url = "horizon:project:volumes:volumes:create_snapshot"
|
||||||
classes = ("ajax-modal", "btn-camera")
|
classes = ("ajax-modal", "btn-camera")
|
||||||
policy_rules = (("volume", "volume:create_snapshot"),)
|
policy_rules = (("volume", "volume:create_snapshot"),)
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ class CreateSnapshot(tables.LinkAction):
|
|||||||
class EditVolume(tables.LinkAction):
|
class EditVolume(tables.LinkAction):
|
||||||
name = "edit"
|
name = "edit"
|
||||||
verbose_name = _("Edit Volume")
|
verbose_name = _("Edit Volume")
|
||||||
url = "horizon:project:volumes:update"
|
url = "horizon:project:volumes:volumes:update"
|
||||||
classes = ("ajax-modal", "btn-edit")
|
classes = ("ajax-modal", "btn-edit")
|
||||||
policy_rules = (("volume", "volume:update"),)
|
policy_rules = (("volume", "volume:update"),)
|
||||||
|
|
||||||
@ -228,7 +228,7 @@ class VolumesTableBase(tables.DataTable):
|
|||||||
)
|
)
|
||||||
name = tables.Column("display_name",
|
name = tables.Column("display_name",
|
||||||
verbose_name=_("Name"),
|
verbose_name=_("Name"),
|
||||||
link="horizon:project:volumes:detail")
|
link="horizon:project:volumes:volumes:detail")
|
||||||
description = tables.Column("display_description",
|
description = tables.Column("display_description",
|
||||||
verbose_name=_("Description"),
|
verbose_name=_("Description"),
|
||||||
truncate=40)
|
truncate=40)
|
||||||
@ -257,7 +257,7 @@ class VolumesFilterAction(tables.FilterAction):
|
|||||||
class VolumesTable(VolumesTableBase):
|
class VolumesTable(VolumesTableBase):
|
||||||
name = tables.Column("display_name",
|
name = tables.Column("display_name",
|
||||||
verbose_name=_("Name"),
|
verbose_name=_("Name"),
|
||||||
link="horizon:project:volumes:detail")
|
link="horizon:project:volumes:volumes:detail")
|
||||||
volume_type = tables.Column(get_volume_type,
|
volume_type = tables.Column(get_volume_type,
|
||||||
verbose_name=_("Type"),
|
verbose_name=_("Type"),
|
||||||
empty_value="-")
|
empty_value="-")
|
@ -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,)
|
@ -27,7 +27,8 @@ from mox import IsA # noqa
|
|||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
from openstack_dashboard.api import cinder
|
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.test import helpers as test
|
||||||
from openstack_dashboard.usage import quotas
|
from openstack_dashboard.usage import quotas
|
||||||
|
|
||||||
@ -96,7 +97,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:create')
|
url = reverse('horizon:project:volumes:volumes:create')
|
||||||
res = self.client.post(url, formData)
|
res = self.client.post(url, formData)
|
||||||
|
|
||||||
redirect_url = reverse('horizon:project:volumes:index')
|
redirect_url = reverse('horizon:project:volumes:index')
|
||||||
@ -160,7 +161,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:create')
|
url = reverse('horizon:project:volumes:volumes:create')
|
||||||
res = self.client.post(url, formData)
|
res = self.client.post(url, formData)
|
||||||
|
|
||||||
redirect_url = reverse('horizon:project:volumes:index')
|
redirect_url = reverse('horizon:project:volumes:index')
|
||||||
@ -207,7 +208,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
# get snapshot from url
|
# get snapshot from url
|
||||||
url = reverse('horizon:project:volumes:create')
|
url = reverse('horizon:project:volumes:volumes:create')
|
||||||
res = self.client.post("?".join([url,
|
res = self.client.post("?".join([url,
|
||||||
"snapshot_id=" + str(snapshot.id)]),
|
"snapshot_id=" + str(snapshot.id)]),
|
||||||
formData)
|
formData)
|
||||||
@ -276,7 +277,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
source_volid=volume.id).AndReturn(volume)
|
source_volid=volume.id).AndReturn(volume)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:create')
|
url = reverse('horizon:project:volumes:volumes:create')
|
||||||
redirect_url = reverse('horizon:project:volumes:index')
|
redirect_url = reverse('horizon:project:volumes:index')
|
||||||
res = self.client.post(url, formData)
|
res = self.client.post(url, formData)
|
||||||
self.assertNoFormErrors(res)
|
self.assertNoFormErrors(res)
|
||||||
@ -346,7 +347,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
# get snapshot from dropdown list
|
# get snapshot from dropdown list
|
||||||
url = reverse('horizon:project:volumes:create')
|
url = reverse('horizon:project:volumes:volumes:create')
|
||||||
res = self.client.post(url, formData)
|
res = self.client.post(url, formData)
|
||||||
|
|
||||||
redirect_url = reverse('horizon:project:volumes:index')
|
redirect_url = reverse('horizon:project:volumes:index')
|
||||||
@ -382,7 +383,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:create')
|
url = reverse('horizon:project:volumes:volumes:create')
|
||||||
res = self.client.post("?".join([url,
|
res = self.client.post("?".join([url,
|
||||||
"snapshot_id=" + str(snapshot.id)]),
|
"snapshot_id=" + str(snapshot.id)]),
|
||||||
formData, follow=True)
|
formData, follow=True)
|
||||||
@ -437,7 +438,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
# get image from url
|
# get image from url
|
||||||
url = reverse('horizon:project:volumes:create')
|
url = reverse('horizon:project:volumes:volumes:create')
|
||||||
res = self.client.post("?".join([url,
|
res = self.client.post("?".join([url,
|
||||||
"image_id=" + str(image.id)]),
|
"image_id=" + str(image.id)]),
|
||||||
formData)
|
formData)
|
||||||
@ -508,7 +509,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
# get image from dropdown list
|
# get image from dropdown list
|
||||||
url = reverse('horizon:project:volumes:create')
|
url = reverse('horizon:project:volumes:volumes:create')
|
||||||
res = self.client.post(url, formData)
|
res = self.client.post(url, formData)
|
||||||
|
|
||||||
redirect_url = reverse('horizon:project:volumes:index')
|
redirect_url = reverse('horizon:project:volumes:index')
|
||||||
@ -546,7 +547,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:create')
|
url = reverse('horizon:project:volumes:volumes:create')
|
||||||
res = self.client.post("?".join([url,
|
res = self.client.post("?".join([url,
|
||||||
"image_id=" + str(image.id)]),
|
"image_id=" + str(image.id)]),
|
||||||
formData, follow=True)
|
formData, follow=True)
|
||||||
@ -588,7 +589,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:create')
|
url = reverse('horizon:project:volumes:volumes:create')
|
||||||
res = self.client.post("?".join([url,
|
res = self.client.post("?".join([url,
|
||||||
"image_id=" + str(image.id)]),
|
"image_id=" + str(image.id)]),
|
||||||
formData, follow=True)
|
formData, follow=True)
|
||||||
@ -639,7 +640,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:create')
|
url = reverse('horizon:project:volumes:volumes:create')
|
||||||
res = self.client.post(url, formData)
|
res = self.client.post(url, formData)
|
||||||
|
|
||||||
expected_error = [u'A volume of 5000GB cannot be created as you only'
|
expected_error = [u'A volume of 5000GB cannot be created as you only'
|
||||||
@ -688,7 +689,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:create')
|
url = reverse('horizon:project:volumes:volumes:create')
|
||||||
res = self.client.post(url, formData)
|
res = self.client.post(url, formData)
|
||||||
|
|
||||||
expected_error = [u'You are already using all of your available'
|
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')
|
url = reverse('horizon:project:volumes:index')
|
||||||
res = self.client.post(url, formData, follow=True)
|
res = self.client.post(url, formData, follow=True)
|
||||||
self.assertMessageCount(res, error=1)
|
|
||||||
self.assertEqual(list(res.context['messages'])[0].message,
|
self.assertEqual(list(res.context['messages'])[0].message,
|
||||||
u'Unable to delete volume "%s". '
|
u'Unable to delete volume "%s". '
|
||||||
u'One or more snapshots depend on it.' %
|
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])
|
api.nova.server_list(IsA(http.HttpRequest)).AndReturn([servers, False])
|
||||||
self.mox.ReplayAll()
|
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)
|
res = self.client.get(url)
|
||||||
# Asserting length of 2 accounts for the one instance option,
|
# Asserting length of 2 accounts for the one instance option,
|
||||||
# and the one 'Choose 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])
|
api.nova.server_list(IsA(http.HttpRequest)).AndReturn([servers, False])
|
||||||
self.mox.ReplayAll()
|
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)
|
res = self.client.get(url)
|
||||||
# Assert the device field is hidden.
|
# Assert the device field is hidden.
|
||||||
form = res.context['form']
|
form = res.context['form']
|
||||||
@ -815,7 +817,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:attach',
|
url = reverse('horizon:project:volumes:volumes:attach',
|
||||||
args=[volume.id])
|
args=[volume.id])
|
||||||
res = self.client.get(url)
|
res = self.client.get(url)
|
||||||
|
|
||||||
@ -874,7 +876,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:detail',
|
url = reverse('horizon:project:volumes:volumes:detail',
|
||||||
args=[volume.id])
|
args=[volume.id])
|
||||||
res = self.client.get(url)
|
res = self.client.get(url)
|
||||||
|
|
||||||
@ -925,7 +927,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:detail',
|
url = reverse('horizon:project:volumes:volumes:detail',
|
||||||
args=[volume.id])
|
args=[volume.id])
|
||||||
res = self.client.get(url)
|
res = self.client.get(url)
|
||||||
|
|
||||||
@ -948,7 +950,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
'name': volume.display_name,
|
'name': volume.display_name,
|
||||||
'description': volume.display_description}
|
'description': volume.display_description}
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:update',
|
url = reverse('horizon:project:volumes:volumes:update',
|
||||||
args=[volume.id])
|
args=[volume.id])
|
||||||
res = self.client.post(url, formData)
|
res = self.client.post(url, formData)
|
||||||
self.assertRedirectsNoFollow(res, VOLUME_INDEX_URL)
|
self.assertRedirectsNoFollow(res, VOLUME_INDEX_URL)
|
||||||
@ -970,7 +972,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:extend',
|
url = reverse('horizon:project:volumes:volumes:extend',
|
||||||
args=[volume.id])
|
args=[volume.id])
|
||||||
res = self.client.post(url, formData)
|
res = self.client.post(url, formData)
|
||||||
|
|
||||||
@ -996,7 +998,7 @@ class VolumeViewTests(test.TestCase):
|
|||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
url = reverse('horizon:project:volumes:extend',
|
url = reverse('horizon:project:volumes:volumes:extend',
|
||||||
args=[volume.id])
|
args=[volume.id])
|
||||||
res = self.client.post(url, formData)
|
res = self.client.post(url, formData)
|
||||||
self.assertFormError(res, 'form', None,
|
self.assertFormError(res, 'form', None,
|
@ -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<volume_id>[^/]+)/extend/$',
|
||||||
|
views.ExtendView.as_view(),
|
||||||
|
name='extend'),
|
||||||
|
url(r'^(?P<volume_id>[^/]+)/attach/$',
|
||||||
|
views.EditAttachmentsView.as_view(),
|
||||||
|
name='attach'),
|
||||||
|
url(r'^(?P<volume_id>[^/]+)/create_snapshot/$',
|
||||||
|
views.CreateSnapshotView.as_view(),
|
||||||
|
name='create_snapshot'),
|
||||||
|
url(r'^(?P<volume_id>[^/]+)/$',
|
||||||
|
views.DetailView.as_view(),
|
||||||
|
name='detail'),
|
||||||
|
url(r'^(?P<volume_id>[^/]+)/update/$',
|
||||||
|
views.UpdateView.as_view(),
|
||||||
|
name='update'),
|
||||||
|
)
|
244
openstack_dashboard/dashboards/project/volumes/volumes/views.py
Normal file
244
openstack_dashboard/dashboards/project/volumes/volumes/views.py
Normal file
@ -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)
|
@ -81,7 +81,7 @@ class NetworkProfile(tables.DataTable):
|
|||||||
class EditPolicyProfile(tables.LinkAction):
|
class EditPolicyProfile(tables.LinkAction):
|
||||||
name = "edit"
|
name = "edit"
|
||||||
verbose_name = _("Edit Policy Profile")
|
verbose_name = _("Edit Policy Profile")
|
||||||
url = "horizon:project:images_and_snapshots:images:update"
|
url = "horizon:project:images:images:update"
|
||||||
classes = ("ajax-modal", "btn-edit")
|
classes = ("ajax-modal", "btn-edit")
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user