diff --git a/horizon/horizon/dashboards/nova/access_and_security/floating_ips/tests.py b/horizon/horizon/dashboards/nova/access_and_security/floating_ips/tests.py index d90a25451..dd94e96f6 100644 --- a/horizon/horizon/dashboards/nova/access_and_security/floating_ips/tests.py +++ b/horizon/horizon/dashboards/nova/access_and_security/floating_ips/tests.py @@ -97,9 +97,6 @@ class FloatingIpViewTests(test.BaseViewTests): api.server_add_floating_ip(IsA(http.HttpRequest), IsA(unicode), IsA(unicode)).\ AndReturn(None) - self.mox.StubOutWithMock(messages, 'info') - messages.info(IsA(http.HttpRequest), IsA(unicode)) - self.mox.StubOutWithMock(api, 'tenant_floating_ip_get') api.tenant_floating_ip_get = self.mox.CreateMockAnything() api.tenant_floating_ip_get(IsA(http.HttpRequest), str(1)).\ @@ -129,8 +126,8 @@ class FloatingIpViewTests(test.BaseViewTests): self.mox.StubOutWithMock(api, 'security_group_list') api.security_group_list(IsA(http.HttpRequest)).\ AndReturn(self.security_groups) - self.mox.StubOutWithMock(api, 'keypair_list') - api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs) + self.mox.StubOutWithMock(api.nova, 'keypair_list') + api.nova.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs) self.mox.StubOutWithMock(api, 'server_add_floating_ip') api.server_add_floating_ip = self.mox.CreateMockAnything() @@ -140,9 +137,6 @@ class FloatingIpViewTests(test.BaseViewTests): IsA(unicode)).\ AndRaise(exception) - self.mox.StubOutWithMock(messages, 'error') - messages.error(IsA(http.HttpRequest), IsA(basestring)) - self.mox.StubOutWithMock(api, 'tenant_floating_ip_get') api.tenant_floating_ip_get = self.mox.CreateMockAnything() api.tenant_floating_ip_get(IsA(http.HttpRequest), IsA(unicode)).\ @@ -174,8 +168,8 @@ class FloatingIpViewTests(test.BaseViewTests): self.mox.StubOutWithMock(api, 'security_group_list') api.security_group_list(IsA(http.HttpRequest)).\ AndReturn(self.security_groups) - self.mox.StubOutWithMock(api, 'keypair_list') - api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs) + self.mox.StubOutWithMock(api.nova, 'keypair_list') + api.nova.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs) self.mox.StubOutWithMock(api, 'server_remove_floating_ip') api.server_remove_floating_ip = self.mox.CreateMockAnything() @@ -204,8 +198,8 @@ class FloatingIpViewTests(test.BaseViewTests): self.mox.StubOutWithMock(api, 'security_group_list') api.security_group_list(IsA(http.HttpRequest)).\ AndReturn(self.security_groups) - self.mox.StubOutWithMock(api, 'keypair_list') - api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs) + self.mox.StubOutWithMock(api.nova, 'keypair_list') + api.nova.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs) self.mox.StubOutWithMock(api, 'server_remove_floating_ip') exception = novaclient_exceptions.ClientException('ClientException', @@ -213,9 +207,6 @@ class FloatingIpViewTests(test.BaseViewTests): api.server_remove_floating_ip(IsA(http.HttpRequest), IsA(int), IsA(int)).AndRaise(exception) - self.mox.StubOutWithMock(messages, 'error') - messages.error(IsA(http.HttpRequest), IsA(basestring)) - self.mox.StubOutWithMock(api, 'tenant_floating_ip_get') api.tenant_floating_ip_get = self.mox.CreateMockAnything() api.tenant_floating_ip_get(IsA(http.HttpRequest), IsA(unicode)).\ diff --git a/horizon/horizon/dashboards/nova/access_and_security/keypairs/tables.py b/horizon/horizon/dashboards/nova/access_and_security/keypairs/tables.py new file mode 100644 index 000000000..da31eae60 --- /dev/null +++ b/horizon/horizon/dashboards/nova/access_and_security/keypairs/tables.py @@ -0,0 +1,81 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 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. + +import logging + +from django import shortcuts +from django.contrib import messages +from django.core.urlresolvers import reverse + +from horizon import api +from horizon import tables + + +LOG = logging.getLogger(__name__) + + +class DeleteKeyPairs(tables.Action): + name = "delete" + verbose_name = _("Delete") + verbose_name_plural = _("Delete Keypairs") + classes = ("danger",) + + def handle(self, data_table, request, object_ids): + failures = 0 + deleted = [] + for obj_id in object_ids: + try: + api.nova.keypair_delete(request, obj_id) + deleted.append(obj_id) + except Exception, e: + failures += 1 + messages.error(request, _("Error deleting keypair: %s") % e) + LOG.exception("Error deleting keypair.") + if failures: + messages.info(request, _("Deleted the following keypairs: %s") + % ", ".join(deleted)) + else: + messages.success(request, _("Successfully deleted keypairs: %s") + % ", ".join(deleted)) + return shortcuts.redirect('horizon:nova:access_and_security:index') + + +class ImportKeyPair(tables.LinkAction): + name = "import" + verbose_name = _("Import Keypair") + url = "horizon:nova:access_and_security:keypairs:import" + attrs = {"class": "ajax-modal btn"} + + +class CreateKeyPair(tables.LinkAction): + name = "create" + verbose_name = _("Create Keypair") + url = "horizon:nova:access_and_security:keypairs:create" + attrs = {"class": "ajax-modal btn"} + + +class KeypairsTable(tables.DataTable): + name = tables.Column("name") + fingerprint = tables.Column("fingerprint") + + def get_object_id(self, keypair): + return keypair.name + + class Meta: + name = "keypairs" + verbose_name = _("Keypairs") + table_actions = (CreateKeyPair, ImportKeyPair, DeleteKeyPairs,) + row_actions = (DeleteKeyPairs,) diff --git a/horizon/horizon/dashboards/nova/access_and_security/keypairs/tests.py b/horizon/horizon/dashboards/nova/access_and_security/keypairs/tests.py index 53bf97d90..5a9e7b203 100644 --- a/horizon/horizon/dashboards/nova/access_and_security/keypairs/tests.py +++ b/horizon/horizon/dashboards/nova/access_and_security/keypairs/tests.py @@ -28,6 +28,9 @@ from horizon import api from horizon import test +INDEX_VIEW_URL = reverse('horizon:nova:access_and_security:index') + + class KeyPairViewTests(test.BaseViewTests): def setUp(self): super(KeyPairViewTests, self).setUp() @@ -38,20 +41,16 @@ class KeyPairViewTests(test.BaseViewTests): def test_delete_keypair(self): KEYPAIR_ID = self.keypairs[0].name formData = {'method': 'DeleteKeypair', - 'keypair_id': KEYPAIR_ID, - } + 'keypair_id': KEYPAIR_ID} self.mox.StubOutWithMock(api, 'keypair_delete') api.keypair_delete(IsA(http.HttpRequest), unicode(KEYPAIR_ID)) self.mox.ReplayAll() - res = self.client.post( - reverse('horizon:nova:access_and_security:index'), - formData) + res = self.client.post(INDEX_VIEW_URL, formData) - self.assertRedirectsNoFollow(res, - reverse('horizon:nova:access_and_security:index')) + self.assertRedirectsNoFollow(res, INDEX_VIEW_URL) def test_delete_keypair_exception(self): KEYPAIR_ID = self.keypairs[0].name @@ -67,12 +66,9 @@ class KeyPairViewTests(test.BaseViewTests): self.mox.ReplayAll() - res = self.client.post( - reverse('horizon:nova:access_and_security:index'), - formData) + res = self.client.post(INDEX_VIEW_URL, formData) - self.assertRedirectsNoFollow(res, - reverse('horizon:nova:access_and_security:index')) + self.assertRedirectsNoFollow(res, INDEX_VIEW_URL) def test_create_keypair_get(self): res = self.client.get( diff --git a/horizon/horizon/dashboards/nova/access_and_security/keypairs/urls.py b/horizon/horizon/dashboards/nova/access_and_security/keypairs/urls.py index edf5a076b..67ea09f65 100644 --- a/horizon/horizon/dashboards/nova/access_and_security/keypairs/urls.py +++ b/horizon/horizon/dashboards/nova/access_and_security/keypairs/urls.py @@ -20,10 +20,12 @@ from django.conf.urls.defaults import patterns, url +from .views import IndexView, CreateView, ImportView + urlpatterns = patterns( 'horizon.dashboards.nova.access_and_security.keypairs.views', - url(r'^$', 'index', name='index'), - url(r'^create/$', 'create', name='create'), - url(r'^import/$', 'import_keypair', name='import'), + url(r'^$', IndexView.as_view(), name='index'), + url(r'^create/$', CreateView.as_view(), name='create'), + url(r'^import/$', ImportView.as_view(), name='import'), ) diff --git a/horizon/horizon/dashboards/nova/access_and_security/keypairs/views.py b/horizon/horizon/dashboards/nova/access_and_security/keypairs/views.py index 72ed6a47f..8bb12cbdc 100644 --- a/horizon/horizon/dashboards/nova/access_and_security/keypairs/views.py +++ b/horizon/horizon/dashboards/nova/access_and_security/keypairs/views.py @@ -31,69 +31,35 @@ from django.utils.translation import ugettext as _ from novaclient import exceptions as novaclient_exceptions from horizon import api -from horizon.dashboards.nova.access_and_security.keypairs.forms import \ - (CreateKeypair, DeleteKeypair, ImportKeypair) +from horizon import forms +from horizon import tables +from .forms import CreateKeypair, DeleteKeypair, ImportKeypair +from .tables import KeypairsTable LOG = logging.getLogger(__name__) -# FIXME(gabriel): There's a very obvious pattern to these views. -# This is a perfect candidate for a class-based view. +class IndexView(tables.DataTableView): + table_class = KeypairsTable + template_name = 'nova/access_and_security/keypairs/index.html' -@login_required -def index(request): - delete_form, handled = DeleteKeypair.maybe_handle(request) - if handled: - return handled - - try: - keypairs = api.keypair_list(request) - except novaclient_exceptions.ClientException, e: - keypairs = [] - LOG.exception("ClientException in keypair index") - messages.error(request, _('Error fetching keypairs: %s') % e.message) - - context = {'keypairs': keypairs, 'delete_form': delete_form} - - if request.is_ajax(): - template = 'nova/access_and_security/keypairs/_list.html' - context['hide'] = True - else: - template = 'nova/access_and_security/keypairs/index.html' - - return shortcuts.render(request, template, context) + def get_data(self): + try: + keypairs = api.nova.keypair_list(self.request) + except Exception, e: + keypairs = [] + LOG.exception("ClientException in keypair index") + messages.error(request, + _('Error fetching keypairs: %s') % e.message) + return keypairs -@login_required -def create(request): - form, handled = CreateKeypair.maybe_handle(request) - if handled: - return handled - - context = {'form': form} - - if request.is_ajax(): - template = 'nova/access_and_security/keypairs/_create.html' - context['hide'] = True - else: - template = 'nova/access_and_security/keypairs/create.html' - - return shortcuts.render(request, template, context) +class CreateView(forms.ModalFormView): + form_class = CreateKeypair + template_name = 'nova/access_and_security/keypairs/create.html' -@login_required -def import_keypair(request): - form, handled = ImportKeypair.maybe_handle(request) - if handled: - return handled - - context = {'form': form} - - if request.is_ajax(): - template = 'nova/access_and_security/keypairs/_import.html' - context['hide'] = True - else: - template = 'nova/access_and_security/keypairs/import.html' - - return shortcuts.render(request, template, context) +class ImportView(forms.ModalFormView): + form_class = ImportKeypair + template_name = 'nova/access_and_security/keypairs/import.html' diff --git a/horizon/horizon/dashboards/nova/access_and_security/tests.py b/horizon/horizon/dashboards/nova/access_and_security/tests.py index 4011bf9f6..deaaa8665 100644 --- a/horizon/horizon/dashboards/nova/access_and_security/tests.py +++ b/horizon/horizon/dashboards/nova/access_and_security/tests.py @@ -48,7 +48,7 @@ class AccessAndSecurityTests(test.BaseViewTests): floating_ip.ip = '58.58.58.58' self.floating_ip = floating_ip - self.floating_ips = [floating_ip, ] + self.floating_ips = (floating_ip,) security_group = api.SecurityGroup(None) security_group.id = '1' @@ -57,13 +57,14 @@ class AccessAndSecurityTests(test.BaseViewTests): def test_index(self): self.mox.StubOutWithMock(api, 'tenant_floating_ip_list') + self.mox.StubOutWithMock(api, 'security_group_list') + self.mox.StubOutWithMock(api.nova, 'keypair_list') + + api.nova.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs) api.tenant_floating_ip_list(IsA(http.HttpRequest)).\ AndReturn(self.floating_ips) - self.mox.StubOutWithMock(api, 'security_group_list') api.security_group_list(IsA(http.HttpRequest)).\ AndReturn(self.security_groups) - self.mox.StubOutWithMock(api, 'keypair_list') - api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs) self.mox.ReplayAll() @@ -71,7 +72,8 @@ class AccessAndSecurityTests(test.BaseViewTests): reverse('horizon:nova:access_and_security:index')) self.assertTemplateUsed(res, 'nova/access_and_security/index.html') - self.assertItemsEqual(res.context['keypairs'], self.keypairs) + self.assertItemsEqual(res.context['keypairs_table'].data, + self.keypairs) self.assertItemsEqual(res.context['security_groups'], self.security_groups) self.assertItemsEqual(res.context['floating_ips'], self.floating_ips) diff --git a/horizon/horizon/dashboards/nova/access_and_security/views.py b/horizon/horizon/dashboards/nova/access_and_security/views.py index 3e93580fb..6ce161a68 100644 --- a/horizon/horizon/dashboards/nova/access_and_security/views.py +++ b/horizon/horizon/dashboards/nova/access_and_security/views.py @@ -36,15 +36,11 @@ import openstackx.api.exceptions as api_exceptions from horizon import api from horizon import forms from horizon import test -from horizon.dashboards.nova.access_and_security.keypairs.forms import \ - (DeleteKeypair) -from horizon.dashboards.nova.access_and_security.security_groups.forms import \ - (CreateGroup, - DeleteGroup) -from horizon.dashboards.nova.access_and_security.floating_ips.forms import \ - (ReleaseFloatingIp, - FloatingIpDisassociate, - FloatingIpAllocate) +from .keypairs.forms import DeleteKeypair +from .keypairs.tables import KeypairsTable +from .security_groups.forms import CreateGroup, DeleteGroup +from .floating_ips.forms import (ReleaseFloatingIp, FloatingIpDisassociate, + FloatingIpAllocate) LOG = logging.getLogger(__name__) @@ -53,12 +49,27 @@ LOG = logging.getLogger(__name__) @login_required def index(request): tenant_id = request.user.tenant_id + for f in (CreateGroup, DeleteGroup, DeleteKeypair, ReleaseFloatingIp, FloatingIpDisassociate, FloatingIpAllocate): _unused, handled = f.maybe_handle(request) if handled: return handled + # NOTE(gabriel): This is all temporary until all tables + # in this view are converted to DataTables. + try: + keypairs = api.nova.keypair_list(request) + except Exception, e: + keypairs = [] + LOG.exception("Exception in keypair index") + messages.error(request, + _('Keypair list is currently unavailable.')) + keypairs_table = KeypairsTable(request, keypairs) + handled = keypairs_table.maybe_handle() + if handled: + return handled + try: security_groups = api.security_group_list(request) except novaclient_exceptions.ClientException, e: @@ -73,14 +84,8 @@ def index(request): LOG.exception("ClientException in floating ip index") messages.error(request, _('Error fetching floating ips: %s') % e.message) - try: - keypairs = api.keypair_list(request) - except novaclient_exceptions.ClientException, e: - keypairs = [] - LOG.exception("ClientException in keypair index") - messages.error(request, _('Error fetching keypairs: %s') % e.message) - context = {'keypairs': keypairs, + context = {'keypairs_table': keypairs_table, 'floating_ips': floating_ips, 'security_groups': security_groups, 'keypair_delete_form': DeleteKeypair(), diff --git a/horizon/horizon/dashboards/nova/templates/nova/access_and_security/index.html b/horizon/horizon/dashboards/nova/templates/nova/access_and_security/index.html index 3b1738b80..dea0ab5fb 100644 --- a/horizon/horizon/dashboards/nova/templates/nova/access_and_security/index.html +++ b/horizon/horizon/dashboards/nova/templates/nova/access_and_security/index.html @@ -7,7 +7,7 @@ {% endblock page_header %} {% block dash_main %} - +
{% if floating_ips %} {% include 'nova/access_and_security/floating_ips/_list.html' %} @@ -34,8 +34,8 @@
- {% if keypairs %} - {% include 'nova/access_and_security/keypairs/_list.html' %} + {% if keypairs_table.data %} + {{ keypairs_table.render }} {% else %}

{% trans "Info: " %}{% trans "There are currently no keypairs." %}

diff --git a/horizon/horizon/dashboards/nova/templates/nova/access_and_security/keypairs/_list.html b/horizon/horizon/dashboards/nova/templates/nova/access_and_security/keypairs/_list.html deleted file mode 100644 index a5045794b..000000000 --- a/horizon/horizon/dashboards/nova/templates/nova/access_and_security/keypairs/_list.html +++ /dev/null @@ -1,40 +0,0 @@ -{% load i18n %} - -
-

{% trans "Keypairs" %}

- - - -
- - - - - - - - - - {% for keypair in keypairs %} - - - - - - {% endfor %} - -
{% trans "Name" %}{% trans "Fingerprint" %}{% trans "Actions" %}
{{ keypair.name }}{{ keypair.fingerprint }} -
    -
  • {% include "nova/access_and_security/keypairs/_delete.html" with form=keypair_delete_form %}
  • -
-
diff --git a/horizon/horizon/dashboards/nova/templates/nova/access_and_security/keypairs/index.html b/horizon/horizon/dashboards/nova/templates/nova/access_and_security/keypairs/index.html index 0b831172b..fbbb7cb8d 100644 --- a/horizon/horizon/dashboards/nova/templates/nova/access_and_security/keypairs/index.html +++ b/horizon/horizon/dashboards/nova/templates/nova/access_and_security/keypairs/index.html @@ -7,8 +7,8 @@ {% endblock page_header %} {% block dash_main %} - {% if keypairs %} - {% include 'nova/access_and_security/keypairs/_list.html' %} + {% if table.data %} + {{ table.render }} {% else %}

{% trans "Info: " %}{% trans "There are currently no keypairs." %}

diff --git a/openstack-dashboard/manage.py b/openstack-dashboard/manage.py old mode 100644 new mode 100755