Gabriel Hurley 753ebd673c Moved ajax updating from Action to Row.
This fixes the problem of having to "hide" the update action
in the row's actions column. Fixes bug 948397.

Additionally, added some protection for spillover between
the attrs dictionary on action instances. Fixes bug 954592.

FWIW, the code involved in the AJAX updating is largely identical,
it's just been moved from Action to Row and had few data accessors
renamed to account for the differing relation to Table and Row.
This also allowed the javascript to be significantly cleaner.

Change-Id: Ic8932a33ca6956a56c8eee09bb0a4d1f59e0ab3a
2012-03-13 17:44:07 -07:00

186 lines
5.8 KiB
Python

# 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.
import logging
from django.core.urlresolvers import reverse
from django.template.defaultfilters import title
from django.utils import safestring
from django.utils.translation import ugettext as _
from horizon import api
from horizon import tables
LOG = logging.getLogger(__name__)
URL_PREFIX = "horizon:nova:instances_and_volumes"
DELETABLE_STATES = ("available", "error")
class DeleteVolume(tables.DeleteAction):
data_type_singular = _("Volume")
data_type_plural = _("Volumes")
classes = ('btn-danger',)
def delete(self, request, obj_id):
api.volume_delete(request, obj_id)
def allowed(self, request, volume=None):
if volume:
return volume.status in DELETABLE_STATES
return True
def update(self, request, volume=None):
# TODO(gabriel): This can be removed once the Nova Volume API supports
# deleting volumes in error states.
if volume and getattr(volume, "status", None) == "error":
self.classes += ("disabled",)
self.attrs['disabled'] = 'disabled'
self.attrs['title'] = _("Volumes in error states cannot be "
"deleted via the Nova API.")
class CreateVolume(tables.LinkAction):
name = "create"
verbose_name = _("Create Volume")
url = "%s:volumes:create" % URL_PREFIX
classes = ("ajax-modal",)
class EditAttachments(tables.LinkAction):
name = "attachments"
verbose_name = _("Edit Attachments")
url = "%s:volumes:attach" % URL_PREFIX
attrs = {"class": "ajax-modal"}
def allowed(self, request, volume=None):
return volume.status in ("available", "in-use")
class CreateSnapshot(tables.LinkAction):
name = "snapshots"
verbose_name = _("Create Snapshot")
url = "%s:volumes:create_snapshot" % URL_PREFIX
attrs = {"class": "ajax-modal"}
def allowed(self, request, volume=None):
return volume.status == "available"
class UpdateRow(tables.Row):
ajax = True
@classmethod
def get_data(cls, request, volume_id):
volume = api.volume_get(request, volume_id)
return volume
def get_size(volume):
return _("%s GB") % volume.size
def get_attachment(volume):
attachments = []
link = '<a href="%(url)s">Instance %(instance)s&nbsp;' \
'<small>(%(dev)s)</small></a>'
# Filter out "empty" attachments which the client returns...
for attachment in [att for att in volume.attachments if att]:
url = reverse("%s:instances:detail" % URL_PREFIX,
args=(attachment["serverId"],))
# TODO(jake): Make "instance" the instance name
vals = {"url": url,
"instance": attachment["serverId"],
"dev": attachment["device"]}
attachments.append(link % vals)
return safestring.mark_safe(", ".join(attachments))
class VolumesTableBase(tables.DataTable):
STATUS_CHOICES = (
("in-use", True),
("available", True),
("creating", None),
("error", False),
)
name = tables.Column("displayName", verbose_name=_("Name"),
link="%s:volumes:detail" % URL_PREFIX)
description = tables.Column("displayDescription",
verbose_name=_("Description"))
size = tables.Column(get_size, verbose_name=_("Size"))
status = tables.Column("status",
filters=(title,),
verbose_name=_("Status"),
status=True,
status_choices=STATUS_CHOICES)
def get_object_display(self, obj):
return obj.displayName
class VolumesTable(VolumesTableBase):
attachments = tables.Column(get_attachment,
verbose_name=_("Attachments"))
class Meta:
name = "volumes"
verbose_name = _("Volumes")
status_columns = ["status"]
row_class = UpdateRow
table_actions = (CreateVolume, DeleteVolume,)
row_actions = (EditAttachments, CreateSnapshot, DeleteVolume)
class DetachVolume(tables.BatchAction):
name = "detach"
action_present = _("Detach")
action_past = _("Detached")
data_type_singular = _("Volume")
data_type_plural = _("Volumes")
classes = ('btn-danger',)
def action(self, request, obj_id):
instance_id = self.table.get_object_by_id(obj_id)['serverId']
api.volume_detach(request, instance_id, obj_id)
def get_success_url(self, request):
return reverse('%s:index' % URL_PREFIX)
class AttachmentsTable(tables.DataTable):
instance = tables.Column("serverId", verbose_name=_("Instance"))
device = tables.Column("device")
def get_object_id(self, obj):
return obj['id']
def get_object_display(self, obj):
vals = {"dev": obj['device'],
"instance": obj['serverId']}
return "Attachment %(dev)s on %(instance)s" % vals
def get_object_by_id(self, obj_id):
for obj in self.data:
if self.get_object_id(obj) == obj_id:
return obj
raise ValueError('No match found for the id "%s".' % obj_id)
class Meta:
name = "attachments"
table_actions = (DetachVolume,)
row_actions = (DetachVolume,)