Merge pull request #141 from cloudbuilders/owner-editable-images
Owner editable images
This commit is contained in:
commit
c0fdb7b704
@ -141,7 +141,7 @@ class Image(APIDictWrapper):
|
|||||||
"""Simple wrapper around glance image dictionary"""
|
"""Simple wrapper around glance image dictionary"""
|
||||||
_attrs = ['checksum', 'container_format', 'created_at', 'deleted',
|
_attrs = ['checksum', 'container_format', 'created_at', 'deleted',
|
||||||
'deleted_at', 'disk_format', 'id', 'is_public', 'location',
|
'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):
|
def __getattr__(self, attrname):
|
||||||
if attrname == "properties":
|
if attrname == "properties":
|
||||||
|
@ -50,6 +50,7 @@ urlpatterns += patterns('django_openstack.dash.views.security_groups',
|
|||||||
urlpatterns += patterns('django_openstack.dash.views.images',
|
urlpatterns += patterns('django_openstack.dash.views.images',
|
||||||
url(r'^(?P<tenant_id>[^/]+)/images/$', 'index', name='dash_images'),
|
url(r'^(?P<tenant_id>[^/]+)/images/$', 'index', name='dash_images'),
|
||||||
url(IMAGES % 'launch', 'launch', name='dash_images_launch'),
|
url(IMAGES % 'launch', 'launch', name='dash_images_launch'),
|
||||||
|
url(IMAGES % 'update', 'update', name='dash_images_update'),
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns += patterns('django_openstack.dash.views.keypairs',
|
urlpatterns += patterns('django_openstack.dash.views.keypairs',
|
||||||
|
@ -22,19 +22,14 @@
|
|||||||
Views for managing Nova images.
|
Views for managing Nova images.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import datetime
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
|
||||||
|
|
||||||
from django import http
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.shortcuts import redirect, render_to_response
|
from django.shortcuts import redirect, render_to_response
|
||||||
from django.utils.text import normalize_newlines
|
from django.utils.text import normalize_newlines
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django import shortcuts
|
|
||||||
|
|
||||||
from django_openstack import api
|
from django_openstack import api
|
||||||
from django_openstack import forms
|
from django_openstack import forms
|
||||||
@ -46,6 +41,71 @@ from novaclient import exceptions as novaclient_exceptions
|
|||||||
LOG = logging.getLogger('django_openstack.dash.views.images')
|
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):
|
class LaunchForm(forms.SelfHandlingForm):
|
||||||
name = forms.CharField(max_length=80, label="Server Name")
|
name = forms.CharField(max_length=80, label="Server Name")
|
||||||
image_id = forms.CharField(widget=forms.HiddenInput())
|
image_id = forms.CharField(widget=forms.HiddenInput())
|
||||||
@ -70,12 +130,15 @@ class LaunchForm(forms.SelfHandlingForm):
|
|||||||
required=False,
|
required=False,
|
||||||
help_text="Which keypair to use for authentication")
|
help_text="Which keypair to use for authentication")
|
||||||
|
|
||||||
securitygrouplist = kwargs.get('initial', {}).get('securitygrouplist', [])
|
securitygrouplist = kwargs.get('initial', {}).get(
|
||||||
self.fields['security_groups'] = forms.MultipleChoiceField(choices=securitygrouplist,
|
'securitygrouplist', [])
|
||||||
|
self.fields['security_groups'] = forms.MultipleChoiceField(
|
||||||
|
choices=securitygrouplist,
|
||||||
label='Security Groups',
|
label='Security Groups',
|
||||||
required=True,
|
required=True,
|
||||||
initial=['default'],
|
initial=['default'],
|
||||||
widget=forms.SelectMultiple(attrs={'class': 'chzn-select',
|
widget=forms.SelectMultiple(
|
||||||
|
attrs={'class': 'chzn-select',
|
||||||
'style': "min-width: 200px"}),
|
'style': "min-width: 200px"}),
|
||||||
help_text="Launch instance in these Security Groups")
|
help_text="Launch instance in these Security Groups")
|
||||||
# setting self.fields.keyOrder seems to break validation,
|
# setting self.fields.keyOrder seems to break validation,
|
||||||
@ -108,22 +171,51 @@ class LaunchForm(forms.SelfHandlingForm):
|
|||||||
return redirect('dash_instances', tenant_id)
|
return redirect('dash_instances', tenant_id)
|
||||||
|
|
||||||
except api_exceptions.ApiException, e:
|
except api_exceptions.ApiException, e:
|
||||||
LOG.exception('ApiException while creating instances of image "%s"' %
|
LOG.exception('ApiException while creating instances of image "%s"'
|
||||||
image_id)
|
% image_id)
|
||||||
messages.error(request,
|
messages.error(request,
|
||||||
'Unable to launch instance: %s' % e.message)
|
'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
|
@login_required
|
||||||
def index(request, tenant_id):
|
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:
|
try:
|
||||||
tenant = api.token_get_tenant(request, request.user.tenant_id)
|
tenant = api.token_get_tenant(request, request.user.tenant_id)
|
||||||
except api_exceptions.ApiException, e:
|
except api_exceptions.ApiException, e:
|
||||||
messages.error(request, "Unable to retrienve tenant info\
|
messages.error(request, "Unable to retrienve tenant info\
|
||||||
from keystone: %s" % e.message)
|
from keystone: %s" % e.message)
|
||||||
|
|
||||||
all_images = []
|
all_images = []
|
||||||
try:
|
try:
|
||||||
all_images = api.image_list_detailed(request)
|
all_images = api.image_list_detailed(request)
|
||||||
@ -145,6 +237,7 @@ def index(request, tenant_id):
|
|||||||
|
|
||||||
return render_to_response(
|
return render_to_response(
|
||||||
'django_openstack/dash/images/index.html', {
|
'django_openstack/dash/images/index.html', {
|
||||||
|
'delete_form': delete_form,
|
||||||
'tenant': tenant,
|
'tenant': tenant,
|
||||||
'images': images,
|
'images': images,
|
||||||
}, context_instance=template.RequestContext(request))
|
}, context_instance=template.RequestContext(request))
|
||||||
@ -152,6 +245,7 @@ def index(request, tenant_id):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def launch(request, tenant_id, image_id):
|
def launch(request, tenant_id, image_id):
|
||||||
|
|
||||||
def flavorlist():
|
def flavorlist():
|
||||||
try:
|
try:
|
||||||
fl = api.flavor_list(request)
|
fl = api.flavor_list(request)
|
||||||
@ -210,3 +304,32 @@ def launch(request, tenant_id, image_id):
|
|||||||
'form': form,
|
'form': form,
|
||||||
'quotas': quotas,
|
'quotas': quotas,
|
||||||
}, context_instance=template.RequestContext(request))
|
}, 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))
|
||||||
|
@ -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>
|
@ -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>
|
@ -14,11 +14,14 @@
|
|||||||
<td>{{image.created_at|parse_date}}</td>
|
<td>{{image.created_at|parse_date}}</td>
|
||||||
<td>{{image.updated_at|parse_date}}</td>
|
<td>{{image.updated_at|parse_date}}</td>
|
||||||
<td>{{image.status|capfirst}}</td>
|
<td>{{image.status|capfirst}}</td>
|
||||||
|
{% if image.owner == request.user.username %}
|
||||||
<td id="actions">
|
<td id="actions">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a id="launch_{{image.id}}" class="launch" href="{% url dash_images_launch request.user.tenant_id image.id %}">Launch</a></li>
|
<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>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
@ -16,5 +16,10 @@
|
|||||||
{% if images %}
|
{% if images %}
|
||||||
{% include 'django_openstack/dash/images/_list.html' %}
|
{% include 'django_openstack/dash/images/_list.html' %}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<div class="message_box info">
|
||||||
|
<h2>Info</h2>
|
||||||
|
<p>There are currently no images.</p>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -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"> </div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue
Block a user