Move volume snapshots table to volumes panel

Now that instance snapshots are just images, the images & snapshots
page only has images and volume snapshots. This is an odd combination.
I think we should now move volume snapshots to the volumes panel,
since a volume snapshot is tied to a volume.

Closes-Bug: #1190843
Change-Id: I3e12cec8b859a30db9f131e9889a92277e064086
This commit is contained in:
Zhenguo Niu 2014-02-18 10:14:00 +08:00
parent 16a22952c5
commit 3d25f1d595
73 changed files with 710 additions and 608 deletions

View File

@ -18,8 +18,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from openstack_dashboard.dashboards.project.images_and_snapshots \
.images import forms
from openstack_dashboard.dashboards.project.images.images import forms
class AdminCreateImageForm(forms.CreateImageForm):

View File

@ -19,7 +19,7 @@ from django.utils.translation import ugettext_lazy as _
from horizon import tables
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.images_and_snapshots.images \
from openstack_dashboard.dashboards.project.images.images \
import tables as project_tables

View File

@ -25,8 +25,7 @@ from horizon import exceptions
from horizon import tables
from openstack_dashboard import api
from openstack_dashboard.dashboards.project \
.images_and_snapshots.images import views
from openstack_dashboard.dashboards.project.images.images import views
from openstack_dashboard.dashboards.admin.images import forms
from openstack_dashboard.dashboards.admin.images \

View File

@ -15,7 +15,7 @@ from django.utils.translation import ugettext_lazy as _
from horizon import tables
from openstack_dashboard.api import cinder
from openstack_dashboard.dashboards.project.volumes \
import tables as project_tables
.volumes import tables as project_tables
class CreateVolumeType(tables.LinkAction):

View File

@ -34,10 +34,13 @@ from openstack_dashboard.dashboards.admin.volumes \
from openstack_dashboard.dashboards.admin.volumes \
import tables as project_tables
from openstack_dashboard.dashboards.project.volumes import views
from openstack_dashboard.dashboards.project.volumes \
import tabs as project_tabs
from openstack_dashboard.dashboards.project.volumes \
.volumes import views as volume_views
class IndexView(tables.MultiTableView, views.VolumeTableMixIn):
class IndexView(tables.MultiTableView, project_tabs.VolumeTableMixIn):
table_classes = (project_tables.VolumesTable,
project_tables.VolumeTypesTable)
template_name = "admin/volumes/index.html"
@ -74,7 +77,7 @@ class IndexView(tables.MultiTableView, views.VolumeTableMixIn):
return volume_types
class DetailView(views.DetailView):
class DetailView(volume_views.DetailView):
template_name = "admin/volumes/detail.html"

View File

@ -25,7 +25,7 @@ class BasePanels(horizon.PanelGroup):
panels = ('overview',
'instances',
'volumes',
'images_and_snapshots',
'images',
'access_and_security',)

View File

@ -77,7 +77,7 @@ class DeleteImage(tables.DeleteAction):
class CreateImage(tables.LinkAction):
name = "create"
verbose_name = _("Create Image")
url = "horizon:project:images_and_snapshots:images:create"
url = "horizon:project:images:images:create"
classes = ("ajax-modal", "btn-create")
policy_rules = (("image", "add_image"),)
@ -85,7 +85,7 @@ class CreateImage(tables.LinkAction):
class EditImage(tables.LinkAction):
name = "edit"
verbose_name = _("Edit")
url = "horizon:project:images_and_snapshots:images:update"
url = "horizon:project:images:images:update"
classes = ("ajax-modal", "btn-edit")
policy_rules = (("image", "modify_image"),)
@ -101,7 +101,7 @@ class EditImage(tables.LinkAction):
class CreateVolumeFromImage(tables.LinkAction):
name = "create_volume_from_image"
verbose_name = _("Create Volume")
url = "horizon:project:volumes:create"
url = "horizon:project:volumes:volumes:create"
classes = ("ajax-modal", "btn-camera")
policy_rules = (("volume", "volume:create"),)
@ -206,8 +206,7 @@ class ImagesTable(tables.DataTable):
("deleted", False),
)
name = tables.Column(get_image_name,
link=("horizon:project:images_and_snapshots:"
"images:detail"),
link=("horizon:project:images:images:detail"),
verbose_name=_("Image Name"))
image_type = tables.Column(get_image_type,
verbose_name=_("Type"),

View File

@ -23,7 +23,7 @@ from horizon import tabs
class OverviewTab(tabs.Tab):
name = _("Overview")
slug = "overview"
template_name = "project/images_and_snapshots/images/_detail_overview.html"
template_name = "project/images/images/_detail_overview.html"
def get_context_data(self, request):
image = self.tab_group.kwargs['image']

View File

@ -33,13 +33,11 @@ from horizon import tables as horizon_tables
from openstack_dashboard import api
from openstack_dashboard.test import helpers as test
from openstack_dashboard.dashboards.project.images_and_snapshots.images \
import forms
from openstack_dashboard.dashboards.project.images_and_snapshots.images \
import tables
from openstack_dashboard.dashboards.project.images.images import forms
from openstack_dashboard.dashboards.project.images.images import tables
IMAGES_INDEX_URL = reverse('horizon:project:images_and_snapshots:index')
IMAGES_INDEX_URL = reverse('horizon:project:images:index')
class CreateImageFormTests(test.TestCase):
@ -74,10 +72,10 @@ class CreateImageFormTests(test.TestCase):
class ImageViewTests(test.TestCase):
def test_image_create_get(self):
url = reverse('horizon:project:images_and_snapshots:images:create')
url = reverse('horizon:project:images:images:create')
res = self.client.get(url)
self.assertTemplateUsed(res,
'project/images_and_snapshots/images/create.html')
'project/images/images/create.html')
@test.create_stubs({api.glance: ('image_create',)})
def test_image_create_post_copy_from(self):
@ -111,7 +109,7 @@ class ImageViewTests(test.TestCase):
AndReturn(self.images.first())
self.mox.ReplayAll()
url = reverse('horizon:project:images_and_snapshots:images:create')
url = reverse('horizon:project:images:images:create')
res = self.client.post(url, data)
self.assertNoFormErrors(res)
@ -151,7 +149,7 @@ class ImageViewTests(test.TestCase):
AndReturn(self.images.first())
self.mox.ReplayAll()
url = reverse('horizon:project:images_and_snapshots:images:create')
url = reverse('horizon:project:images:images:create')
res = self.client.post(url, data)
self.assertNoFormErrors(res)
@ -165,12 +163,11 @@ class ImageViewTests(test.TestCase):
.AndReturn(self.images.first())
self.mox.ReplayAll()
res = self.client.get(
reverse('horizon:project:images_and_snapshots:images:detail',
args=[image.id]))
res = self.client.get(reverse('horizon:project:images:images:detail',
args=[image.id]))
self.assertTemplateUsed(res,
'project/images_and_snapshots/images/detail.html')
'project/images/images/detail.html')
self.assertEqual(res.context['image'].name, image.name)
self.assertEqual(res.context['image'].protected, image.protected)
self.assertContains(res, "<h2>Image Details: %s</h2>" % image.name,
@ -184,9 +181,8 @@ class ImageViewTests(test.TestCase):
.AndReturn(image)
self.mox.ReplayAll()
res = self.client.get(
reverse('horizon:project:images_and_snapshots:images:detail',
args=[image.id]))
res = self.client.get(reverse('horizon:project:images:images:detail',
args=[image.id]))
image_props = res.context['image_props']
@ -213,10 +209,10 @@ class ImageViewTests(test.TestCase):
self.mox.ReplayAll()
res = self.client.get(
reverse('horizon:project:images_and_snapshots:images:detail',
reverse('horizon:project:images:images:detail',
args=[image.id]))
self.assertTemplateUsed(res,
'project/images_and_snapshots/images/detail.html')
'project/images/images/detail.html')
self.assertEqual(res.context['image'].protected, image.protected)
@test.create_stubs({api.glance: ('image_get',)})
@ -227,7 +223,7 @@ class ImageViewTests(test.TestCase):
.AndRaise(self.exceptions.glance)
self.mox.ReplayAll()
url = reverse('horizon:project:images_and_snapshots:images:detail',
url = reverse('horizon:project:images:images:detail',
args=[image.id])
res = self.client.get(url)
self.assertRedirectsNoFollow(res, IMAGES_INDEX_URL)
@ -242,11 +238,11 @@ class ImageViewTests(test.TestCase):
self.mox.ReplayAll()
res = self.client.get(
reverse('horizon:project:images_and_snapshots:images:update',
reverse('horizon:project:images:images:update',
args=[image.id]))
self.assertTemplateUsed(res,
'project/images_and_snapshots/images/_update.html')
'project/images/images/_update.html')
self.assertEqual(res.context['image'].name, image.name)
# Bug 1076216 - is_public checkbox not being set correctly
self.assertContains(res, "<input type='checkbox' id='id_public'"

View File

@ -21,12 +21,10 @@
from django.conf.urls import patterns # noqa
from django.conf.urls import url # noqa
from openstack_dashboard.dashboards.project.images_and_snapshots.images \
import views
from openstack_dashboard.dashboards.project.images.images import views
VIEWS_MOD = ('openstack_dashboard.dashboards.project'
'.images_and_snapshots.images.views')
VIEWS_MOD = 'openstack_dashboard.dashboards.project.images.images.views'
urlpatterns = patterns(VIEWS_MOD,

View File

@ -32,23 +32,23 @@ from horizon.utils import memoized
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
from openstack_dashboard.dashboards.project.images_and_snapshots.images \
from openstack_dashboard.dashboards.project.images.images \
import tabs as project_tabs
class CreateView(forms.ModalFormView):
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'
success_url = reverse_lazy("horizon:project:images_and_snapshots:index")
success_url = reverse_lazy("horizon:project:images:index")
class UpdateView(forms.ModalFormView):
form_class = project_forms.UpdateImageForm
template_name = 'project/images_and_snapshots/images/update.html'
success_url = reverse_lazy("horizon:project:images_and_snapshots:index")
template_name = 'project/images/images/update.html'
success_url = reverse_lazy("horizon:project:images:index")
@memoized.memoized_method
def get_object(self):
@ -56,7 +56,7 @@ class UpdateView(forms.ModalFormView):
return api.glance.image_get(self.request, self.kwargs['image_id'])
except Exception:
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)
def get_context_data(self, **kwargs):
@ -80,7 +80,7 @@ class UpdateView(forms.ModalFormView):
class DetailView(tabs.TabView):
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):
context = super(DetailView, self).get_context_data(**kwargs)
@ -92,7 +92,7 @@ class DetailView(tabs.TabView):
try:
return api.glance.image_get(self.request, self.kwargs['image_id'])
except Exception:
url = reverse('horizon:project:images_and_snapshots:index')
url = reverse('horizon:project:images:index')
exceptions.handle(self.request,
_('Unable to retrieve image details.'),
redirect=url)

View File

@ -22,9 +22,9 @@ import horizon
from openstack_dashboard.dashboards.project import dashboard
class ImagesAndSnapshots(horizon.Panel):
name = _("Images & Snapshots")
slug = 'images_and_snapshots'
class Images(horizon.Panel):
name = _("Images")
slug = 'images'
dashboard.Project.register(ImagesAndSnapshots)
dashboard.Project.register(Images)

View File

@ -27,7 +27,7 @@ from openstack_dashboard import api
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):
@ -37,11 +37,11 @@ class SnapshotsViewTests(test.TestCase):
api.nova.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
self.mox.ReplayAll()
url = reverse('horizon:project:images_and_snapshots:snapshots:create',
url = reverse('horizon:project:images:snapshots:create',
args=[server.id])
res = self.client.get(url)
self.assertTemplateUsed(res,
'project/images_and_snapshots/snapshots/create.html')
'project/images/snapshots/create.html')
def test_create_get_server_exception(self):
server = self.servers.first()
@ -50,7 +50,7 @@ class SnapshotsViewTests(test.TestCase):
.AndRaise(self.exceptions.nova)
self.mox.ReplayAll()
url = reverse('horizon:project:images_and_snapshots:snapshots:create',
url = reverse('horizon:project:images:snapshots:create',
args=[server.id])
res = self.client.get(url)
redirect = reverse("horizon:project:instances:index")
@ -71,7 +71,7 @@ class SnapshotsViewTests(test.TestCase):
'tenant_id': self.tenant.id,
'instance_id': server.id,
'name': snapshot.name}
url = reverse('horizon:project:images_and_snapshots:snapshots:create',
url = reverse('horizon:project:images:snapshots:create',
args=[server.id])
res = self.client.post(url, formData)
@ -91,7 +91,7 @@ class SnapshotsViewTests(test.TestCase):
'tenant_id': self.tenant.id,
'instance_id': server.id,
'name': snapshot.name}
url = reverse('horizon:project:images_and_snapshots:snapshots:create',
url = reverse('horizon:project:images:snapshots:create',
args=[server.id])
res = self.client.post(url, formData)
redirect = reverse("horizon:project:instances:index")

View File

@ -21,8 +21,7 @@
from django.conf.urls import patterns # noqa
from django.conf.urls import url # noqa
from openstack_dashboard.dashboards.project.images_and_snapshots.snapshots \
import views
from openstack_dashboard.dashboards.project.images.snapshots import views
urlpatterns = patterns('',

View File

@ -31,14 +31,14 @@ from horizon.utils import memoized
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
class CreateView(forms.ModalFormView):
form_class = project_forms.CreateSnapshot
template_name = 'project/images_and_snapshots/snapshots/create.html'
success_url = reverse_lazy("horizon:project:images_and_snapshots:index")
template_name = 'project/images/snapshots/create.html'
success_url = reverse_lazy("horizon:project:images:index")
@memoized.memoized_method
def get_object(self):

View File

@ -3,7 +3,7 @@
{% load url from future %}
{% 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 modal-header %}{% trans "Create An Image" %}{% endblock %}
@ -31,5 +31,5 @@
{% block modal-footer %}
<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 %}

View File

@ -3,7 +3,7 @@
{% load url from future %}
{% block form_id %}update_image_form{% endblock %}
{% block form_action %}{% url 'horizon:project:images_and_snapshots:images:update' image.id %}{% endblock %}
{% block form_action %}{% url 'horizon:project:images:images:update' image.id %}{% endblock %}
{% block modal-header %}{% trans "Update Image" %}{% endblock %}
@ -21,5 +21,5 @@
{% block modal-footer %}
<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 %}

View File

@ -7,5 +7,5 @@
{% endblock page_header %}
{% block main %}
{% include 'project/images_and_snapshots/images/_create.html' %}
{% include 'project/images/images/_create.html' %}
{% endblock %}

View File

@ -7,5 +7,5 @@
{% endblock page_header %}
{% block main %}
{% include 'project/images_and_snapshots/images/_update.html' %}
{% include 'project/images/images/_update.html' %}
{% endblock %}

View File

@ -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 %}

View File

@ -3,7 +3,7 @@
{% load url from future %}
{% block form_id %}create_snapshot_form{% endblock %}
{% block form_action %}{% url 'horizon:project:images_and_snapshots:snapshots:create' instance.id %}{% endblock %}
{% block form_action %}{% url 'horizon:project:images:snapshots:create' instance.id %}{% endblock %}
{% block modal_id %}create_snapshot_modal{% endblock %}
{% block modal-header %}{% trans "Create Snapshot" %}{% endblock %}
@ -22,5 +22,5 @@
{% block modal-footer %}
<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 %}

View File

@ -7,5 +7,5 @@
{% endblock page_header %}
{% block main %}
{% include 'project/images_and_snapshots/snapshots/_create.html' %}
{% include 'project/images/snapshots/_create.html' %}
{% endblock %}

View File

@ -27,32 +27,23 @@ from mox import IsA # noqa
from horizon import exceptions
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.images_and_snapshots import utils
from openstack_dashboard.dashboards.project.images import utils
from openstack_dashboard.test import helpers as test
INDEX_URL = reverse('horizon:project:images_and_snapshots:index')
INDEX_URL = reverse('horizon:project:images:index')
class ImagesAndSnapshotsTests(test.TestCase):
@test.create_stubs({api.glance: ('image_list_detailed',),
api.cinder: ('volume_snapshot_list',
'volume_list',)})
@test.create_stubs({api.glance: ('image_list_detailed',)})
def test_index(self):
images = self.images.list()
vol_snaps = self.volume_snapshots.list()
volumes = self.volumes.list()
api.cinder.volume_snapshot_list(IsA(http.HttpRequest)) \
.AndReturn(vol_snaps)
api.cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(volumes)
api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn([images, False])
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, 'project/images_and_snapshots/index.html')
self.assertTemplateUsed(res, 'project/images/index.html')
self.assertIn('images_table', res.context)
images_table = res.context['images_table']
images = images_table.data
@ -67,61 +58,34 @@ class ImagesAndSnapshotsTests(test.TestCase):
row_actions = images_table.get_row_actions(images[2])
self.assertTrue(len(row_actions), 3)
@test.create_stubs({api.glance: ('image_list_detailed',),
api.cinder: ('volume_snapshot_list',
'volume_list',)})
@test.create_stubs({api.glance: ('image_list_detailed',)})
def test_index_no_images(self):
vol_snaps = self.volume_snapshots.list()
volumes = self.volumes.list()
api.cinder.volume_snapshot_list(IsA(http.HttpRequest)) \
.AndReturn(vol_snaps)
api.cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(volumes)
api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn([(), False])
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, 'project/images_and_snapshots/index.html')
self.assertTemplateUsed(res, 'project/images/index.html')
@test.create_stubs({api.glance: ('image_list_detailed',),
api.cinder: ('volume_snapshot_list',
'volume_list',)})
@test.create_stubs({api.glance: ('image_list_detailed',)})
def test_index_error(self):
vol_snaps = self.volume_snapshots.list()
volumes = self.volumes.list()
api.cinder.volume_snapshot_list(IsA(http.HttpRequest)) \
.AndReturn(vol_snaps)
api.cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(volumes)
api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None) \
.AndRaise(self.exceptions.glance)
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, 'project/images_and_snapshots/index.html')
self.assertTemplateUsed(res, 'project/images/index.html')
@test.create_stubs({api.glance: ('image_list_detailed',),
api.cinder: ('volume_snapshot_list',
'volume_list',)})
@test.create_stubs({api.glance: ('image_list_detailed',)})
def test_snapshot_actions(self):
snapshots = self.snapshots.list()
vol_snaps = self.volume_snapshots.list()
volumes = self.volumes.list()
api.cinder.volume_snapshot_list(IsA(http.HttpRequest)) \
.AndReturn(vol_snaps)
api.cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(volumes)
api.glance.image_list_detailed(IsA(http.HttpRequest), marker=None) \
.AndReturn([snapshots, False])
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, 'project/images_and_snapshots/index.html')
self.assertTemplateUsed(res, 'project/images/index.html')
self.assertIn('images_table', res.context)
snaps = res.context['images_table']
self.assertEqual(len(snaps.get_rows()), 3)

View File

@ -22,18 +22,15 @@ from django.conf.urls import include # noqa
from django.conf.urls import patterns # noqa
from django.conf.urls import url # noqa
from openstack_dashboard.dashboards.project.images_and_snapshots.images \
from openstack_dashboard.dashboards.project.images.images \
import urls as image_urls
from openstack_dashboard.dashboards.project.images_and_snapshots.snapshots \
from openstack_dashboard.dashboards.project.images.snapshots \
import urls as snapshot_urls
from openstack_dashboard.dashboards.project.images_and_snapshots import views
from openstack_dashboard.dashboards.project.images import views
urlpatterns = patterns('',
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'', include(image_urls, namespace='images')),
url(r'', include(snapshot_urls, namespace='snapshots')),
url(r'^snapshots/(?P<snapshot_id>[^/]+)/$',
views.DetailView.as_view(),
name='detail'),
)

View 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

View File

@ -1,16 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Images &amp; Snapshots" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Images &amp; Snapshots") %}
{% endblock page_header %}
{% block main %}
<div class="images">
{{ images_table.render }}
</div>
<div class="volume_snapshots">
{{ volume_snapshots_table.render }}
</div>
{% endblock %}

View File

@ -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)

View File

@ -27,7 +27,7 @@ from horizon.utils import fields
from horizon.utils import validators
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.images_and_snapshots import utils
from openstack_dashboard.dashboards.project.images import utils
def _image_choice_title(img):

View File

@ -290,7 +290,7 @@ class EditInstanceSecurityGroups(EditInstance):
class CreateSnapshot(tables.LinkAction):
name = "snapshot"
verbose_name = _("Create Snapshot")
url = "horizon:project:images_and_snapshots:snapshots:create"
url = "horizon:project:images:snapshots:create"
classes = ("ajax-modal", "btn-camera")
policy_rules = (("compute", "compute:snapshot"),)

View File

@ -100,7 +100,7 @@
{% with default_key_name="<em>"|add:_("None")|add:"</em>" %}
<dd>{{ instance.key_name|default:default_key_name }}</dd>
{% 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>
<dd><a href="{{ image_url }}">{{ instance.image_name }}</a></dd>
{% with default_item_value="<em>"|add:_("N/A")|add:"</em>" %}

View File

@ -855,8 +855,6 @@ class InstanceTests(test.TestCase):
'server_list',
'flavor_list',
'server_delete'),
cinder: ('volume_snapshot_list',
'volume_list',),
api.glance: ('image_list_detailed',)})
def test_create_instance_snapshot(self):
server = self.servers.first()
@ -868,17 +866,15 @@ class InstanceTests(test.TestCase):
api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn([[], False])
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
cinder.volume_list(IsA(http.HttpRequest)).AndReturn([])
self.mox.ReplayAll()
formData = {'instance_id': server.id,
'method': 'CreateSnapshot',
'name': 'snapshot1'}
url = reverse('horizon:project:images_and_snapshots:snapshots:create',
url = reverse('horizon:project:images:snapshots:create',
args=[server.id])
redir_url = reverse('horizon:project:images_and_snapshots:index')
redir_url = reverse('horizon:project:images:index')
res = self.client.post(url, formData)
self.assertRedirects(res, redir_url)

View File

@ -40,7 +40,7 @@ from openstack_dashboard.api import base
from openstack_dashboard.api import cinder
from openstack_dashboard.usage import quotas
from openstack_dashboard.dashboards.project.images_and_snapshots import utils
from openstack_dashboard.dashboards.project.images import utils
LOG = logging.getLogger(__name__)

View File

@ -27,7 +27,7 @@ from openstack_dashboard.api import base
from openstack_dashboard.api import cinder
from openstack_dashboard.dashboards.project.volumes \
import tables as volume_tables
.volumes import tables as volume_tables
class DeleteVolumeSnapshot(tables.DeleteAction):
@ -51,7 +51,7 @@ class DeleteVolumeSnapshot(tables.DeleteAction):
class CreateVolumeFromSnapshot(tables.LinkAction):
name = "create_from_snapshot"
verbose_name = _("Create Volume")
url = "horizon:project:volumes:create"
url = "horizon:project:volumes:volumes:create"
classes = ("ajax-modal", "btn-camera")
policy_rules = (("volume", "volume:create"),)
@ -95,10 +95,11 @@ class SnapshotVolumeNameColumn(tables.Column):
class VolumeSnapshotsTable(volume_tables.VolumesTableBase):
name = tables.Column("display_name",
verbose_name=_("Name"),
link="horizon:project:images_and_snapshots:detail")
volume_name = SnapshotVolumeNameColumn("display_name",
verbose_name=_("Volume Name"),
link="horizon:project:volumes:detail")
link="horizon:project:volumes:detail")
volume_name = SnapshotVolumeNameColumn(
"display_name",
verbose_name=_("Volume Name"),
link="horizon:project:volumes:volumes:detail")
class Meta:
name = "volume_snapshots"

View File

@ -26,15 +26,14 @@ from openstack_dashboard.api import cinder
class OverviewTab(tabs.Tab):
name = _("Overview")
slug = "overview"
template_name = ("project/images_and_snapshots/snapshots/"
"_detail_overview.html")
template_name = ("project/volumes/snapshots/_detail_overview.html")
def get_context_data(self, request):
try:
snapshot = self.tab_group.kwargs['snapshot']
volume = cinder.volume_get(request, snapshot.volume_id)
except Exception:
redirect = reverse('horizon:project:images_and_snapshots:index')
redirect = reverse('horizon:project:volumes:index')
exceptions.handle(self.request,
_('Unable to retrieve snapshot details.'),
redirect=redirect)

View File

@ -28,7 +28,7 @@ from openstack_dashboard.test import helpers as test
from openstack_dashboard.usage import quotas
INDEX_URL = reverse('horizon:project:images_and_snapshots:index')
INDEX_URL = reverse('horizon:project:volumes:index')
class VolumeSnapshotsViewTests(test.TestCase):
@ -46,11 +46,12 @@ class VolumeSnapshotsViewTests(test.TestCase):
AndReturn(usage_limit)
self.mox.ReplayAll()
url = reverse('horizon:project:volumes:create_snapshot',
args=[volume.id])
url = reverse('horizon:project:volumes:'
'volumes:create_snapshot', args=[volume.id])
res = self.client.get(url)
self.assertTemplateUsed(res, 'project/volumes/create_snapshot.html')
self.assertTemplateUsed(res, 'project/volumes/volumes/'
'create_snapshot.html')
@test.create_stubs({cinder: ('volume_get',
'volume_snapshot_create',)})
@ -73,7 +74,7 @@ class VolumeSnapshotsViewTests(test.TestCase):
'volume_id': volume.id,
'name': snapshot.display_name,
'description': snapshot.display_description}
url = reverse('horizon:project:volumes:create_snapshot',
url = reverse('horizon:project:volumes:volumes:create_snapshot',
args=[volume.id])
res = self.client.post(url, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@ -99,12 +100,12 @@ class VolumeSnapshotsViewTests(test.TestCase):
'volume_id': volume.id,
'name': snapshot.display_name,
'description': snapshot.display_description}
url = reverse('horizon:project:volumes:create_snapshot',
url = reverse('horizon:project:volumes:volumes:create_snapshot',
args=[volume.id])
res = self.client.post(url, formData)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.glance: ('image_list_detailed',),
@test.create_stubs({api.nova: ('server_list',),
api.cinder: ('volume_snapshot_list',
'volume_list',
'volume_snapshot_delete')})
@ -113,20 +114,20 @@ class VolumeSnapshotsViewTests(test.TestCase):
volumes = self.volumes.list()
snapshot = self.volume_snapshots.first()
api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn(([], False))
api.cinder.volume_snapshot_list(IsA(http.HttpRequest)). \
AndReturn(vol_snapshots)
api.cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(volumes)
api.cinder.volume_list(IsA(http.HttpRequest)). \
AndReturn(volumes)
api.cinder.volume_snapshot_delete(IsA(http.HttpRequest), snapshot.id)
api.glance.image_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn(([], False))
api.cinder.volume_list(IsA(http.HttpRequest), search_opts=None). \
AndReturn(volumes)
api.nova.server_list(IsA(http.HttpRequest), search_opts=None). \
AndReturn([self.servers.list(), False])
api.cinder.volume_snapshot_list(IsA(http.HttpRequest)). \
AndReturn([])
api.cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(volumes)
api.cinder.volume_list(IsA(http.HttpRequest)). \
AndReturn(volumes)
self.mox.ReplayAll()
formData = {'action':
@ -148,7 +149,7 @@ class VolumeSnapshotsViewTests(test.TestCase):
self.mox.ReplayAll()
url = reverse('horizon:project:images_and_snapshots:detail',
url = reverse('horizon:project:volumes:detail',
args=[snapshot.id])
res = self.client.get(url)
@ -172,7 +173,7 @@ class VolumeSnapshotsViewTests(test.TestCase):
AndRaise(self.exceptions.cinder)
self.mox.ReplayAll()
url = reverse('horizon:project:images_and_snapshots:detail',
url = reverse('horizon:project:volumes:detail',
args=[snapshot.id])
res = self.client.get(url)
@ -191,7 +192,7 @@ class VolumeSnapshotsViewTests(test.TestCase):
self.mox.ReplayAll()
url = reverse('horizon:project:images_and_snapshots:detail',
url = reverse('horizon:project:volumes:detail',
args=[snapshot.id])
res = self.client.get(url)

View File

@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, Inc.
# Copyright 2013 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -14,21 +14,98 @@
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.datastructures import SortedDict
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from openstack_dashboard import api
class OverviewTab(tabs.Tab):
name = _("Overview")
slug = "overview"
template_name = ("project/volumes/"
"_detail_overview.html")
def get_context_data(self, request):
return {"volume": self.tab_group.kwargs['volume']}
from openstack_dashboard.dashboards.project.volumes.snapshots \
import tables as vol_snapshot_tables
from openstack_dashboard.dashboards.project.volumes.volumes \
import tables as volume_tables
class VolumeDetailTabs(tabs.TabGroup):
slug = "volume_details"
tabs = (OverviewTab,)
class VolumeTableMixIn(object):
def _get_volumes(self, search_opts=None):