Commit 2c9fc11e by Őry Máté

dashboard: add VmRenewView

parent c6c2d058
{% extends "dashboard/base.html" %}
{% load i18n %}
{% block content %}
<div class="body-content">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin">
{%blocktrans with instance=instance.name%}
Renewing <em>{{instance}}</em>
{%endblocktrans%}
</h3>
</div>
<div class="panel-body">
{%blocktrans with object=object%}
Do you want to renew <strong>{{ object }}</strong>?
{%endblocktrans%}
{%blocktrans with suspend=time_of_suspend delete=time_of_delete|default:"n/a" %}
The instance will be suspended at <em>{{suspend}}</em>
and removed at <em>{{delete}}</em> if you renew it now.
{%endblocktrans%}
<div class="pull-right">
<form action="" method="POST">
{% csrf_token %}
<a class="btn btn-default"
href="{{instance.get_absolute_path}}">{% trans "Back" %}</a>
<button class="btn btn-danger">{% trans "Renew" %}</button>
</form>
</div>
</div>
</div>
{% endblock %}
...@@ -9,7 +9,7 @@ from .views import ( ...@@ -9,7 +9,7 @@ from .views import (
FavouriteView, NodeStatus, GroupList, TemplateDelete, LeaseDelete, FavouriteView, NodeStatus, GroupList, TemplateDelete, LeaseDelete,
VmGraphView, TemplateAclUpdateView, GroupDetailView, GroupDelete, VmGraphView, TemplateAclUpdateView, GroupDetailView, GroupDelete,
GroupAclUpdateView, GroupUserDelete, NotificationView, NodeGraphView, GroupAclUpdateView, GroupUserDelete, NotificationView, NodeGraphView,
VmMigrateView, VmDetailVncTokenView, VmMigrateView, VmDetailVncTokenView, VmRenewView,
) )
urlpatterns = patterns( urlpatterns = patterns(
...@@ -53,6 +53,8 @@ urlpatterns = patterns( ...@@ -53,6 +53,8 @@ urlpatterns = patterns(
url(r'^vm/(?P<pk>\d+)/activity/$', vm_activity), url(r'^vm/(?P<pk>\d+)/activity/$', vm_activity),
url(r'^vm/(?P<pk>\d+)/migrate/$', VmMigrateView.as_view(), url(r'^vm/(?P<pk>\d+)/migrate/$', VmMigrateView.as_view(),
name='dashboard.views.vm-migrate'), name='dashboard.views.vm-migrate'),
url(r'^vm/(?P<pk>\d+)/renew/((?P<key>.*)/?)$', VmRenewView.as_view(),
name='dashboard.views.vm-renew'),
url(r'^node/list/$', NodeList.as_view(), name='dashboard.views.node-list'), url(r'^node/list/$', NodeList.as_view(), name='dashboard.views.node-list'),
url(r'^node/(?P<pk>\d+)/$', NodeDetailView.as_view(), url(r'^node/(?P<pk>\d+)/$', NodeDetailView.as_view(),
......
...@@ -6,6 +6,7 @@ from datetime import datetime ...@@ -6,6 +6,7 @@ from datetime import datetime
import requests import requests
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from django.contrib.auth.views import redirect_to_login
from django.contrib.messages import warning from django.contrib.messages import warning
from django.core.exceptions import ( from django.core.exceptions import (
PermissionDenied, SuspiciousOperation, PermissionDenied, SuspiciousOperation,
...@@ -13,7 +14,7 @@ from django.core.exceptions import ( ...@@ -13,7 +14,7 @@ from django.core.exceptions import (
from django.core import signing from django.core import signing
from django.core.urlresolvers import reverse, reverse_lazy from django.core.urlresolvers import reverse, reverse_lazy
from django.http import HttpResponse, HttpResponseRedirect, Http404 from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.shortcuts import redirect, render from django.shortcuts import redirect, render, get_object_or_404
from django.views.decorators.http import require_GET from django.views.decorators.http import require_GET
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from django.views.generic import (TemplateView, DetailView, View, DeleteView, from django.views.generic import (TemplateView, DetailView, View, DeleteView,
...@@ -25,7 +26,9 @@ from django.template.loader import render_to_string ...@@ -25,7 +26,9 @@ from django.template.loader import render_to_string
from django.forms.models import inlineformset_factory from django.forms.models import inlineformset_factory
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
from braces.views import LoginRequiredMixin, SuperuserRequiredMixin from braces.views import (
LoginRequiredMixin, SuperuserRequiredMixin, AccessMixin
)
from .forms import ( from .forms import (
VmCustomizeForm, TemplateForm, LeaseForm, NodeForm, HostForm, VmCustomizeForm, TemplateForm, LeaseForm, NodeForm, HostForm,
...@@ -1524,6 +1527,145 @@ class TransferOwnershipView(LoginRequiredMixin, DetailView): ...@@ -1524,6 +1527,145 @@ class TransferOwnershipView(LoginRequiredMixin, DetailView):
kwargs={'pk': obj.pk})) kwargs={'pk': obj.pk}))
class AbstractVmFunctionView(AccessMixin, View):
"""Abstract instance-action view.
User can do the action with a valid token or if has at least required_level
ACL level for the instance.
Children should at least implement/add template_name, success_message,
url_name, and do_action().
"""
token_max_age = 3 * 24 * 3600
required_level = 'owner'
success_message = _("Failed to perform requested action.")
@classmethod
def check_acl(cls, instance, user):
if not instance.has_level(user, cls.required_level):
raise PermissionDenied()
@classmethod
def get_salt(cls):
return unicode(cls)
@classmethod
def get_token(cls, instance, user, *args):
t = tuple([getattr(i, 'pk', i) for i in [instance, user] + list(args)])
return signing.dumps(t, salt=cls.get_salt())
@classmethod
def get_token_url(cls, instance, user, *args):
key = cls.get_token(instance, user, *args)
args = (instance.pk, key) + args
return reverse(cls.url_name, args=args)
# this wont work, CBVs suck: reverse(cls.as_view(), args=args)
def get_template_names(self):
return [self.template_name]
def get(self, request, pk, key=None, *args, **kwargs):
class LoginNeeded(Exception):
pass
pk = int(pk)
instance = get_object_or_404(Instance, pk=pk)
try:
if key:
logger.debug('Confirm dialog for token %s.', key)
try:
self.validate_key(pk, key)
except signing.SignatureExpired:
messages.error(request, _(
'The token has expired, please log in.'))
raise LoginNeeded()
self.key = key
else:
if not request.user.is_authenticated():
raise LoginNeeded()
self.check_acl(instance, request.user)
except LoginNeeded:
return redirect_to_login(request.get_full_path(),
self.get_login_url(),
self.get_redirect_field_name())
except SuspiciousOperation as e:
messages.error(request, _('This token is invalid.'))
logger.warning('This token %s is invalid. %s', key, unicode(e))
raise PermissionDenied()
return render(request, self.get_template_names(),
self.get_context(instance))
def post(self, request, pk, key=None, *args, **kwargs):
pk = int(pk)
instance = get_object_or_404(Instance, pk=pk)
if key:
user = self.validate_key(pk, key)
else:
user = request.user
self.check_acl(instance, user)
if self.do_action(instance, user):
messages.success(request, self.success_message)
else:
messages.error(request, self.fail_message)
return HttpResponseRedirect(instance.get_absolute_url())
def validate_key(self, pk, key):
"""Get object based on signed token.
"""
try:
data = signing.loads(key, salt=self.get_salt())
logger.debug('Token data: %s', unicode(data))
instance, user = data
logger.debug('Extracted token data: instance: %s, user: %s',
unicode(instance), unicode(user))
except (signing.BadSignature, ValueError, TypeError) as e:
logger.warning('Tried invalid token. Token: %s, user: %s. %s',
key, unicode(self.request.user), unicode(e))
raise SuspiciousOperation()
try:
instance, user = signing.loads(key, max_age=self.token_max_age,
salt=self.get_salt())
logger.debug('Extracted non-expired token data: %s, %s',
unicode(instance), unicode(user))
except signing.BadSignature as e:
raise signing.SignatureExpired()
if pk != instance:
logger.debug('pk (%d) != instance (%d)', pk, instance)
raise SuspiciousOperation()
user = User.objects.get(pk=user)
return user
def do_action(self, instance, user):
raise NotImplementedError('Please override do_action(instance, user)')
def get_context(self, instance):
context = {'instance': instance}
if getattr(self, 'key', None) is not None:
context['key'] = self.key
return context
class VmRenewView(AbstractVmFunctionView):
"""User can renew an instance."""
template_name = 'dashboard/confirm/base-renew.html'
success_message = _("Virtual machine is successfully renewed.")
url_name = 'dashboard.views.vm-renew'
def get_context(self, instance):
context = super(VmRenewView, self).get_context(instance)
(context['time_of_suspend'],
context['time_of_delete']) = instance.get_renew_times()
return context
def do_action(self, instance, user):
instance.renew(user=user)
logger.info('Instance %s renewed by %s.', unicode(instance),
unicode(user))
return True
class TransferOwnershipConfirmView(LoginRequiredMixin, View): class TransferOwnershipConfirmView(LoginRequiredMixin, View):
"""User can accept an ownership offer.""" """User can accept an ownership offer."""
......
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