from __future__ import unicode_literals

from os import getenv
import json
import logging
import re
from datetime import datetime
import requests

from django.conf import settings
from django.contrib.auth.models import User, Group
from django.contrib.auth.views import login, redirect_to_login
from django.contrib.messages import warning
from django.core.exceptions import (
    PermissionDenied, SuspiciousOperation,
)
from django.core import signing
from django.core.urlresolvers import reverse, reverse_lazy
from django.db.models import Count
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.shortcuts import redirect, render, get_object_or_404
from django.views.decorators.http import require_GET
from django.views.generic.detail import SingleObjectMixin
from django.views.generic import (TemplateView, DetailView, View, DeleteView,
                                  UpdateView, CreateView, ListView)
from django.contrib import messages
from django.utils.translation import ugettext as _
from django.template.defaultfilters import title as title_filter
from django.template.loader import render_to_string
from django.template import RequestContext

from django.forms.models import inlineformset_factory
from django_tables2 import SingleTableView
from braces.views import (
    LoginRequiredMixin, SuperuserRequiredMixin, AccessMixin
)

from .forms import (
    CircleAuthenticationForm, DiskAddForm, HostForm, LeaseForm, MyProfileForm,
    NodeForm, TemplateForm, TraitForm, VmCustomizeForm,
)
from .tables import (NodeListTable, NodeVmListTable,
                     TemplateListTable, LeaseListTable, GroupListTable,)
from vm.models import (
    Instance, instance_activity, InstanceActivity, InstanceTemplate, Interface,
    InterfaceTemplate, Lease, Node, NodeActivity, Trait,
)
from storage.models import Disk
from firewall.models import Vlan, Host, Rule
from dashboard.models import Favourite, Profile

logger = logging.getLogger(__name__)


def search_user(keyword):
    try:
        return User.objects.get(username=keyword)
    except User.DoesNotExist:
        try:
            return User.objects.get(profile__org_id=keyword)
        except User.DoesNotExist:
            return User.objects.get(email=keyword)


# github.com/django/django/blob/stable/1.6.x/django/contrib/messages/views.py
class SuccessMessageMixin(object):
    """
    Adds a success message on successful form submission.
    """
    success_message = ''

    def form_valid(self, form):
        response = super(SuccessMessageMixin, self).form_valid(form)
        success_message = self.get_success_message(form.cleaned_data)
        if success_message:
            messages.success(self.request, success_message)
        return response

    def get_success_message(self, cleaned_data):
        return self.success_message % cleaned_data


class IndexView(LoginRequiredMixin, TemplateView):
    template_name = "dashboard/index.html"

    def get_context_data(self, **kwargs):
        user = self.request.user
        context = super(IndexView, self).get_context_data(**kwargs)

        # instances
        favs = Instance.objects.filter(favourite__user=self.request.user)
        instances = Instance.get_objects_with_level(
            'user', user).filter(destroyed_at=None)
        display = list(favs) + list(set(instances) - set(favs))
        for d in display:
            d.fav = True if d in favs else False
        context.update({
            'instances': display[:5],
            'more_instances': instances.count() - len(instances[:5])
        })

        running = instances.filter(status='RUNNING')
        stopped = instances.exclude(status__in=('RUNNING', 'NOSTATE'))

        context.update({
            'running_vms': running[:20],
            'running_vm_num': running.count(),
            'stopped_vm_num': stopped.count()
        })

        # notifications
        context['new_notifications'] = user.notification_set.filter(
            status="new").count()

        # nodes
        if user.is_superuser:
            nodes = Node.objects.all()
            context.update({
                'nodes': nodes[:10],
                'more_nodes': nodes.count() - len(nodes[:10]),
                'sum_node_num': nodes.count(),
                'node_num': {
                    'running': Node.get_state_count(True, True),
                    'missing': Node.get_state_count(False, True),
                    'disabled': Node.get_state_count(True, False),
                    'offline': Node.get_state_count(False, False)
                }
            })

        # groups
        groups = Group.objects.all()
        context.update({
            'groups': groups[:10],
            'more_groups': groups.count() - len(groups[:10]),
        })

        # template
        if user.has_perm('vm.create_template'):
            context['templates'] = InstanceTemplate.get_objects_with_level(
                'operator', user).all()[:5]

        return context


def get_vm_acl_data(obj):
    levels = obj.ACL_LEVELS
    users = obj.get_users_with_level()
    users = [{'user': u, 'level': l} for u, l in users]
    groups = obj.get_groups_with_level()
    groups = [{'group': g, 'level': l} for g, l in groups]
    return {'users': users, 'groups': groups, 'levels': levels,
            'url': reverse('dashboard.views.vm-acl', args=[obj.pk])}


def get_group_acl_data(obj):
    aclobj = obj.profile
    levels = aclobj.ACL_LEVELS
    users = aclobj.get_users_with_level()
    users = [{'user': u, 'level': l} for u, l in users]
    groups = aclobj.get_groups_with_level()
    groups = [{'group': g, 'level': l} for g, l in groups]
    return {'users': users, 'groups': groups, 'levels': levels,
            'url': reverse('dashboard.views.group-acl', args=[obj.pk])}


class CheckedDetailView(LoginRequiredMixin, DetailView):
    read_level = 'user'

    def get_has_level(self):
        return self.object.has_level

    def get_context_data(self, **kwargs):
        context = super(CheckedDetailView, self).get_context_data(**kwargs)
        if not self.get_has_level()(self.request.user, self.read_level):
            raise PermissionDenied()
        return context


class VmDetailVncTokenView(CheckedDetailView):
    template_name = "dashboard/vm-detail.html"
    model = Instance

    def get(self, request, **kwargs):
        self.object = self.get_object()
        if not self.object.has_level(request.user, 'operator'):
            raise PermissionDenied()
        if self.object.node:
            port = self.object.vnc_port
            host = str(self.object.node.host.ipv4)
            value = signing.dumps({'host': host,
                                   'port': port},
                                  key=getenv("PROXY_SECRET", 'asdasd')),
            return HttpResponse('vnc/?d=%s' % value)
        else:
            raise Http404()


class VmDetailView(CheckedDetailView):
    template_name = "dashboard/vm-detail.html"
    model = Instance

    def get_context_data(self, **kwargs):
        context = super(VmDetailView, self).get_context_data(**kwargs)
        instance = context['instance']
        context.update({
            'graphite_enabled': VmGraphView.get_graphite_url() is not None,
            'vnc_url': reverse_lazy("dashboard.views.detail-vnc",
                                    kwargs={'pk': self.object.pk})
        })

        # activity data
        context['activities'] = (
            InstanceActivity.objects.filter(
                instance=self.object, parent=None).
            order_by('-started').
            select_related('user').prefetch_related('children'))

        context['vlans'] = Vlan.get_objects_with_level(
            'user', self.request.user
        ).exclude(
            pk__in=Interface.objects.filter(
                instance=self.get_object()).values_list("vlan", flat=True)
        ).all()
        context['acl'] = get_vm_acl_data(instance)
        context['forms'] = {
            'disk_add_form': DiskAddForm(
                user=self.request.user,
                is_template=False, object_pk=self.get_object().pk,
                prefix="disk"),
        }
        context['os_type_icon'] = instance.os_type.replace("unknown",
                                                           "question")
        return context

    def post(self, request, *args, **kwargs):
        if (request.POST.get('ram-size') and request.POST.get('cpu-count')
                and request.POST.get('cpu-priority')):
            return self.__set_resources(request)

        options = {
            'change_password': self.__change_password,
            'new_name': self.__set_name,
            'new_tag': self.__add_tag,
            'to_remove': self.__remove_tag,
            'port': self.__add_port,
            'new_network_vlan': self.__new_network,
            'save_as': self.__save_as,
            'shut_down': self.__shut_down,
            'sleep': self.__sleep,
            'wake_up': self.__wake_up,
            'deploy': self.__deploy,
            'reset': self.__reset,
            'reboot': self.__reboot,
            'shut_off': self.__shut_off,
        }
        for k, v in options.iteritems():
            if request.POST.get(k) is not None:
                return v(request)

    def __change_password(self, request):
        self.object = self.get_object()
        if not self.object.has_level(request.user, 'owner'):
            raise PermissionDenied()

        self.object.change_password(user=request.user)
        messages.success(request, _("Password changed!"))
        if request.is_ajax():
            return HttpResponse("Success!")
        else:
            return redirect(reverse_lazy("dashboard.views.detail",
                                         kwargs={'pk': self.object.pk}))

    def __set_resources(self, request):
        self.object = self.get_object()
        if not self.object.has_level(request.user, 'owner'):
            raise PermissionDenied()
        if not request.user.has_perm('vm.change_resources'):
            raise PermissionDenied()

        resources = {
            'num_cores': request.POST.get('cpu-count'),
            'ram_size': request.POST.get('ram-size'),
            'max_ram_size': request.POST.get('ram-size'),  # TODO: max_ram
            'priority': request.POST.get('cpu-priority')
        }
        Instance.objects.filter(pk=self.object.pk).update(**resources)

        success_message = _("Resources successfully updated!")
        if request.is_ajax():
            response = {'message': success_message}
            return HttpResponse(
                json.dumps(response),
                content_type="application/json"
            )
        else:
            messages.success(request, success_message)
            return redirect(reverse_lazy("dashboard.views.detail",
                                         kwargs={'pk': self.object.pk}))

    def __set_name(self, request):
        self.object = self.get_object()
        if not self.object.has_level(request.user, 'owner'):
            raise PermissionDenied()
        new_name = request.POST.get("new_name")
        Instance.objects.filter(pk=self.object.pk).update(
            **{'name': new_name})

        success_message = _("VM successfully renamed!")
        if request.is_ajax():
            response = {
                'message': success_message,
                'new_name': new_name,
                'vm_pk': self.object.pk
            }
            return HttpResponse(
                json.dumps(response),
                content_type="application/json"
            )
        else:
            messages.success(request, success_message)
            return redirect(reverse_lazy("dashboard.views.detail",
                                         kwargs={'pk': self.object.pk}))

    def __add_tag(self, request):
        new_tag = request.POST.get('new_tag')
        self.object = self.get_object()
        if not self.object.has_level(request.user, 'owner'):
            raise PermissionDenied()

        if len(new_tag) < 1:
            message = u"Please input something!"
        elif len(new_tag) > 20:
            message = u"Tag name is too long!"
        else:
            self.object.tags.add(new_tag)

        try:
            messages.error(request, message)
        except:
            pass

        return redirect(reverse_lazy("dashboard.views.detail",
                                     kwargs={'pk': self.object.pk}))

    def __remove_tag(self, request):
        try:
            to_remove = request.POST.get('to_remove')
            self.object = self.get_object()
            if not self.object.has_level(request.user, 'owner'):
                raise PermissionDenied()

            self.object.tags.remove(to_remove)
            message = u"Success"
        except:  # note this won't really happen
            message = u"Not success"

        if request.is_ajax():
            return HttpResponse(
                json.dumps({'message': message}),
                content_type="application=json"
            )
        else:
            return redirect(reverse_lazy("dashboard.views.detail",
                            kwargs={'pk': self.object.pk}))

    def __add_port(self, request):
        object = self.get_object()
        if (not object.has_level(request.user, 'owner') or
                not request.user.has_perm('vm.config_ports')):
            raise PermissionDenied()

        port = request.POST.get("port")
        proto = request.POST.get("proto")

        try:
            error = None
            interfaces = object.interface_set.all()
            host = Host.objects.get(pk=request.POST.get("host_pk"),
                                    interface__in=interfaces)
            host.add_port(proto, private=port)
        except Host.DoesNotExist:
            logger.error('Tried to add port to nonexistent host %d. User: %s. '
                         'Instance: %s', request.POST.get("host_pk"),
                         unicode(request.user), object)
            raise PermissionDenied()
        except ValueError:
            error = _("There is a problem with your input!")
        except Exception as e:
            error = _("Unknown error.")
            logger.error(e)

        if request.is_ajax():
            pass
        else:
            if error:
                messages.error(request, error)
            return redirect(reverse_lazy("dashboard.views.detail",
                                         kwargs={'pk': self.get_object().pk}))

    def __new_network(self, request):
        self.object = self.get_object()
        if not self.object.has_level(request.user, 'owner'):
            raise PermissionDenied()

        vlan = Vlan.objects.get(pk=request.POST.get("new_network_vlan"))
        if not vlan.has_level(request.user, 'user'):
            raise PermissionDenied()
        try:
            Interface.create(vlan=vlan, instance=self.object,
                             managed=vlan.managed, owner=request.user)
            messages.success(request, _("Successfully added new interface!"))
        except Exception, e:
            error = u' '.join(e.messages)
            messages.error(request, error)

        return redirect("%s#network" % reverse_lazy(
            "dashboard.views.detail", kwargs={'pk': self.object.pk}))

    def __save_as(self, request):
        self.object = self.get_object()
        if not self.object.has_level(request.user, 'owner'):
            raise PermissionDenied()

        date = datetime.now().strftime("%Y-%m-%d %H:%M")
        new_name = "Saved from %s (#%d) at %s" % (
            self.object.name, self.object.pk, date
        )
        self.object.save_as_template_async(name=new_name,
                                           user=request.user)
        messages.success(request, _("Saving instance as template!"))
        return redirect("%s#activity" % self.object.get_absolute_url())

    def __shut_down(self, request):
        self.object = self.get_object()
        if not self.object.has_level(request.user, 'owner'):
            raise PermissionDenied()

        self.object.shutdown_async(request.user)
        return redirect("%s#activity" % self.object.get_absolute_url())

    def __sleep(self, request):
        self.object = self.get_object()
        if not self.object.has_level(request.user, 'owner'):
            raise PermissionDenied()

        self.object.sleep_async(request.user)
        return redirect("%s#activity" % self.object.get_absolute_url())

    def __wake_up(self, request):
        self.object = self.get_object()
        if not self.object.has_level(request.user, 'owner'):
            raise PermissionDenied()

        self.object.wake_up_async(request.user)
        return redirect("%s#activity" % self.object.get_absolute_url())

    def __deploy(self, request):
        self.object = self.get_object()
        if not self.object.has_level(request.user, 'owner'):
            raise PermissionDenied()

        self.object.deploy_async(request.user)
        return redirect("%s#activity" % self.object.get_absolute_url())

    def __reset(self, request):
        self.object = self.get_object()
        if not self.object.has_level(request.user, 'owner'):
            raise PermissionDenied()

        self.object.reset_async(request.user)
        return redirect("%s#activity" % self.object.get_absolute_url())

    def __reboot(self, request):
        self.object = self.get_object()
        if not self.object.has_level(request.user, 'owner'):
            raise PermissionDenied()

        self.object.reboot_async(request.user)
        return redirect("%s#activity" % self.object.get_absolute_url())

    def __shut_off(self, request):
        self.object = self.get_object()
        if not self.object.has_level(request.user, 'owner'):
            raise PermissionDenied()

        self.object.shut_off_async(request.user)
        return redirect("%s#activity" % self.object.get_absolute_url())


class NodeDetailView(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
    template_name = "dashboard/node-detail.html"
    model = Node
    form = None
    form_class = TraitForm

    def get_context_data(self, form=None, **kwargs):
        if form is None:
            form = self.form_class()
        context = super(NodeDetailView, self).get_context_data(**kwargs)
        instances = Instance.active.filter(node=self.object)
        context['table'] = NodeVmListTable(instances)
        na = NodeActivity.objects.filter(
            node=self.object, parent=None
        ).order_by('-started').select_related()
        context['activities'] = na
        context['trait_form'] = form
        context['graphite_enabled'] = (
            NodeGraphView.get_graphite_url() is not None)
        return context

    def post(self, request, *args, **kwargs):
        print request.POST
        if request.POST.get('new_name'):
            return self.__set_name(request)
        if request.POST.get('change_status') is not None:
            return self.__set_status(request)
        if request.POST.get('to_remove'):
            return self.__remove_trait(request)
        return redirect(reverse_lazy("dashboard.views.node-detail",
                                     kwargs={'pk': self.get_object().pk}))

    def __set_name(self, request):
        self.object = self.get_object()
        new_name = request.POST.get("new_name")
        Node.objects.filter(pk=self.object.pk).update(
            **{'name': new_name})

        success_message = _("Node successfully renamed!")
        if request.is_ajax():
            response = {
                'message': success_message,
                'new_name': new_name,
                'node_pk': self.object.pk
            }
            return HttpResponse(
                json.dumps(response),
                content_type="application/json"
            )
        else:
            messages.success(request, success_message)
            return redirect(reverse_lazy("dashboard.views.node-detail",
                                         kwargs={'pk': self.object.pk}))

    def __set_status(self, request):
        self.object = self.get_object()
        if not self.object.enabled:
            self.object.enable(user=request.user)
        else:
            self.object.disable(user=request.user)
        success_message = _("Node successfully changed status!")
        if request.is_ajax():
            response = {
                'message': success_message,
                'node_pk': self.object.pk
            }
            return HttpResponse(
                json.dumps(response),
                content_type="application/json"
            )
        else:
            messages.success(request, success_message)
            return redirect(reverse_lazy("dashboard.views.node-detail",
                                         kwargs={'pk': self.object.pk}))

    def __remove_trait(self, request):
        try:
            to_remove = request.POST.get('to_remove')
            self.object = self.get_object()
            self.object.traits.remove(to_remove)
            message = u"Success"
        except:  # note this won't really happen
            message = u"Not success"

        if request.is_ajax():
            return HttpResponse(
                json.dumps({'message': message}),
                content_type="application/json"
            )
        else:
            return redirect(self.object.get_absolute_url())


class GroupDetailView(CheckedDetailView):
    template_name = "dashboard/group-detail.html"
    model = Group

    def get_has_level(self):
        return self.object.profile.has_level

    def get_context_data(self, **kwargs):
        context = super(GroupDetailView, self).get_context_data(**kwargs)
        context['group'] = self.object
        context['users'] = self.object.user_set.all()
        context['acl'] = get_group_acl_data(self.object)
        return context

    def post(self, request, *args, **kwargs):

        if request.POST.get('new_name'):
            return self.__set_name(request)

    def __set_name(self, request):
        self.object = self.get_object()
        new_name = request.POST.get("new_name")
        Group.objects.filter(pk=self.object.pk).update(
            **{'name': new_name})

        success_message = _("Group successfully renamed!")
        if request.is_ajax():
            response = {
                'message': success_message,
                'new_name': new_name,
                'group_pk': self.object.pk
            }
            return HttpResponse(
                json.dumps(response),
                content_type="application/json"
            )
        else:
            messages.success(request, success_message)
            return redirect(reverse_lazy("dashboard.views.group-detail",
                                         kwargs={'pk': self.object.pk}))


class AclUpdateView(LoginRequiredMixin, View, SingleObjectMixin):

    def post(self, request, *args, **kwargs):
        instance = self.get_object()
        if not (instance.has_level(request.user, "owner") or
                getattr(instance, 'owner', None) == request.user):
            logger.warning('Tried to set permissions of %s by non-owner %s.',
                           unicode(instance), unicode(request.user))
            raise PermissionDenied()
        self.set_levels(request, instance)
        self.add_levels(request, instance)
        return redirect(instance)

    def set_levels(self, request, instance):
        for key, value in request.POST.items():
            m = re.match('perm-([ug])-(\d+)', key)
            if m:
                typ, id = m.groups()
                entity = {'u': User, 'g': Group}[typ].objects.get(id=id)
                if getattr(instance, "owner", None) == entity:
                    logger.info("Tried to set owner's acl level for %s by %s.",
                                unicode(instance), unicode(request.user))
                    continue
                instance.set_level(entity, value)
                logger.info("Set %s's acl level for %s to %s by %s.",
                            unicode(entity), unicode(instance),
                            value, unicode(request.user))

    def add_levels(self, request, instance):
        name = request.POST['perm-new-name']
        value = request.POST['perm-new']
        if not name:
            return
        try:
            entity = User.objects.get(username=name)
        except User.DoesNotExist:
            entity = None
            try:
                entity = Group.objects.get(name=name)
            except Group.DoesNotExist:
                warning(request, _('User or group "%s" not found.') % name)
                return

        instance.set_level(entity, value)
        logger.info("Set %s's new acl level for %s to %s by %s.",
                    unicode(entity), unicode(instance),
                    value, unicode(request.user))


class TemplateAclUpdateView(AclUpdateView):
    model = InstanceTemplate

    def post(self, request, *args, **kwargs):
        template = self.get_object()
        if not (template.has_level(request.user, "owner") or
                getattr(template, 'owner', None) == request.user):
            logger.warning('Tried to set permissions of %s by non-owner %s.',
                           unicode(template), unicode(request.user))
            raise PermissionDenied()

        name = request.POST['perm-new-name']
        if (User.objects.filter(username=name).count() +
                Group.objects.filter(name=name).count() < 1
                and len(name) > 0):
            warning(request, _('User or group "%s" not found.') % name)
        else:
            self.set_levels(request, template)
            self.add_levels(request, template)

            post_for_disk = request.POST.copy()
            post_for_disk['perm-new'] = 'user'
            request.POST = post_for_disk
            for d in template.disks.all():
                self.add_levels(request, d)

        return redirect(reverse("dashboard.views.template-detail",
                                kwargs=self.kwargs))


class GroupAclUpdateView(AclUpdateView):
    model = Group

    def post(self, request, *args, **kwargs):
        instance = self.get_object().profile
        if not (instance.has_level(request.user, "owner") or
                getattr(instance, 'owner', None) == request.user):
            logger.warning('Tried to set permissions of %s by non-owner %s.',
                           unicode(instance), unicode(request.user))
            raise PermissionDenied()
        name = request.POST['perm-new-name']
        if (User.objects.filter(username=name).count() +
                Group.objects.filter(name=name).count() < 1
                and len(name) > 0):
            warning(request, _('User or group "%s" not found.') % name)
        else:
            self.set_levels(request, instance)
            self.add_levels(request, instance)
#        return redirect(self.profile)
        return redirect(reverse("dashboard.views.group-detail",
                                kwargs=self.kwargs))

    def repost(self, request, *args, **kwargs):
        group = self.get_object()
        if not (group.profile.has_level(request.user, "owner") or
                getattr(group.profile, 'owner', None) == request.user):
            logger.warning('Tried to set permissions of %s by non-owner %s.',
                           unicode(group), unicode(request.user))
            raise PermissionDenied()

        name = request.POST['perm-new-name']
        if (User.objects.filter(username=name).count() +
                Group.objects.filter(name=name).count() < 1
                and len(name) > 0):
            warning(request, _('User or group "%s" not found.') % name)
        else:
            self.set_levels(request, group.profile)
            self.add_levels(request, group.profile)

        return redirect(reverse("dashboard.views.group-detail",
                                kwargs=self.kwargs))


class TemplateCreate(SuccessMessageMixin, CreateView):
    model = InstanceTemplate
    form_class = TemplateForm
    template_name = "dashboard/template-create.html"
    success_message = _("Successfully created a new template!")

    def get(self, *args, **kwargs):
        if not self.request.user.has_perm('vm.create_template'):
            raise PermissionDenied()

        self.parent = self.request.GET.get("parent")
        return super(TemplateCreate, self).get(*args, **kwargs)

    def get_form_kwargs(self):
        kwargs = super(TemplateCreate, self).get_form_kwargs()
        kwargs['parent'] = getattr(self, "parent", None)
        kwargs['user'] = self.request.user
        return kwargs

    def post(self, request, *args, **kwargs):
        if not self.request.user.has_perm('vm.create_template'):
            raise PermissionDenied()

        form = self.form_class(request.POST, user=request.user)
        if not form.is_valid():
            return self.get(request, form, *args, **kwargs)
        else:
            post = form.cleaned_data

            networks = self.__create_networks(post.pop("networks"))
            req_traits = post.pop("req_traits")
            tags = post.pop("tags")
            post['pw'] = User.objects.make_random_password()
            post.pop("parent")
            post['max_ram_size'] = post['ram_size']
            inst = Instance.create(params=post, disks=[], networks=networks,
                                   tags=tags, req_traits=req_traits)
            messages.success(request, _("Your disk has been created, "
                                        "you can now add disks to it!"))
            return redirect("%s#resources" % inst.get_absolute_url())

        return super(TemplateCreate, self).post(self, request, args, kwargs)

    def __create_networks(self, vlans):
        networks = []
        for v in vlans:
            networks.append(InterfaceTemplate(vlan=v, managed=v.managed))
        return networks

    def get_success_url(self):
        return reverse_lazy("dashboard.views.template-list")


class TemplateDetail(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
    model = InstanceTemplate
    template_name = "dashboard/template-edit.html"
    form_class = TemplateForm
    success_message = _("Successfully modified template!")

    def get(self, request, *args, **kwargs):
        template = InstanceTemplate.objects.get(pk=kwargs['pk'])
        if not template.has_level(request.user, 'user'):
            raise PermissionDenied()
        if request.is_ajax():
            template = {
                'num_cores': template.num_cores,
                'ram_size': template.ram_size,
                'priority': template.priority,
                'arch': template.arch,
                'description': template.description,
                'system': template.system,
                'name': template.name,
                'disks': [{'pk': d.pk, 'name': d.name}
                          for d in template.disks.all()],
                'network': [
                    {'vlan_pk': i.vlan.pk, 'vlan': i.vlan.name,
                     'managed': i.managed}
                    for i in InterfaceTemplate.objects.filter(
                        template=self.get_object()).all()
                ]
            }
            return HttpResponse(json.dumps(template),
                                content_type="application/json")
        else:
            return super(TemplateDetail, self).get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        obj = self.get_object()
        context = super(TemplateDetail, self).get_context_data(**kwargs)
        context['acl'] = get_vm_acl_data(obj)
        context['disks'] = obj.disks.all()
        context['disk_add_form'] = DiskAddForm(
            user=self.request.user,
            is_template=True,
            object_pk=obj.pk,
            prefix="disk",
        )
        return context

    def get_success_url(self):
        return reverse_lazy("dashboard.views.template-detail",
                            kwargs=self.kwargs)

    def post(self, request, *args, **kwargs):
        template = self.get_object()
        if not template.has_level(request.user, 'owner'):
            raise PermissionDenied()
        for disk in self.get_object().disks.all():
            if not disk.has_level(request.user, 'user'):
                raise PermissionDenied()
        return super(TemplateDetail, self).post(self, request, args, kwargs)

    def get_form_kwargs(self):
        kwargs = super(TemplateDetail, self).get_form_kwargs()
        kwargs['user'] = self.request.user
        return kwargs


class TemplateList(LoginRequiredMixin, SingleTableView):
    template_name = "dashboard/template-list.html"
    model = InstanceTemplate
    table_class = TemplateListTable
    table_pagination = False

    def get_context_data(self, *args, **kwargs):
        context = super(TemplateList, self).get_context_data(*args, **kwargs)
        context['lease_table'] = LeaseListTable(Lease.objects.all())
        return context

    def get_queryset(self):
        logger.debug('TemplateList.get_queryset() called. User: %s',
                     unicode(self.request.user))
        return InstanceTemplate.get_objects_with_level(
            'user', self.request.user).all()


class TemplateDelete(LoginRequiredMixin, DeleteView):
    model = InstanceTemplate

    def get_success_url(self):
        return reverse("dashboard.views.template-list")

    def get_template_names(self):
        if self.request.is_ajax():
            return ['dashboard/confirm/ajax-delete.html']
        else:
            return ['dashboard/confirm/base-delete.html']

    def delete(self, request, *args, **kwargs):
        object = self.get_object()
        if not object.has_level(request.user, 'owner'):
            raise PermissionDenied()

        object.delete()
        success_url = self.get_success_url()
        success_message = _("Template successfully deleted!")

        if request.is_ajax():
            return HttpResponse(
                json.dumps({'message': success_message}),
                content_type="application/json",
            )
        else:
            messages.success(request, success_message)
            return HttpResponseRedirect(success_url)


class VmList(LoginRequiredMixin, ListView):
    template_name = "dashboard/vm-list.html"

    def get(self, *args, **kwargs):
        if self.request.is_ajax():
            favs = Instance.objects.filter(
                favourite__user=self.request.user).values_list('pk', flat=True)
            instances = Instance.get_objects_with_level(
                'user', self.request.user).filter(
                destroyed_at=None).all()
            instances = [{
                'pk': i.pk,
                'name': i.name,
                'state': i.state,
                'fav': i.pk in favs} for i in instances]
            return HttpResponse(
                json.dumps(list(instances)),  # instances is ValuesQuerySet
                content_type="application/json",
            )
        else:
            return super(VmList, self).get(*args, **kwargs)

    def get_queryset(self):
        logger.debug('VmList.get_queryset() called. User: %s',
                     unicode(self.request.user))
        queryset = Instance.get_objects_with_level(
            'user', self.request.user).filter(destroyed_at=None)
        s = self.request.GET.get("s")
        if s:
            queryset = queryset.filter(name__icontains=s)
        return queryset.select_related('owner', 'node')


class NodeList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
    template_name = "dashboard/node-list.html"
    table_class = NodeListTable
    table_pagination = False

    def get_queryset(self):
        return Node.objects.annotate(
            number_of_VMs=Count('instance_set')).select_related('host')


class GroupList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
    template_name = "dashboard/group-list.html"
    model = Group
    table_class = GroupListTable
    table_pagination = False


class GroupUserDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):

    """This stuff deletes the group.
    """
    model = User
    template_name = "dashboard/confirm/base-delete.html"

    def get_template_names(self):
        if self.request.is_ajax():
            return ['dashboard/confirm/ajax-delete.html']
        else:
            return ['dashboard/confirm/base-delete.html']

    def get_context_data(self, **kwargs):
        # this is redundant now, but if we wanna add more to print
        # we'll need this
        context = super(GroupUserDelete, self).get_context_data(**kwargs)
        return context

    # github.com/django/django/blob/master/django/views/generic/edit.py#L245
    def delete(self, request, *args, **kwargs):
        object = self.get_object()

        object.delete()
        success_url = self.get_success_url()
        success_message = _("Group successfully deleted!")

        if request.is_ajax():
            if request.POST.get('redirect').lower() == "true":
                messages.success(request, success_message)
            return HttpResponse(
                json.dumps({'message': success_message}),
                content_type="application/json",
            )
        else:
            messages.success(request, success_message)
            return HttpResponseRedirect(success_url)

    def get_success_url(self):
        next = self.request.POST.get('next')
        if next:
            return next
        else:
            return reverse_lazy('dashboard.index')


class GroupDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):

    """This stuff deletes the group.
    """
    model = Group
    template_name = "dashboard/confirm/base-delete.html"

    def get_template_names(self):
        if self.request.is_ajax():
            return ['dashboard/confirm/ajax-delete.html']
        else:
            return ['dashboard/confirm/base-delete.html']

    def get_context_data(self, **kwargs):
        # this is redundant now, but if we wanna add more to print
        # we'll need this
        context = super(GroupDelete, self).get_context_data(**kwargs)
        return context

    # github.com/django/django/blob/master/django/views/generic/edit.py#L245
    def delete(self, request, *args, **kwargs):
        object = self.get_object()

        object.delete()
        success_url = self.get_success_url()
        success_message = _("Group successfully deleted!")

        if request.is_ajax():
            if request.POST.get('redirect').lower() == "true":
                messages.success(request, success_message)
            return HttpResponse(
                json.dumps({'message': success_message}),
                content_type="application/json",
            )
        else:
            messages.success(request, success_message)
            return HttpResponseRedirect(success_url)

    def get_success_url(self):
        next = self.request.POST.get('next')
        if next:
            return next
        else:
            return reverse_lazy('dashboard.index')


class VmCreate(LoginRequiredMixin, TemplateView):

    form_class = VmCustomizeForm
    form = None

    def get_template_names(self):
        if self.request.is_ajax():
            return ['dashboard/modal-wrapper.html']
        else:
            return ['dashboard/nojs-wrapper.html']

    def get(self, request, form=None, *args, **kwargs):
        form_error = form is not None
        template = (form.template.pk if form_error
                    else request.GET.get("template"))
        templates = InstanceTemplate.get_objects_with_level('user',
                                                            request.user)
        if form is None and template:
            form = self.form_class(user=request.user,
                                   template=templates.get(pk=template))

        context = self.get_context_data(**kwargs)
        if template:
            context.update({
                'template': 'dashboard/_vm-create-2.html',
                'box_title': _('Customize VM'),
                'ajax_title': False,
                'vm_create_form': form,
                'template_o': templates.get(pk=template),
            })
        else:
            context.update({
                'template': 'dashboard/_vm-create-1.html',
                'box_title': _('Create a VM'),
                'ajax_title': False,
                'templates': templates.all(),
            })
        return self.render_to_response(context)

    def __create_normal(self, request, *args, **kwargs):
        user = request.user
        template = InstanceTemplate.objects.get(
            pk=request.POST.get("template"))

        # permission check
        if not template.has_level(request.user, 'user'):
            raise PermissionDenied()

        inst = Instance.create_from_template(
            template=template, owner=user)
        return self.__deploy(request, inst)

    def __create_customized(self, request, *args, **kwargs):
        user = request.user
        form = self.form_class(
            request.POST, user=request.user,
            template=InstanceTemplate.objects.get(
                pk=request.POST.get("template")
            )
        )
        if not form.is_valid():
            return self.get(request, form, *args, **kwargs)
        post = form.cleaned_data

        template = InstanceTemplate.objects.get(pk=post['template'])
        # permission check
        if not template.has_level(user, 'user'):
            raise PermissionDenied()

        if request.user.has_perm('vm.set_resources'):
            ikwargs = {
                'name': post['name'],
                'num_cores': post['cpu_count'],
                'ram_size': post['ram_size'],
                'priority': post['cpu_priority'],
            }
            networks = [InterfaceTemplate(vlan=l, managed=l.managed)
                        for l in post['networks']]
            disks = post['disks']
            inst = Instance.create_from_template(
                template=template, owner=user, networks=networks,
                disks=disks, **ikwargs)
            return self.__deploy(request, inst)
        else:
            raise PermissionDenied()

    def __deploy(self, request, instance, *args, **kwargs):
        instance.deploy_async(user=request.user)
        messages.success(request, _('VM successfully created!'))
        path = instance.get_absolute_url()
        if request.is_ajax():
            return HttpResponse(json.dumps({'redirect': path}),
                                content_type="application/json")
        else:
            return redirect("%s#activity" % path)

    def post(self, request, *args, **kwargs):
        user = request.user

        # limit chekcs
        try:
            limit = user.profile.instance_limit
        except Exception as e:
            logger.debug('No profile or instance limit: %s', e)
        else:
            current = Instance.active.filter(owner=user).count()
            logger.debug('current use: %d, limit: %d', current, limit)
            if limit < current:
                messages.error(request,
                               _('Instance limit (%d) exceeded.') % limit)
                if request.is_ajax():
                    return HttpResponse(json.dumps({'redirect': '/'}),
                                        content_type="application/json")
                else:
                    return redirect('/')

        create_func = (self.__create_normal if
                       request.POST.get("customized") is None else
                       self.__create_customized)

        return create_func(request, *args, **kwargs)


class NodeCreate(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView):

    form_class = HostForm
    hostform = None

    formset_class = inlineformset_factory(Host, Node, form=NodeForm, extra=1)
    formset = None

    def get_template_names(self):
        if self.request.is_ajax():
            return ['dashboard/modal-wrapper.html']
        else:
            return ['dashboard/nojs-wrapper.html']

    def get(self, request, hostform=None, formset=None, *args, **kwargs):
        if hostform is None:
            hostform = self.form_class()
        if formset is None:
            formset = self.formset_class(instance=Host())
        context = self.get_context_data(**kwargs)
        context.update({
            'template': 'dashboard/node-create.html',
            'box_title': 'Create a Node',
            'hostform': hostform,
            'formset': formset,

        })
        return self.render_to_response(context)

    def get_context_data(self, **kwargs):
        context = super(NodeCreate, self).get_context_data(**kwargs)
        # TODO acl
        context.update({
        })

        return context

    # TODO handle not ajax posts
    def post(self, request, *args, **kwargs):
        if not self.request.user.is_authenticated():
            raise PermissionDenied()

        hostform = self.form_class(request.POST)
        formset = self.formset_class(request.POST, Host())
        if not hostform.is_valid():
            return self.get(request, hostform, formset, *args, **kwargs)
        hostform.setowner(request.user)
        savedform = hostform.save(commit=False)
        formset = self.formset_class(request.POST, instance=savedform)
        if not formset.is_valid():
            return self.get(request, hostform, formset, *args, **kwargs)

        savedform.save()
        nodemodel = formset.save()
        messages.success(request, _('Node successfully created!'))
        path = nodemodel[0].get_absolute_url()
        if request.is_ajax():
            return HttpResponse(json.dumps({'redirect': path}),
                                content_type="application/json")
        else:
            return redirect(path)


class VmDelete(LoginRequiredMixin, DeleteView):
    model = Instance
    template_name = "dashboard/confirm/base-delete.html"

    def get_template_names(self):
        if self.request.is_ajax():
            return ['dashboard/confirm/ajax-delete.html']
        else:
            return ['dashboard/confirm/base-delete.html']

    def get_success_url(self):
        next = self.request.POST.get('next')
        if next:
            return next
        else:
            return reverse_lazy('dashboard.index')

    def get_context_data(self, **kwargs):
        object = self.get_object()
        if not object.has_level(self.request.user, 'owner'):
            raise PermissionDenied()
        # this is redundant now, but if we wanna add more to print
        # we'll need this
        context = super(VmDelete, self).get_context_data(**kwargs)
        return context

    # github.com/django/django/blob/master/django/views/generic/edit.py#L245
    def delete(self, request, *args, **kwargs):
        object = self.get_object()
        if not object.has_level(request.user, 'owner'):
            raise PermissionDenied()

        object.destroy_async(user=request.user)
        success_url = self.get_success_url()
        success_message = _("VM successfully deleted!")

        if request.is_ajax():
            if request.POST.get('redirect').lower() == "true":
                messages.success(request, success_message)
            return HttpResponse(
                json.dumps({'message': success_message}),
                content_type="application/json",
            )
        else:
            messages.success(request, success_message)
            return HttpResponseRedirect(success_url)


class NodeDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):

    """This stuff deletes the node.
    """
    model = Node
    template_name = "dashboard/confirm/base-delete.html"

    def get_template_names(self):
        if self.request.is_ajax():
            return ['dashboard/confirm/ajax-delete.html']
        else:
            return ['dashboard/confirm/base-delete.html']

    def get_context_data(self, **kwargs):
        # this is redundant now, but if we wanna add more to print
        # we'll need this
        context = super(NodeDelete, self).get_context_data(**kwargs)
        return context

    # github.com/django/django/blob/master/django/views/generic/edit.py#L245
    def delete(self, request, *args, **kwargs):
        object = self.get_object()

        object.delete()
        success_url = self.get_success_url()
        success_message = _("Node successfully deleted!")

        if request.is_ajax():
            if request.POST.get('redirect').lower() == "true":
                messages.success(request, success_message)
            return HttpResponse(
                json.dumps({'message': success_message}),
                content_type="application/json",
            )
        else:
            messages.success(request, success_message)
            return HttpResponseRedirect(success_url)

    def get_success_url(self):
        next = self.request.POST.get('next')
        if next:
            return next
        else:
            return reverse_lazy('dashboard.index')


class NodeAddTraitView(SuperuserRequiredMixin, DetailView):
    model = Node
    template_name = "dashboard/node-add-trait.html"

    def get_success_url(self):
        next = self.request.GET.get('next')
        if next:
            return next
        else:
            return self.object.get_absolute_url()

    def get_context_data(self, **kwargs):
        self.object = self.get_object()
        context = super(NodeAddTraitView, self).get_context_data(**kwargs)
        context['form'] = (TraitForm(self.request.POST) if self.request.POST
                           else TraitForm())
        return context

    def post(self, request, pk, *args, **kwargs):
        context = self.get_context_data(**kwargs)
        form = context['form']
        if form.is_valid():
            node = self.object
            n = form.cleaned_data['name']
            trait, created = Trait.objects.get_or_create(name=n)
            node.traits.add(trait)
            success_message = _("Trait successfully added to node.")
            messages.success(request, success_message)
            return redirect(self.get_success_url())
        else:
            return self.get(self, request, pk, *args, **kwargs)


class NodeStatus(LoginRequiredMixin, SuperuserRequiredMixin, DetailView):
    template_name = "dashboard/confirm/node-status.html"
    model = Node

    def get_template_names(self):
        if self.request.is_ajax():
            return ['dashboard/confirm/ajax-node-status.html']
        else:
            return ['dashboard/confirm/node-status.html']

    def get_success_url(self):
        next = self.request.GET.get('next')
        if next:
            return next
        else:
            return reverse_lazy("dashboard.views.node-detail",
                                kwargs={'pk': self.object.pk})

    def get_context_data(self, **kwargs):
        context = super(NodeStatus, self).get_context_data(**kwargs)
        if self.object.enabled:
            context['status'] = "disable"
        else:
            context['status'] = "enable"
        return context

    def post(self, request, *args, **kwargs):
        if request.POST.get('change_status') is not None:
            return self.__set_status(request)
        return redirect(reverse_lazy("dashboard.views.node-detail",
                                     kwargs={'pk': self.get_object().pk}))

    def __set_status(self, request):
        self.object = self.get_object()
        if not self.object.enabled:
            self.object.enable(user=request.user)
        else:
            self.object.disable(user=request.user)

        success_message = _("Node successfully changed status!")

        if request.is_ajax():
            response = {
                'message': success_message,
                'node_pk': self.object.pk
            }
            return HttpResponse(
                json.dumps(response),
                content_type="application/json"
            )
        else:
            messages.success(request, success_message)
            return redirect(self.get_success_url())


class PortDelete(LoginRequiredMixin, DeleteView):
    model = Rule
    pk_url_kwarg = 'rule'

    def get_template_names(self):
        if self.request.is_ajax():
            return ['dashboard/confirm/ajax-delete.html']
        else:
            return ['dashboard/confirm/base-delete.html']

    def get_context_data(self, **kwargs):
        context = super(PortDelete, self).get_context_data(**kwargs)
        rule = kwargs.get('object')
        instance = rule.host.interface_set.get().instance
        context['title'] = _("Port delete confirmation")
        context['text'] = _("Are you sure you want to close %(port)d/"
                            "%(proto)s on %(vm)s?" % {'port': rule.dport,
                                                      'proto': rule.proto,
                                                      'vm': instance})
        return context

    def delete(self, request, *args, **kwargs):
        rule = Rule.objects.get(pk=kwargs.get("rule"))
        instance = rule.host.interface_set.get().instance
        if not instance.has_level(request.user, 'owner'):
            raise PermissionDenied()

        super(PortDelete, self).delete(request, *args, **kwargs)

        success_url = self.get_success_url()
        success_message = _("Port successfully removed!")

        if request.is_ajax():
            return HttpResponse(
                json.dumps({'message': success_message}),
                content_type="application/json",
            )
        else:
            messages.success(request, success_message)
            return HttpResponseRedirect("%s#network" % success_url)

    def get_success_url(self):
        return reverse_lazy('dashboard.views.detail',
                            kwargs={'pk': self.kwargs.get("pk")})


class VmMassDelete(LoginRequiredMixin, View):
    def get(self, request, *args, **kwargs):
        vms = request.GET.getlist('v[]')
        objects = Instance.objects.filter(pk__in=vms)
        return render(request, "dashboard/confirm/mass-delete.html",
                      {'objects': objects})

    def post(self, request, *args, **kwargs):
        vms = request.POST.getlist('vms')
        names = []
        if vms is not None:
            for i in Instance.objects.filter(pk__in=vms):
                if not i.has_level(request.user, 'owner'):
                    logger.info('Tried to delete instance #%d without owner '
                                'permission by %s.', i.pk,
                                unicode(request.user))
                    raise PermissionDenied()  # no need for rollback or proper
                                            # error message, this can't
                                            # normally happen.
                i.destroy_async(request.user)
                names.append(i.name)

        success_message = _("Mass delete complete, the following VMs were "
                            "deleted: %s!") % u', '.join(names)

        # we can get this only via AJAX ...
        if request.is_ajax():
            return HttpResponse(
                json.dumps({'message': success_message}),
                content_type="application/json"
            )
        else:
            messages.success(request, success_message)
            next = request.GET.get('next')
            return redirect(next if next else reverse_lazy('dashboard.index'))


class LeaseCreate(LoginRequiredMixin, SuperuserRequiredMixin,
                  SuccessMessageMixin, CreateView):
    model = Lease
    form_class = LeaseForm
    template_name = "dashboard/lease-create.html"
    success_message = _("Successfully created a new lease!")

    def get_success_url(self):
        return reverse_lazy("dashboard.views.template-list")


class LeaseDetail(LoginRequiredMixin, SuperuserRequiredMixin,
                  SuccessMessageMixin, UpdateView):
    model = Lease
    form_class = LeaseForm
    template_name = "dashboard/lease-edit.html"
    success_message = _("Successfully modified lease!")

    def get_success_url(self):
        return reverse_lazy("dashboard.views.lease-detail", kwargs=self.kwargs)


class LeaseDelete(LoginRequiredMixin, SuperuserRequiredMixin, DeleteView):
    model = Lease

    def get_success_url(self):
        return reverse("dashboard.views.template-list")

    def get_template_names(self):
        if self.request.is_ajax():
            return ['dashboard/confirm/ajax-delete.html']
        else:
            return ['dashboard/confirm/base-delete.html']

    def delete(self, request, *args, **kwargs):
        object = self.get_object()

        object.delete()
        success_url = self.get_success_url()
        success_message = _("Lease successfully deleted!")

        if request.is_ajax():
            return HttpResponse(
                json.dumps({'message': success_message}),
                content_type="application/json",
            )
        else:
            messages.success(request, success_message)
            return HttpResponseRedirect(success_url)


@require_GET
def vm_activity(request, pk):
    instance = Instance.objects.get(pk=pk)
    if not instance.has_level(request.user, 'owner'):
        raise PermissionDenied()

    response = {}
    only_state = request.GET.get("only_state")

    response['state'] = instance.state
    if only_state is not None and only_state == "false":  # instance activity
        context = {
            'activities': InstanceActivity.objects.filter(
                instance=instance, parent=None
            ).order_by('-started').select_related()
        }

        activities = render_to_string(
            "dashboard/vm-detail/_activity-timeline.html",
            RequestContext(request, context),
        )
        response['activities'] = activities

    return HttpResponse(
        json.dumps(response),
        content_type="application/json"
    )


class FavouriteView(TemplateView):

    def post(self, *args, **kwargs):
        user = self.request.user
        vm = Instance.objects.get(pk=self.request.POST.get("vm"))
        try:
            Favourite.objects.get(instance=vm, user=user).delete()
            return HttpResponse("Deleted!")
        except Favourite.DoesNotExist:
            Favourite(instance=vm, user=user).save()
            return HttpResponse("Added!")


class TransferOwnershipView(LoginRequiredMixin, DetailView):
    model = Instance
    template_name = 'dashboard/vm-detail/tx-owner.html'

    def post(self, request, *args, **kwargs):
        try:
            new_owner = search_user(request.POST['name'])
        except User.DoesNotExist:
            messages.error(request, _('Can not find specified user.'))
            return self.get(request, *args, **kwargs)
        except KeyError:
            raise SuspiciousOperation()

        obj = self.get_object()
        if not (obj.owner == request.user or
                request.user.is_superuser):
            raise PermissionDenied()

        token = signing.dumps((obj.pk, new_owner.pk),
                              salt=TransferOwnershipConfirmView.get_salt())
        token_path = reverse(
            'dashboard.views.vm-transfer-ownership-confirm', args=[token])
        try:
            new_owner.profile.notify(
                _('Ownership offer'),
                'dashboard/notifications/ownership-offer.html',
                {'instance': obj, 'token': token_path})
        except Profile.DoesNotExist:
            messages.error(request, _('Can not notify selected user.'))
        else:
            messages.success(request,
                             _('User %s is notified about the offer.') % (
                                 unicode(new_owner), ))

        return redirect(reverse_lazy("dashboard.views.detail",
                                     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):
        class LoginNeeded(Exception):
            pass
        pk = int(pk)
        instance = get_object_or_404(Instance, pk=pk)

        try:
            if not request.user.is_authenticated() and key:
                try:
                    user = self.validate_key(pk, key)
                except signing.SignatureExpired:
                    messages.error(request, _(
                        'The token has expired, please log in.'))
                    raise LoginNeeded()
                self.key = key
            else:
                user = request.user
                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()

        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):  # noqa
        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):
    """User can accept an ownership offer."""

    max_age = 3 * 24 * 3600
    success_message = _("Ownership successfully transferred to you.")

    @classmethod
    def get_salt(cls):
        return unicode(cls)

    def get(self, request, key, *args, **kwargs):
        """Confirm ownership transfer based on token.
        """
        logger.debug('Confirm dialog for token %s.', key)
        try:
            instance, new_owner = self.get_instance(key, request.user)
        except PermissionDenied:
            messages.error(request, _('This token is for an other user.'))
            raise
        except SuspiciousOperation:
            messages.error(request, _('This token is invalid or has expired.'))
            raise PermissionDenied()
        return render(request,
                      "dashboard/confirm/base-transfer-ownership.html",
                      dictionary={'instance': instance, 'key': key})

    def post(self, request, key, *args, **kwargs):
        """Really transfer ownership based on token.
        """
        instance, owner = self.get_instance(key, request.user)

        old = instance.owner
        with instance_activity(code_suffix='ownership-transferred',
                               instance=instance, user=request.user):
            instance.owner = request.user
            instance.clean()
            instance.save()
        messages.success(request, self.success_message)
        logger.info('Ownership of %s transferred from %s to %s.',
                    unicode(instance), unicode(old), unicode(request.user))
        if old.profile:
            old.profile.notify(
                _('Ownership accepted'),
                'dashboard/notifications/ownership-accepted.html',
                {'instance': instance})
        return HttpResponseRedirect(instance.get_absolute_url())

    def get_instance(self, key, user):
        """Get object based on signed token.
        """
        try:
            instance, new_owner = (
                signing.loads(key, max_age=self.max_age,
                              salt=self.get_salt()))
        except (signing.BadSignature, ValueError, TypeError) as e:
            logger.error('Tried invalid token. Token: %s, user: %s. %s',
                         key, unicode(user), unicode(e))
            raise SuspiciousOperation()

        try:
            instance = Instance.objects.get(id=instance)
        except Instance.DoesNotExist as e:
            logger.error('Tried token to nonexistent instance %d. '
                         'Token: %s, user: %s. %s',
                         instance, key, unicode(user), unicode(e))
            raise Http404()

        if new_owner != user.pk:
            logger.error('%s (%d) tried the token for %s. Token: %s.',
                         unicode(user), user.pk, new_owner, key)
            raise PermissionDenied()
        return (instance, new_owner)


class GraphViewBase(LoginRequiredMixin, View):
    @staticmethod
    def get_graphite_url():
        graphite_host = getenv("GRAPHITE_HOST", None)
        graphite_port = getenv("GRAPHITE_PORT", None)

        if (graphite_host in ['', None] or graphite_port in ['', None]):
            logger.debug('GRAPHITE_HOST is empty.')
            return None

        return 'http://%s:%s' % (graphite_host, graphite_port)

    def get(self, request, pk, metric, time, *args, **kwargs):
        graphite_url = GraphViewBase.get_graphite_url()
        if graphite_url is None:
            raise Http404()

        if metric not in self.metrics.keys():
            raise SuspiciousOperation()

        try:
            instance = self.get_object(request, pk)
        except self.model.DoesNotExist:
            raise Http404()

        prefix = self.get_prefix(instance)
        target = self.metrics[metric] % {'prefix': prefix}
        title = self.get_title(instance, metric)
        params = {'target': target,
                  'from': '-%s' % time,
                  'title': title.encode('UTF-8'),
                  'width': '500',
                  'height': '200'}
        response = requests.post('%s/render/' % graphite_url, data=params)
        return HttpResponse(response.content, mimetype="image/png")

    def get_prefix(self, instance):
        raise NotImplementedError("Subclass must implement abstract method")

    def get_title(self, instance, metric):
        raise NotImplementedError("Subclass must implement abstract method")

    def get_object(self, request, pk):
        instance = self.model.objects.get(id=pk)
        if not instance.has_level(request.user, 'user'):
            raise PermissionDenied()
        return instance


class VmGraphView(GraphViewBase):
    metrics = {
        'cpu': ('cactiStyle(alias(nonNegativeDerivative(%(prefix)s.cpu.usage),'
                '"cpu usage (%%)"))'),
        'memory': ('cactiStyle(alias(%(prefix)s.memory.usage,'
                   '"memory usage (%%)"))'),
        'network': (
            'group('
            'aliasSub(nonNegativeDerivative(%(prefix)s.network.bytes_recv*),'
            ' ".*-(\d+)\\)", "out (vlan \\1)"),'
            'aliasSub(nonNegativeDerivative(%(prefix)s.network.bytes_sent*),'
            ' ".*-(\d+)\\)", "in (vlan \\1)"))'),
    }
    model = Instance

    def get_prefix(self, instance):
        return 'vm.%s' % instance.vm_name

    def get_title(self, instance, metric):
        return '%s (%s) - %s' % (instance.name, instance.vm_name, metric)


class NodeGraphView(SuperuserRequiredMixin, GraphViewBase):
    metrics = {
        'cpu': ('cactiStyle(alias(nonNegativeDerivative(%(prefix)s.cpu.times),'
                '"cpu usage (%%)"))'),
        'memory': ('cactiStyle(alias(%(prefix)s.memory.usage,'
                   '"memory usage (%%)"))'),
        'network': ('cactiStyle(aliasByMetric('
                    'nonNegativeDerivative(%(prefix)s.network.bytes_*)))'),
    }
    model = Node

    def get_prefix(self, instance):
        return 'circle.%s' % instance.name

    def get_title(self, instance, metric):
        return '%s - %s' % (instance.name, metric)

    def get_object(self, request, pk):
        return self.model.objects.get(id=pk)


class NotificationView(LoginRequiredMixin, TemplateView):

    def get_template_names(self):
        if self.request.is_ajax():
            return ['dashboard/_notifications-timeline.html']
        else:
            return ['dashboard/notifications.html']

    def get_context_data(self, *args, **kwargs):
        context = super(NotificationView, self).get_context_data(
            *args, **kwargs)
        # we need to convert it to list, otherwise it's gonna be
        # similar to a QuerySet and update everything to
        # read status after get
        n = 10 if self.request.is_ajax() else 1000
        context['notifications'] = list(
            self.request.user.notification_set.values()[:n])
        return context

    def get(self, *args, **kwargs):
        response = super(NotificationView, self).get(*args, **kwargs)
        un = self.request.user.notification_set.filter(status="new")
        for u in un:
            u.status = "read"
            u.save()
        return response


class VmMigrateView(SuperuserRequiredMixin, TemplateView):

    def get_template_names(self):
        if self.request.is_ajax():
            return ['dashboard/modal-wrapper.html']
        else:
            return ['dashboard/nojs-wrapper.html']

    def get(self, request, form=None, *args, **kwargs):
        context = self.get_context_data(**kwargs)
        vm = Instance.objects.get(pk=kwargs['pk'])
        context.update({
            'template': 'dashboard/_vm-migrate.html',
            'box_title': _('Migrate %(name)s' % {'name': vm.name}),
            'ajax_title': True,
            'vm': vm,
            'nodes': [n for n in Node.objects.filter(enabled=True)
                      if n.state == "ONLINE"]
        })
        return self.render_to_response(context)

    def post(self, *args, **kwargs):
        node = self.request.POST.get("node")
        vm = Instance.objects.get(pk=kwargs['pk'])

        if node:
            node = Node.objects.get(pk=node)
            vm.migrate_async(to_node=node, user=self.request.user)
        else:
            messages.error(self.request, _("You didn't select a node!"))

        return redirect("%s#activity" % vm.get_absolute_url())


def circle_login(request):
    authentication_form = CircleAuthenticationForm
    extra_context = {
        'saml2': hasattr(settings, "SAML_CONFIG")
    }
    response = login(request, authentication_form=authentication_form,
                     extra_context=extra_context)
    set_language_cookie(request, response)
    return response


class DiskAddView(TemplateView):

    def post(self, *args, **kwargs):
        is_template = self.request.POST.get("disk-is_template")
        object_pk = self.request.POST.get("disk-object_pk")
        is_template = int(is_template) == 1
        if is_template:
            obj = InstanceTemplate.objects.get(pk=object_pk)
        else:
            obj = Instance.objects.get(pk=object_pk)

        if not obj.has_level(self.request.user, 'owner'):
            raise PermissionDenied()

        form = DiskAddForm(
            self.request.POST,
            user=self.request.user,
            is_template=is_template, object_pk=object_pk,
            prefix="disk"
        )

        if form.is_valid():
            if form.cleaned_data.get("size"):
                messages.success(self.request, _("Disk successfully added!"))
            else:
                messages.success(self.request, _("Disk download started!"))
            form.save()
        else:
            error = "<br /> ".join(["<strong>%s</strong>: %s" %
                                    (title_filter(i[0]), i[1][0])
                                    for i in form.errors.items()])
            messages.error(self.request, error)

        if is_template:
            r = obj.get_absolute_url()
        else:
            r = obj.get_absolute_url()
            r = "%s#resources" % r
        return redirect(r)


class MyPreferencesView(UpdateView):

    model = Profile
    form_class = MyProfileForm

    def get_object(self, queryset=None):
        if self.request.user.is_anonymous():
            raise PermissionDenied()
        try:
            return self.request.user.profile
        except Profile.DoesNotExist:
            raise Http404(_("You don't have a profile."))

    def form_valid(self, form):
        response = super(MyPreferencesView, self).form_valid(form)
        set_language_cookie(self.request, response)
        return response


def set_language_cookie(request, response, lang=None):
    if lang is None:
        try:
            lang = request.user.profile.preferred_language
        except:
            return

    cname = getattr(settings, 'LANGUAGE_COOKIE_NAME', 'django_language')
    response.set_cookie(cname, lang, 365 * 86400)


class DiskRemoveView(DeleteView):
    model = Disk

    def get_template_names(self):
        if self.request.is_ajax():
            return ['dashboard/confirm/ajax-delete.html']
        else:
            return ['dashboard/confirm/base-delete.html']

    def get_context_data(self, **kwargs):
        context = super(DiskRemoveView, self).get_context_data(**kwargs)
        disk = self.get_object()
        app = disk.get_appliance()
        context['title'] = _("Disk remove confirmation")
        context['text'] = _("Are you sure you want to remove "
                            "<strong>%(disk)s</strong> from "
                            "<strong>%(app)s</strong>?" % {'disk': disk,
                                                           'app': app}
                            )
        return context

    def delete(self, request, *args, **kwargs):
        disk = self.get_object()
        if not disk.has_level(request.user, 'owner'):
            raise PermissionDenied()

        disk = self.get_object()
        app = disk.get_appliance()

        app.disks.remove(disk)
        disk.destroy()

        next_url = request.POST.get("next")
        success_url = next_url if next_url else app.get_absolute_url()
        success_message = _("Disk successfully removed!")

        if request.is_ajax():
            return HttpResponse(
                json.dumps({'message': success_message}),
                content_type="application/json",
            )
        else:
            messages.success(request, success_message)
            return HttpResponseRedirect("%s#resources" % success_url)


@require_GET
def get_disk_download_status(request, pk):
    disk = Disk.objects.get(pk=pk)
    if not disk.has_level(request.user, 'owner'):
        raise PermissionDenied()

    return HttpResponse(
        json.dumps({
            'percentage': disk.get_download_percentage(),
            'failed': disk.failed
        }),
        content_type="application/json",
    )