Merge pull request #141 from cloudbuilders/owner-editable-images

Owner editable images
This commit is contained in:
Devin Carlen 2011-10-02 15:38:57 -07:00
commit c0fdb7b704
8 changed files with 195 additions and 20 deletions

View File

@ -141,7 +141,7 @@ class Image(APIDictWrapper):
"""Simple wrapper around glance image dictionary"""
_attrs = ['checksum', 'container_format', 'created_at', 'deleted',
'deleted_at', 'disk_format', 'id', 'is_public', 'location',
'name', 'properties', 'size', 'status', 'updated_at']
'name', 'properties', 'size', 'status', 'updated_at', 'owner']
def __getattr__(self, attrname):
if attrname == "properties":

View File

@ -50,6 +50,7 @@ urlpatterns += patterns('django_openstack.dash.views.security_groups',
urlpatterns += patterns('django_openstack.dash.views.images',
url(r'^(?P<tenant_id>[^/]+)/images/$', 'index', name='dash_images'),
url(IMAGES % 'launch', 'launch', name='dash_images_launch'),
url(IMAGES % 'update', 'update', name='dash_images_update'),
)
urlpatterns += patterns('django_openstack.dash.views.keypairs',

View File

@ -22,19 +22,14 @@
Views for managing Nova images.
"""
import datetime
import logging
import re
from django import http
from django import template
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect, render_to_response
from django.utils.text import normalize_newlines
from django.utils.translation import ugettext as _
from django import shortcuts
from django_openstack import api
from django_openstack import forms
@ -46,6 +41,71 @@ from novaclient import exceptions as novaclient_exceptions
LOG = logging.getLogger('django_openstack.dash.views.images')
class UpdateImageForm(forms.SelfHandlingForm):
image_id = forms.CharField(widget=forms.HiddenInput())
name = forms.CharField(max_length="25", label="Name")
kernel = forms.CharField(max_length="25", label="Kernel ID",
required=False)
ramdisk = forms.CharField(max_length="25", label="Ramdisk ID",
required=False)
architecture = forms.CharField(label="Architecture", required=False)
container_format = forms.CharField(label="Container Format",
required=False)
disk_format = forms.CharField(label="Disk Format")
def handle(self, request, data):
image_id = data['image_id']
tenant_id = request.user.tenant_id
error_retrieving = _('Unable to retreive image info from glance: %s' % image_id)
error_updating = _('Error updating image with id: %s' % image_id)
try:
image = api.image_get(request, image_id)
except glance_exception.ClientConnectionError, e:
LOG.exception(_('Error connecting to glance'))
messages.error(request, error_retrieving)
except glance_exception.Error, e:
LOG.exception(error_retrieving)
messages.error(request, error_retrieving)
if image.owner == request.user.username:
try:
meta = {
'is_public': True,
'disk_format': data['disk_format'],
'container_format': data['container_format'],
'name': data['name'],
}
# TODO add public flag to properties
meta['properties'] = {}
if data['kernel']:
meta['properties']['kernel_id'] = data['kernel']
if data['ramdisk']:
meta['properties']['ramdisk_id'] = data['ramdisk']
if data['architecture']:
meta['properties']['architecture'] = data['architecture']
api.image_update(request, image_id, meta)
messages.success(request, _('Image was successfully updated.'))
except glance_exception.ClientConnectionError, e:
LOG.exception(_('Error connecting to glance'))
messages.error(request, error_retrieving)
except glance_exception.Error, e:
LOG.exception(error_updating)
messages.error(request, error_updating)
except:
LOG.exception(_('Unspecified Exception in image update'))
messages.error(request, error_updating)
return redirect('dash_images_update', tenant_id, image_id)
else:
messages.info(request, _('Unable to update image. You are not its \
owner.'))
return redirect('dash_images_update', tenant_id, image_id)
class LaunchForm(forms.SelfHandlingForm):
name = forms.CharField(max_length=80, label="Server Name")
image_id = forms.CharField(widget=forms.HiddenInput())
@ -70,13 +130,16 @@ class LaunchForm(forms.SelfHandlingForm):
required=False,
help_text="Which keypair to use for authentication")
securitygrouplist = kwargs.get('initial', {}).get('securitygrouplist', [])
self.fields['security_groups'] = forms.MultipleChoiceField(choices=securitygrouplist,
securitygrouplist = kwargs.get('initial', {}).get(
'securitygrouplist', [])
self.fields['security_groups'] = forms.MultipleChoiceField(
choices=securitygrouplist,
label='Security Groups',
required=True,
initial=['default'],
widget=forms.SelectMultiple(attrs={'class': 'chzn-select',
'style': "min-width: 200px"}),
widget=forms.SelectMultiple(
attrs={'class': 'chzn-select',
'style': "min-width: 200px"}),
help_text="Launch instance in these Security Groups")
# setting self.fields.keyOrder seems to break validation,
# so ordering fields manually
@ -108,22 +171,51 @@ class LaunchForm(forms.SelfHandlingForm):
return redirect('dash_instances', tenant_id)
except api_exceptions.ApiException, e:
LOG.exception('ApiException while creating instances of image "%s"' %
image_id)
LOG.exception('ApiException while creating instances of image "%s"'
% image_id)
messages.error(request,
'Unable to launch instance: %s' % e.message)
class DeleteImage(forms.SelfHandlingForm):
image_id = forms.CharField(required=True)
def handle(self, request, data):
image_id = data['image_id']
tenant_id = request.user.tenant_id
try:
image = api.image_get(request, image_id)
if image.owner == request.user.username:
api.image_delete(request, image_id)
else:
messages.info(request, "Unable to delete image, you are not \
its owner.")
return redirect('dash_images_update', tenant_id, image_id)
except glance_exception.ClientConnectionError, e:
LOG.exception("Error connecting to glance")
messages.error(request, "Error connecting to glance: %s"
% e.message)
except glance_exception.Error, e:
LOG.exception('Error deleting image with id "%s"' % image_id)
messages.error(request, "Error deleting image: %s: %s"
% (image_id, e.message))
return redirect(request.build_absolute_uri())
@login_required
def index(request, tenant_id):
tenant = {}
for f in (DeleteImage, ):
unused, handled = f.maybe_handle(request)
if handled:
return handled
delete_form = DeleteImage()
tenant = {}
try:
tenant = api.token_get_tenant(request, request.user.tenant_id)
except api_exceptions.ApiException, e:
messages.error(request, "Unable to retrienve tenant info\
from keystone: %s" % e.message)
all_images = []
try:
all_images = api.image_list_detailed(request)
@ -145,6 +237,7 @@ def index(request, tenant_id):
return render_to_response(
'django_openstack/dash/images/index.html', {
'delete_form': delete_form,
'tenant': tenant,
'images': images,
}, context_instance=template.RequestContext(request))
@ -152,6 +245,7 @@ def index(request, tenant_id):
@login_required
def launch(request, tenant_id, image_id):
def flavorlist():
try:
fl = api.flavor_list(request)
@ -210,3 +304,32 @@ def launch(request, tenant_id, image_id):
'form': form,
'quotas': quotas,
}, context_instance=template.RequestContext(request))
@login_required
def update(request, tenant_id, image_id):
try:
image = api.image_get(request, image_id)
except glance_exception.ClientConnectionError, e:
LOG.exception("Error connecting to glance")
messages.error(request, "Error connecting to glance: %s"
% e.message)
except glance_exception.Error, e:
LOG.exception('Error retrieving image with id "%s"' % image_id)
messages.error(request, "Error retrieving image %s: %s"
% (image_id, e.message))
form, handled = UpdateImageForm().maybe_handle(request, initial={
'image_id': image_id,
'name': image.get('name', ''),
'kernel': image['properties'].get('kernel_id', ''),
'ramdisk': image['properties'].get('ramdisk_id', ''),
'architecture': image['properties'].get('architecture', ''),
'container_format': image.get('container_format', ''),
'disk_format': image.get('disk_format', ''), })
if handled:
return handled
return render_to_response('django_openstack/dash/images/update.html', {
'form': form,
}, context_instance=template.RequestContext(request))

View File

@ -0,0 +1,8 @@
<form id="form_delete_{{image.id}}" class="form-delete" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{hidden}}
{% endfor %}
<input name="image_id" type="hidden" value="{{image.id}}" />
<input id="delete_{{image.id}}" class="delete" title="Image: {{image.name}}" type="submit" value="Delete" />
</form>

View File

@ -0,0 +1,10 @@
<form id="image_form" action="" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
{% for field in form.visible_fields %}
{{ field.label_tag }}
{{ field.errors }}
{{ field }}
{% endfor %}
<input type="submit" value="Update Image" class="large-rounded" />
</form>

View File

@ -14,11 +14,14 @@
<td>{{image.created_at|parse_date}}</td>
<td>{{image.updated_at|parse_date}}</td>
<td>{{image.status|capfirst}}</td>
<td id="actions">
<ul>
<li><a id="launch_{{image.id}}" class="launch" href="{% url dash_images_launch request.user.tenant_id image.id %}">Launch</a></li>
</ul>
</td>
{% if image.owner == request.user.username %}
<td id="actions">
<ul>
<li class="form">{% include "django_openstack/dash/images/_delete.html" with form=delete_form %}</li>
<li><a href="{% url dash_images_update request.user.tenant_id image.id %}">Edit</a></li>
</ul>
</td>
{% endif %}
</tr>
{% endfor %}
</table>

View File

@ -16,5 +16,10 @@
{% if images %}
{% include 'django_openstack/dash/images/_list.html' %}
{% endif %}
{% else %}
<div class="message_box info">
<h2>Info</h2>
<p>There are currently no images.</p>
</div>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,25 @@
{% extends 'django_openstack/dash/base.html' %}
{% block sidebar %}
{% with current_sidebar="images" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block page_header %}
{% include "django_openstack/common/_page_header.html" with title="Update Image" %}
{% endblock page_header %}
{% block dash_main %}
<div class="dash_block">
<div class="left">
{% include 'django_openstack/dash/images/_form.html' %}
</div>
<div class="right">
<h3>Description:</h3>
<p>From here you can modify different properties of an image.</p>
</div>
<div class="clear">&nbsp;</div>
</div>
{% endblock %}