Commit 013ed92b by Kálmán Viktor

Merge branch 'feature-resize-help' into 'master'

Disk resize help and request

![Kijelölés_053](https://git.ik.bme.hu/circle/cloud/uploads/04c1c2c4264db0e93ad9243f22c79d8e/Kijelölés_053.png)

See merge request !341
parents 9d2184d8 fb293c07
...@@ -25,7 +25,7 @@ from django.shortcuts import redirect ...@@ -25,7 +25,7 @@ from django.shortcuts import redirect
from circle.settings.base import get_env_variable from circle.settings.base import get_env_variable
from dashboard.views import circle_login, HelpView from dashboard.views import circle_login, HelpView, ResizeHelpView
from dashboard.forms import CirclePasswordResetForm, CircleSetPasswordForm from dashboard.forms import CirclePasswordResetForm, CircleSetPasswordForm
from firewall.views import add_blacklist_item from firewall.views import add_blacklist_item
...@@ -65,6 +65,8 @@ urlpatterns = patterns( ...@@ -65,6 +65,8 @@ urlpatterns = patterns(
url(r'^info/support/$', url(r'^info/support/$',
TemplateView.as_view(template_name="info/support.html"), TemplateView.as_view(template_name="info/support.html"),
name="info.support"), name="info.support"),
url(r'^info/resize-how-to/$', ResizeHelpView.as_view(),
name="info.resize"),
) )
......
...@@ -1488,3 +1488,38 @@ textarea[name="new_members"] { ...@@ -1488,3 +1488,38 @@ textarea[name="new_members"] {
.acl-table td:first-child { .acl-table td:first-child {
text-align: center; text-align: center;
} }
#resize-help {
table {
background-color: #f5f5f5;
}
.panel {
padding: 2px 20px;
background-color: #f5f5f5;
margin: 20px 0px;
}
ol li {
margin-top: 15px;
}
img {
display: block;
margin: 15px 0 5px 0;
}
pre {
margin-top: 5px;
}
hr {
margin: 50px 0;
}
}
#vm-details-resize-how-to {
font-size: 1.5em;
text-align: center;
width: 100%;
}
...@@ -4,24 +4,29 @@ ...@@ -4,24 +4,29 @@
<i class="fa fa-file"></i> <i class="fa fa-file"></i>
{{ d.name }} (#{{ d.id }}) - {{ d.size|filesize }} {{ d.name }} (#{{ d.id }}) - {{ d.size|filesize }}
{% if op.remove_disk %}
<span class="operation-wrapper"> <span class="operation-wrapper pull-right">
<a href="{{ op.remove_disk.get_url }}?disk={{d.pk}}" {% if d.is_resizable %}
class="btn btn-xs btn-{{ op.remove_disk.effect}} pull-right operation disk-remove-btn {% if op.resize_disk %}
{% if op.remove_disk.disabled %}disabled{% endif %}">
<i class="fa fa-{{ op.remove_disk.icon }} fa-fw-12"></i> {% trans "Remove" %}
</a>
</span>
{% endif %}
{% if op.resize_disk %}
<span class="operation-wrapper">
<a href="{{ op.resize_disk.get_url }}?disk={{d.pk}}" <a href="{{ op.resize_disk.get_url }}?disk={{d.pk}}"
class="btn btn-xs btn-{{ op.resize_disk.effect }} pull-right operation disk-resize-btn class="btn btn-xs btn-{{ op.resize_disk.effect }} operation disk-resize-btn
{% if op.resize_disk.disabled %}disabled{% endif %}"> {% if op.resize_disk.disabled %}disabled{% endif %}">
<i class="fa fa-{{ op.resize_disk.icon }} fa-fw-12"></i> {% trans "Resize" %} <i class="fa fa-{{ op.resize_disk.icon }} fa-fw-12"></i> {% trans "Resize" %}
</a> </a>
</span> {% else %}
{% endif %} <a href="{% url "request.views.request-resize" vm_pk=instance.pk disk_pk=d.pk %}" class="btn btn-xs btn-primary operation">
<i class="fa fa-arrows-alt fa-fw-12"></i> {% trans "Request resize" %}
</a>
{% endif %}
{% endif %}
{% if op.remove_disk %}
<a href="{{ op.remove_disk.get_url }}?disk={{d.pk}}"
class="btn btn-xs btn-{{ op.remove_disk.effect}} operation disk-remove-btn
{% if op.remove_disk.disabled %}disabled{% endif %}">
<i class="fa fa-{{ op.remove_disk.icon }} fa-fw-12"></i> {% trans "Remove" %}
</a>
{% endif %}
</span>
<div style="clear: both;"></div> <div style="clear: both;"></div>
{% if request.user.is_superuser %} {% if request.user.is_superuser %}
......
...@@ -66,6 +66,17 @@ ...@@ -66,6 +66,17 @@
{% endfor %} {% endfor %}
</div> </div>
<hr />
{% if instance.disks.all %}
<div id="vm-details-resize-how-to">
<i class="fa fa-question"></i>
{% url "info.resize" as resize_url %}
{% blocktrans with url=resize_url %}
If you need help resizing the disks check out our <a href="{{ url }}">resize how-to.</a>
{% endblocktrans %}
</div>
{% endif %}
{% if user.is_superuser %} {% if user.is_superuser %}
<hr/> <hr/>
......
...@@ -136,6 +136,10 @@ class HelpView(TemplateView): ...@@ -136,6 +136,10 @@ class HelpView(TemplateView):
return ctx return ctx
class ResizeHelpView(TemplateView):
template_name = "info/resize.html"
class OpenSearchDescriptionView(TemplateView): class OpenSearchDescriptionView(TemplateView):
template_name = "dashboard/vm-opensearch.xml" template_name = "dashboard/vm-opensearch.xml"
content_type = "application/opensearchdescription+xml" content_type = "application/opensearchdescription+xml"
......
...@@ -22,6 +22,8 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -22,6 +22,8 @@ from django.utils.translation import ugettext_lazy as _
from django.template import RequestContext from django.template import RequestContext
from django.template.loader import render_to_string from django.template.loader import render_to_string
from sizefield.widgets import FileSizeWidget
from sizefield.utils import filesizeformat
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit from crispy_forms.layout import Submit
...@@ -70,34 +72,32 @@ class InitialFromFileMixin(object): ...@@ -70,34 +72,32 @@ class InitialFromFileMixin(object):
RequestContext(request, {}), RequestContext(request, {}),
) )
def clean(self): def clean_message(self):
cleaned_data = super(InitialFromFileMixin, self).clean() message = self.cleaned_data['message']
if cleaned_data['message'].strip() == self.initial['message'].strip(): if message.strip() == self.initial['message'].strip():
raise ValidationError( raise ValidationError(_("Fill in the message."), code="invalid")
_("Fill in the message."), return message.strip()
code="invalid")
return cleaned_data
class TemplateRequestForm(InitialFromFileMixin, Form): class TemplateRequestForm(InitialFromFileMixin, Form):
message = CharField(widget=Textarea, label=_("Message"))
template = ModelChoiceField(TemplateAccessType.objects.all(), template = ModelChoiceField(TemplateAccessType.objects.all(),
label=_("Template share")) label=_("Template share"))
level = ChoiceField(TemplateAccessAction.LEVELS, widget=RadioSelect, level = ChoiceField(TemplateAccessAction.LEVELS, widget=RadioSelect,
initial=TemplateAccessAction.LEVELS.user) initial=TemplateAccessAction.LEVELS.user)
message = CharField(widget=Textarea, label=_("Message"))
initial_template = "request/initials/template.html" initial_template = "request/initials/template.html"
class LeaseRequestForm(InitialFromFileMixin, Form): class LeaseRequestForm(InitialFromFileMixin, Form):
lease = ModelChoiceField(LeaseType.objects.all(), label=_("Lease")) lease = ModelChoiceField(LeaseType.objects.all(), label=_("Lease"))
message = CharField(widget=Textarea) message = CharField(widget=Textarea, label=_("Message"))
initial_template = "request/initials/lease.html" initial_template = "request/initials/lease.html"
class ResourceRequestForm(InitialFromFileMixin, VmResourcesForm): class ResourceRequestForm(InitialFromFileMixin, VmResourcesForm):
message = CharField(widget=Textarea) message = CharField(widget=Textarea, label=_("Message"))
initial_template = "request/initials/resources.html" initial_template = "request/initials/resources.html"
...@@ -110,3 +110,28 @@ class ResourceRequestForm(InitialFromFileMixin, VmResourcesForm): ...@@ -110,3 +110,28 @@ class ResourceRequestForm(InitialFromFileMixin, VmResourcesForm):
raise ValidationError( raise ValidationError(
_("You haven't changed any of the resources."), _("You haven't changed any of the resources."),
code="invalid") code="invalid")
class ResizeRequestForm(InitialFromFileMixin, Form):
message = CharField(widget=Textarea, label=_("Message"))
size = CharField(widget=FileSizeWidget, label=_('Size'),
help_text=_('Size to resize the disk in bytes or with'
' units like MB or GB.'))
initial_template = "request/initials/resize.html"
def __init__(self, *args, **kwargs):
self.disk = kwargs.pop("disk")
super(ResizeRequestForm, self).__init__(*args, **kwargs)
def clean_size(self):
cleaned_data = super(ResizeRequestForm, self).clean()
disk = self.disk
size_in_bytes = cleaned_data.get("size")
if not size_in_bytes.isdigit() and len(size_in_bytes) > 0:
raise ValidationError(_("Invalid format, you can use GB or MB!"))
if int(size_in_bytes) < int(disk.size):
raise ValidationError(_("Disk size must be greater than the actual"
"size (%s).") % filesizeformat(disk.size))
return size_in_bytes
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import sizefield.models
class Migration(migrations.Migration):
dependencies = [
('vm', '0002_interface_model'),
('storage', '0002_disk_bus'),
('request', '0003_auto_20150410_1917'),
]
operations = [
migrations.CreateModel(
name='DiskResizeAction',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('size', sizefield.models.FileSizeField(default=None, null=True)),
('disk', models.ForeignKey(to='storage.Disk')),
('instance', models.ForeignKey(to='vm.Instance')),
],
options={
'abstract': False,
},
),
migrations.AlterField(
model_name='request',
name='type',
field=models.CharField(max_length=10, choices=[(b'resource', 'resource request'), (b'lease', 'lease request'), (b'template', 'template access request'), (b'resize', 'disk resize request')]),
),
]
...@@ -32,10 +32,14 @@ from django.utils.translation import ( ...@@ -32,10 +32,14 @@ from django.utils.translation import (
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
import requests import requests
from sizefield.models import FileSizeField
from model_utils.models import TimeStampedModel from model_utils.models import TimeStampedModel
from model_utils import Choices from model_utils import Choices
from sizefield.utils import filesizeformat
from vm.models import Instance, InstanceTemplate, Lease from vm.models import Instance, InstanceTemplate, Lease
from vm.operations import ResourcesOperation, ResizeDiskOperation
from storage.models import Disk
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -49,6 +53,9 @@ class RequestAction(Model): ...@@ -49,6 +53,9 @@ class RequestAction(Model):
def accept_msg(self): def accept_msg(self):
raise NotImplementedError raise NotImplementedError
def is_acceptable(self):
return True
class Meta: class Meta:
abstract = True abstract = True
...@@ -77,6 +84,7 @@ class Request(TimeStampedModel): ...@@ -77,6 +84,7 @@ class Request(TimeStampedModel):
('resource', _('resource request')), ('resource', _('resource request')),
('lease', _("lease request")), ('lease', _("lease request")),
('template', _("template access request")), ('template', _("template access request")),
('resize', _("disk resize request")),
) )
type = CharField(choices=TYPES, max_length=10) type = CharField(choices=TYPES, max_length=10)
message = TextField(verbose_name=_("Message")) message = TextField(verbose_name=_("Message"))
...@@ -99,7 +107,8 @@ class Request(TimeStampedModel): ...@@ -99,7 +107,8 @@ class Request(TimeStampedModel):
return { return {
'resource': "tasks", 'resource': "tasks",
'lease': "clock-o", 'lease': "clock-o",
'template': "puzzle-piece" 'template': "puzzle-piece",
'resize': "arrows-alt",
}.get(self.type) }.get(self.type)
def get_effect(self): def get_effect(self):
...@@ -143,6 +152,10 @@ class Request(TimeStampedModel): ...@@ -143,6 +152,10 @@ class Request(TimeStampedModel):
decline_msg, url=self.get_absolute_url(), reason=self.reason, decline_msg, url=self.get_absolute_url(), reason=self.reason,
) )
@property
def is_acceptable(self):
return self.action.is_acceptable()
class LeaseType(RequestType): class LeaseType(RequestType):
lease = ForeignKey(Lease, verbose_name=_("Lease")) lease = ForeignKey(Lease, verbose_name=_("Lease"))
...@@ -200,6 +213,9 @@ class ResourceChangeAction(RequestAction): ...@@ -200,6 +213,9 @@ class ResourceChangeAction(RequestAction):
'priority': self.priority, 'priority': self.priority,
} }
def is_acceptable(self):
return self.instance.status in ResourcesOperation.accept_states
class ExtendLeaseAction(RequestAction): class ExtendLeaseAction(RequestAction):
instance = ForeignKey(Instance) instance = ForeignKey(Instance)
...@@ -246,6 +262,30 @@ class TemplateAccessAction(RequestAction): ...@@ -246,6 +262,30 @@ class TemplateAccessAction(RequestAction):
) % ", ".join([x.name for x in self.template_type.templates.all()]) ) % ", ".join([x.name for x in self.template_type.templates.all()])
class DiskResizeAction(RequestAction):
instance = ForeignKey(Instance)
disk = ForeignKey(Disk)
size = FileSizeField(null=True, default=None)
def accept(self, user):
self.instance.resize_disk(disk=self.disk, size=self.size, user=user)
@property
def accept_msg(self):
return _(
'The disk <em class="text-muted">%(disk_name)s (#%(id)d)</em> of '
'<a href="%(url)s">%(vm_name)s</a> got resized. '
'The new size is: %(bytes)d bytes (%(size)s).'
) % {'disk_name': self.disk.name, 'id': self.disk.id,
'url': self.instance.get_absolute_url(),
'vm_name': self.instance.name,
'bytes': self.size, 'size': filesizeformat(self.size),
}
def is_acceptable(self):
return self.instance.status in ResizeDiskOperation.accept_states
def send_notifications(sender, instance, created, **kwargs): def send_notifications(sender, instance, created, **kwargs):
if not created: if not created:
return return
......
{% load i18n %}
{% load crispy_forms_tags %}
{% load sizefieldtags %}
<dl>
<dt>{% trans "Virtual machine" %}</dt>
<dd><a href="{{ vm.get_absolute_url }}">{{ vm.name }}</a></dd>
<dt>{% trans "Disk" %}</dt>
<dd>
{% if request.user.is_superuser %}
<a href="{{ disk.get_absolute_url }}">{{ disk.name }} (#{{ disk.id }})</a>
{% else %}
{{ disk.name }} (#{{ disk.id }})
{% endif %}
- {{ disk.size|filesize }}
</dd>
</dl>
<form action="{% url "request.views.request-resize" vm_pk=vm.pk disk_pk=disk.pk %}" method="POST">
{% include "display-form-errors.html" %}
{% csrf_token %}
{{ form.size|as_crispy_field }}
{{ form.message|as_crispy_field }}
<input type="submit" class="btn btn-primary" id="op-form-send"/>
</form>
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
{% load i18n %} {% load i18n %}
{% load render_table from django_tables2 %} {% load render_table from django_tables2 %}
{% load arrowfilter %} {% load arrowfilter %}
{% load sizefieldtags %}
{% block title-page %}{% trans "Request" %}{% endblock %} {% block title-page %}{% trans "Request" %}{% endblock %}
...@@ -65,6 +66,15 @@ ...@@ -65,6 +66,15 @@
<dd>{{ action.get_readable_level }}</dd> <dd>{{ action.get_readable_level }}</dd>
</dl> </dl>
{% elif object.type == "resource" %} {% elif object.type == "resource" %}
{% if not is_acceptable %}
<div class="alert alert-warning">
{% blocktrans %}
To change the resources the virtual machine must be in one of the following states:
STOPPED, PENDING, RUNNING. If the virtual machine is running it will be
automatically stopped when accepting the request.
{% endblocktrans %}
</div>
{% endif %}
<dl> <dl>
<dt>{% trans "VM name" %}</dt> <dt>{% trans "VM name" %}</dt>
<dd><a href="{{ action.instance.get_absolute_url }}">{{ action.instance.name }}</a></dd> <dd><a href="{{ action.instance.get_absolute_url }}">{{ action.instance.name }}</a></dd>
...@@ -74,7 +84,7 @@ ...@@ -74,7 +84,7 @@
{{ action.instance.get_status_display|upper }} {{ action.instance.get_status_display|upper }}
</dd> </dd>
<dt>{% trans "VM description" %}</dt> <dt>{% trans "VM description" %}</dt>
<dd>{{ action.instance.description }}</dd> <dd>{{ action.instance.description|default:"-" }}</dd>
<dt> <dt>
{% trans "Priority" %} {% trans "Priority" %}
<span class="text-muted" style="font-weight: normal;">{% trans "(old values in parentheses)" %}</span> <span class="text-muted" style="font-weight: normal;">{% trans "(old values in parentheses)" %}</span>
...@@ -85,8 +95,39 @@ ...@@ -85,8 +95,39 @@
<dt>{% trans "Ram size" %}</dt> <dt>{% trans "Ram size" %}</dt>
<dd>{{ action.ram_size }} ({{ action.instance.ram_size }}) MiB</dd> <dd>{{ action.ram_size }} ({{ action.instance.ram_size }}) MiB</dd>
</dl> </dl>
{% elif object.type == "resize" %}
{% if not is_acceptable %}
<div class="alert alert-warning">
{% trans "To resize the disk the virtual machine must be in RUNNING state." %}
</div>
{% endif %}
<dl>
<dt>{% trans "VM name" %}</dt>
<dd><a href="{{ action.instance.get_absolute_url }}">{{ action.instance.name }}</a></dd>
<dt>{% trans "Status" %}</dt>
<dd>
<i class="fa {{ action.instance.get_status_icon }}"></i>
{{ action.instance.get_status_display|upper }}
</dd>
<dt>{% trans "VM description" %}</dt>
<dd>{{ action.instance.description|default:"-" }}</dd>
<dt>{% trans "Disk" %}</dt>
<dd>
{% if request.user.is_superuser %}
<a href="{{ action.disk.get_absolute_url }}">
{{ action.disk.name }} (#{{ action.disk.id}})
</a>
{% else %}
{{ action.disk.name }} (#{{ action.disk.id}})</dd>
{% endif %}
</dd>
<dt>{% trans "Current size" %}</dt>
<dd>{{ action.disk.size|filesize}} ({{ action.disk.size }} bytes)</dd>
<dt>{% trans "Requested size" %}</dt>
<dd>{{ action.size|filesize}} ({{ action.size }} bytes)</dd>
</dl>
{% else %} {% else %}
hacks!!! Are you adding a new action type?
{% endif %} {% endif %}
{% if object.status == "PENDING" and request.user.is_superuser %} {% if object.status == "PENDING" and request.user.is_superuser %}
...@@ -103,7 +144,7 @@ ...@@ -103,7 +144,7 @@
{% trans "Decline" %} {% trans "Decline" %}
</button> </button>
</form> </form>
{% if object.type == "resource" and action.instance.status not in accept_states %} {% if not is_acceptable %}
{% trans "You can't accept this request because of the VM's state." %} {% trans "You can't accept this request because of the VM's state." %}
{% else %} {% else %}
<form method="POST"> <form method="POST">
......
{% spaceless %}
{% if LANGUAGE_CODE == "en" %}
Why do you need a bigger disk?
{% else %} {# place your translations here #}
Why do you need a bigger disk?
{% endif %}
{% endspaceless %}
...@@ -23,7 +23,7 @@ from .views import ( ...@@ -23,7 +23,7 @@ from .views import (
LeaseTypeCreate, LeaseTypeDetail, LeaseTypeCreate, LeaseTypeDetail,
TemplateAccessTypeCreate, TemplateAccessTypeDetail, TemplateAccessTypeCreate, TemplateAccessTypeDetail,
TemplateRequestView, LeaseRequestView, ResourceRequestView, TemplateRequestView, LeaseRequestView, ResourceRequestView,
LeaseTypeDelete, TemplateAccessTypeDelete, LeaseTypeDelete, TemplateAccessTypeDelete, ResizeRequestView,
) )
urlpatterns = patterns( urlpatterns = patterns(
...@@ -60,4 +60,6 @@ urlpatterns = patterns( ...@@ -60,4 +60,6 @@ urlpatterns = patterns(
name="request.views.request-lease"), name="request.views.request-lease"),
url(r'resource/(?P<vm_pk>\d+)/$', ResourceRequestView.as_view(), url(r'resource/(?P<vm_pk>\d+)/$', ResourceRequestView.as_view(),
name="request.views.request-resource"), name="request.views.request-resource"),
url(r'resize/(?P<vm_pk>\d+)/(?P<disk_pk>\d+)/$',
ResizeRequestView.as_view(), name="request.views.request-resize"),
) )
...@@ -19,27 +19,29 @@ from __future__ import unicode_literals, absolute_import ...@@ -19,27 +19,29 @@ from __future__ import unicode_literals, absolute_import
from django.views.generic import ( from django.views.generic import (
UpdateView, TemplateView, DetailView, CreateView, FormView, DeleteView, UpdateView, TemplateView, DetailView, CreateView, FormView, DeleteView,
) )
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.shortcuts import redirect, get_object_or_404 from django.shortcuts import redirect, get_object_or_404
from django.core.exceptions import PermissionDenied, SuspiciousOperation from django.core.exceptions import PermissionDenied, SuspiciousOperation
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.http import JsonResponse
from braces.views import SuperuserRequiredMixin, LoginRequiredMixin from braces.views import SuperuserRequiredMixin, LoginRequiredMixin
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
from request.models import ( from request.models import (
Request, TemplateAccessType, LeaseType, TemplateAccessAction, Request, TemplateAccessType, LeaseType, TemplateAccessAction,
ExtendLeaseAction, ResourceChangeAction, ExtendLeaseAction, ResourceChangeAction, DiskResizeAction
) )
from storage.models import Disk
from vm.models import Instance from vm.models import Instance
from vm.operations import ResourcesOperation
from request.tables import ( from request.tables import (
RequestTable, TemplateAccessTypeTable, LeaseTypeTable, RequestTable, TemplateAccessTypeTable, LeaseTypeTable,
) )
from request.forms import ( from request.forms import (
LeaseTypeForm, TemplateAccessTypeForm, TemplateRequestForm, LeaseTypeForm, TemplateAccessTypeForm, TemplateRequestForm,
LeaseRequestForm, ResourceRequestForm, LeaseRequestForm, ResourceRequestForm, ResizeRequestForm,
) )
...@@ -93,7 +95,7 @@ class RequestDetail(LoginRequiredMixin, DetailView): ...@@ -93,7 +95,7 @@ class RequestDetail(LoginRequiredMixin, DetailView):
context = super(RequestDetail, self).get_context_data(**kwargs) context = super(RequestDetail, self).get_context_data(**kwargs)
context['action'] = request.action context['action'] = request.action
context['accept_states'] = ResourcesOperation.accept_states context['is_acceptable'] = request.is_acceptable
# workaround for http://git.io/vIIYi # workaround for http://git.io/vIIYi
context['request'] = self.request context['request'] = self.request
...@@ -167,6 +169,7 @@ class RequestTypeList(LoginRequiredMixin, SuperuserRequiredMixin, ...@@ -167,6 +169,7 @@ class RequestTypeList(LoginRequiredMixin, SuperuserRequiredMixin,
class TemplateRequestView(LoginRequiredMixin, FormView): class TemplateRequestView(LoginRequiredMixin, FormView):
form_class = TemplateRequestForm form_class = TemplateRequestForm
template_name = "request/request-template.html" template_name = "request/request-template.html"
success_message = _("Request successfully sent.")
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super(TemplateRequestView, self).get_form_kwargs() kwargs = super(TemplateRequestView, self).get_form_kwargs()
...@@ -192,7 +195,8 @@ class TemplateRequestView(LoginRequiredMixin, FormView): ...@@ -192,7 +195,8 @@ class TemplateRequestView(LoginRequiredMixin, FormView):
) )
req.save() req.save()
return redirect("/") messages.success(self.request, self.success_message)
return redirect(reverse("dashboard.index"))
class VmRequestMixin(LoginRequiredMixin, object): class VmRequestMixin(LoginRequiredMixin, object):
...@@ -224,6 +228,7 @@ class LeaseRequestView(VmRequestMixin, FormView): ...@@ -224,6 +228,7 @@ class LeaseRequestView(VmRequestMixin, FormView):
form_class = LeaseRequestForm form_class = LeaseRequestForm
template_name = "request/request-lease.html" template_name = "request/request-lease.html"
user_level = "operator" user_level = "operator"
success_message = _("Request successfully sent.")
def form_valid(self, form): def form_valid(self, form):
data = form.cleaned_data data = form.cleaned_data
...@@ -244,6 +249,7 @@ class LeaseRequestView(VmRequestMixin, FormView): ...@@ -244,6 +249,7 @@ class LeaseRequestView(VmRequestMixin, FormView):
) )
req.save() req.save()
messages.success(self.request, self.success_message)
return redirect(vm.get_absolute_url()) return redirect(vm.get_absolute_url())
...@@ -251,6 +257,7 @@ class ResourceRequestView(VmRequestMixin, FormView): ...@@ -251,6 +257,7 @@ class ResourceRequestView(VmRequestMixin, FormView):
form_class = ResourceRequestForm form_class = ResourceRequestForm
template_name = "request/request-resource.html" template_name = "request/request-resource.html"
user_level = "user" user_level = "user"
success_message = _("Request successfully sent.")
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super(ResourceRequestView, self).get_form_kwargs() kwargs = super(ResourceRequestView, self).get_form_kwargs()
...@@ -287,4 +294,60 @@ class ResourceRequestView(VmRequestMixin, FormView): ...@@ -287,4 +294,60 @@ class ResourceRequestView(VmRequestMixin, FormView):
) )
req.save() req.save()
messages.success(self.request, self.success_message)
return redirect(vm.get_absolute_url())
class ResizeRequestView(VmRequestMixin, FormView):
form_class = ResizeRequestForm
template_name = "request/_request-resize-form.html"
user_level = "owner"
success_message = _("Request successfully sent.")
def get_disk(self, *args, **kwargs):
disk = get_object_or_404(Disk, pk=self.kwargs['disk_pk'])
if disk not in self.get_vm().disks.all():
raise SuspiciousOperation
return disk
def get_form_kwargs(self):
kwargs = super(ResizeRequestView, self).get_form_kwargs()
kwargs['disk'] = self.get_disk()
return kwargs
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/_modal.html']
else:
return ['dashboard/_base.html']
def get_context_data(self, **kwargs):
context = super(ResizeRequestView, self).get_context_data(**kwargs)
context['disk'] = self.get_disk()
context['template'] = self.template_name
context['box_title'] = context['title'] = _("Disk resize request")
context['ajax_title'] = True
return context
def form_valid(self, form):
disk = self.get_disk()
if not disk.is_resizable:
raise SuspiciousOperation
vm = self.get_vm()
data = form.cleaned_data
user = self.request.user
dra = DiskResizeAction(instance=vm, disk=disk, size=data['size'])
dra.save()
req = Request(user=user, message=data['message'], action=dra,
type=Request.TYPES.resize)
req.save()
if self.request.is_ajax():
return JsonResponse({'success': True,
'messages': [self.success_message]})
else:
messages.success(self.request, self.success_message)
return redirect(vm.get_absolute_url()) return redirect(vm.get_absolute_url())
...@@ -28,6 +28,7 @@ from celery.contrib.abortable import AbortableAsyncResult ...@@ -28,6 +28,7 @@ from celery.contrib.abortable import AbortableAsyncResult
from django.db.models import (Model, BooleanField, CharField, DateTimeField, from django.db.models import (Model, BooleanField, CharField, DateTimeField,
ForeignKey) ForeignKey)
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.core.urlresolvers import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, ugettext_noop from django.utils.translation import ugettext_lazy as _, ugettext_noop
from model_utils.models import TimeStampedModel from model_utils.models import TimeStampedModel
...@@ -535,3 +536,10 @@ class Disk(TimeStampedModel): ...@@ -535,3 +536,10 @@ class Disk(TimeStampedModel):
disk.is_ready = True disk.is_ready = True
disk.save() disk.save()
return disk return disk
def get_absolute_url(self):
return reverse('dashboard.views.disk-detail', kwargs={'pk': self.pk})
@property
def is_resizable(self):
return self.type in ('qcow2-norm', 'raw-rw')
...@@ -306,6 +306,9 @@ class ResizeDiskOperation(RemoteInstanceOperation): ...@@ -306,6 +306,9 @@ class ResizeDiskOperation(RemoteInstanceOperation):
size=filesizeformat(kwargs['size']), name=kwargs['disk'].name) size=filesizeformat(kwargs['size']), name=kwargs['disk'].name)
def _operation(self, disk, size): def _operation(self, disk, size):
if not disk.is_resizable:
raise HumanReadableException.create(ugettext_noop(
'Disk type "%(type)s" is not resizable.'), type=disk.type)
super(ResizeDiskOperation, self)._operation(disk=disk, size=size) super(ResizeDiskOperation, self)._operation(disk=disk, size=size)
disk.size = size disk.size = size
disk.save() disk.save()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment