Commit 8355c1d3 by Kálmán Viktor

Merge branch 'master' into fix-graphs

Conflicts:
	circle/dashboard/views.py
parents 0892583d 0c2709fa
......@@ -149,8 +149,8 @@ class VmOperationViewTestCase(unittest.TestCase):
view = vm_ops['migrate']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.messages') as msg, \
patch('dashboard.views.get_object_or_404') as go4:
patch('dashboard.views.util.messages') as msg, \
patch('dashboard.views.vm.get_object_or_404') as go4:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.migrate = Instance._ops['migrate'](inst)
......@@ -160,14 +160,15 @@ class VmOperationViewTestCase(unittest.TestCase):
go4.return_value = MagicMock()
assert view.as_view()(request, pk=1234)['location']
assert not msg.error.called
assert go4.called
def test_migrate_failed(self):
request = FakeRequestFactory(POST={'node': 1}, superuser=True)
view = vm_ops['migrate']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.messages') as msg, \
patch('dashboard.views.get_object_or_404') as go4:
patch('dashboard.views.util.messages') as msg, \
patch('dashboard.views.vm.get_object_or_404') as go4:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.migrate = Instance._ops['migrate'](inst)
......@@ -178,13 +179,14 @@ class VmOperationViewTestCase(unittest.TestCase):
go4.return_value = MagicMock()
assert view.as_view()(request, pk=1234)['location']
assert msg.error.called
assert go4.called
def test_migrate_wo_permission(self):
request = FakeRequestFactory(POST={'node': 1}, superuser=False)
view = vm_ops['migrate']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.get_object_or_404') as go4:
patch('dashboard.views.vm.get_object_or_404') as go4:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.migrate = Instance._ops['migrate'](inst)
......@@ -194,6 +196,7 @@ class VmOperationViewTestCase(unittest.TestCase):
go4.return_value = MagicMock()
with self.assertRaises(PermissionDenied):
assert view.as_view()(request, pk=1234)['location']
assert go4.called
def test_migrate_template(self):
"""check if GET dialog's template can be rendered"""
......@@ -214,15 +217,13 @@ class VmOperationViewTestCase(unittest.TestCase):
view = vm_ops['save_as_template']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.messages') as msg, \
patch('dashboard.views.get_object_or_404') as go4:
patch('dashboard.views.util.messages') as msg:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.save_as_template = Instance._ops['save_as_template'](inst)
inst.save_as_template.async = MagicMock()
inst.has_level.return_value = True
go.return_value = inst
go4.return_value = MagicMock()
assert view.as_view()(request, pk=1234)
assert not msg.error.called
......@@ -232,15 +233,13 @@ class VmOperationViewTestCase(unittest.TestCase):
view = vm_ops['save_as_template']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.messages') as msg, \
patch('dashboard.views.get_object_or_404') as go4:
patch('dashboard.views.util.messages') as msg:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.save_as_template = Instance._ops['save_as_template'](inst)
inst.save_as_template.async = MagicMock()
inst.has_level.return_value = True
go.return_value = inst
go4.return_value = MagicMock()
assert view.as_view()(request, pk=1234)['location']
assert not msg.error.called
......@@ -306,25 +305,24 @@ class VmMassOperationViewTestCase(unittest.TestCase):
view = vm_mass_ops['migrate']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.messages') as msg, \
patch('dashboard.views.get_object_or_404') as go4:
patch('dashboard.views.util.messages') as msg, \
patch('dashboard.views.vm.messages') as msg2:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.migrate = Instance._ops['migrate'](inst)
inst.migrate.async = MagicMock()
inst.has_level.return_value = True
go.return_value = [inst]
go4.return_value = MagicMock()
assert view.as_view()(request, pk=1234)['location']
assert not msg.error.called
assert not msg2.error.called
def test_migrate_failed(self):
request = FakeRequestFactory(POST={'node': 1}, superuser=True)
view = vm_mass_ops['migrate']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.messages') as msg, \
patch('dashboard.views.get_object_or_404') as go4:
patch('dashboard.views.vm.messages') as msg:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.migrate = Instance._ops['migrate'](inst)
......@@ -332,7 +330,6 @@ class VmMassOperationViewTestCase(unittest.TestCase):
inst.migrate.async.side_effect = Exception
inst.has_level.return_value = True
go.return_value = [inst]
go4.return_value = MagicMock()
assert view.as_view()(request, pk=1234)['location']
assert msg.error.called
......@@ -340,15 +337,13 @@ class VmMassOperationViewTestCase(unittest.TestCase):
request = FakeRequestFactory(POST={'node': 1}, superuser=False)
view = vm_mass_ops['migrate']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.get_object_or_404') as go4:
with patch.object(view, 'get_object') as go:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.migrate = Instance._ops['migrate'](inst)
inst.migrate.async = MagicMock()
inst.has_level.return_value = True
go.return_value = [inst]
go4.return_value = MagicMock()
with self.assertRaises(PermissionDenied):
assert view.as_view()(request, pk=1234)['location']
......@@ -388,15 +383,13 @@ class RenewViewTest(unittest.TestCase):
view = vm_ops['renew']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.messages') as msg, \
patch('dashboard.views.get_object_or_404') as go4:
patch('dashboard.views.util.messages') as msg:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.renew = Instance._ops['renew'](inst)
inst.renew.async = MagicMock()
inst.has_level.return_value = True
go.return_value = inst
go4.return_value = MagicMock()
assert view.as_view()(request, pk=1234)
assert not msg.error.called
assert inst.renew.async.called_with(user=request.user, lease=None)
......@@ -408,15 +401,13 @@ class RenewViewTest(unittest.TestCase):
view = vm_ops['renew']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.messages') as msg, \
patch('dashboard.views.get_object_or_404') as go4:
patch('dashboard.views.util.messages') as msg:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.renew = Instance._ops['renew'](inst)
inst.renew.async = MagicMock()
inst.has_level.return_value = True
go.return_value = inst
go4.return_value = MagicMock()
assert view.as_view()(request, pk=1234)
assert not msg.error.called
......@@ -424,15 +415,13 @@ class RenewViewTest(unittest.TestCase):
request = FakeRequestFactory(authenticated=False)
view = vm_ops['renew']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.get_object_or_404') as go4:
with patch.object(view, 'get_object') as go:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.renew = Instance._ops['renew'](inst)
inst.renew.async = MagicMock()
inst.has_level.return_value = False
go.return_value = inst
go4.return_value = MagicMock()
self.assertIn('login',
view.as_view()(request, pk=1234)['location'])
......@@ -440,15 +429,13 @@ class RenewViewTest(unittest.TestCase):
request = FakeRequestFactory(has_perms_mock=True)
view = vm_ops['renew']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.get_object_or_404') as go4:
with patch.object(view, 'get_object') as go:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.renew = Instance._ops['renew'](inst)
inst.renew.async = MagicMock()
inst.has_level.return_value = False
go.return_value = inst
go4.return_value = MagicMock()
with self.assertRaises(PermissionDenied):
assert view.as_view()(request, pk=1234)
......@@ -456,15 +443,13 @@ class RenewViewTest(unittest.TestCase):
request = FakeRequestFactory(POST={'length': 1}, has_perms_mock=True)
view = vm_ops['renew']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.get_object_or_404') as go4:
with patch.object(view, 'get_object') as go:
inst = MagicMock(spec=Instance, pk=11)
inst._meta.object_name = "Instance"
inst.renew = Instance._ops['renew'](inst)
inst.renew.async = MagicMock()
inst.has_level.return_value = False
go.return_value = inst
go4.return_value = MagicMock()
with self.assertRaises(PermissionDenied):
assert view.as_view()(request, pk=1234)
......@@ -555,7 +540,7 @@ class AclUpdateViewTest(unittest.TestCase):
inst.has_level.assert_called_with('dummy', v)
def test_set_level_mod_owner(self):
with patch('dashboard.views.messages') as msg:
with patch('dashboard.views.util.messages') as msg:
request = FakeRequestFactory(POST={})
inst = MagicMock(spec=Instance)
......@@ -581,7 +566,7 @@ class AclUpdateViewTest(unittest.TestCase):
(None, 'user', ('user', 'operator'), False))
for old_level, new_level, allowed_levels, fail in data:
with patch('dashboard.views.messages') as msg:
with patch('dashboard.views.util.messages') as msg:
def has_level(user, level):
return level in allowed_levels
......@@ -606,7 +591,7 @@ class AclUpdateViewTest(unittest.TestCase):
def test_readd(self):
request = FakeRequestFactory(POST={'name': 'user0', 'level': 'user'})
with patch('dashboard.views.messages') as msg:
with patch('dashboard.views.util.messages') as msg:
with patch.object(AclUpdateView, 'get_object') as go:
view = AclUpdateView.as_view()
inst = MagicMock(spec=Instance)
......
......@@ -20,7 +20,6 @@ from django.conf.urls import patterns, url, include
import autocomplete_light
from vm.models import Instance
from .graph import VmGraphView, NodeGraphView, NodeListGraphView
from .views import (
AclUpdateView, FavouriteView, GroupAclUpdateView, GroupDelete,
GroupDetailView, GroupList, IndexView,
......@@ -47,6 +46,7 @@ from .views import (
GroupPermissionsView,
LeaseAclUpdateView,
ClientCheck, TokenLogin,
VmGraphView, NodeGraphView, NodeListGraphView,
)
autocomplete_light.autodiscover()
......
This source diff could not be displayed because it is too large. You can view the blob instead.
# flake8: noqa
# from .node import Node
# __all__ = [ ]
from group import *
from index import *
from node import *
from store import *
from template import *
from user import *
from util import *
from vm import *
from graph import *
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.
#
# CIRCLE is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals, absolute_import
import json
import logging
from itertools import chain
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.models import User, Group
from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse, reverse_lazy
from django.http import HttpResponse, Http404
from django.shortcuts import redirect
from django.utils.translation import ugettext as _
from django.views.generic import UpdateView, DeleteView, TemplateView
from braces.views import SuperuserRequiredMixin, LoginRequiredMixin
from django_tables2 import SingleTableView
from ..forms import (
AddGroupMemberForm, AclUserOrGroupAddForm, GroupPermissionForm,
GroupCreateForm, GroupProfileUpdateForm,
)
from ..models import FutureMember, GroupProfile
from ..tables import GroupListTable
from .util import CheckedDetailView, AclUpdateView, search_user, saml_available
logger = logging.getLogger(__name__)
class GroupCodeMixin(object):
@classmethod
def get_available_group_codes(cls, request):
newgroups = []
if saml_available:
from djangosaml2.cache import StateCache, IdentityCache
from djangosaml2.conf import get_config
from djangosaml2.views import _get_subject_id
from saml2.client import Saml2Client
state = StateCache(request.session)
conf = get_config(None, request)
client = Saml2Client(conf, state_cache=state,
identity_cache=IdentityCache(request.session),
logger=logger)
subject_id = _get_subject_id(request.session)
identity = client.users.get_identity(subject_id,
check_not_on_or_after=False)
if identity:
attributes = identity[0]
owneratrs = getattr(
settings, 'SAML_GROUP_OWNER_ATTRIBUTES', [])
for group in chain(*[attributes[i]
for i in owneratrs if i in attributes]):
try:
GroupProfile.search(group)
except Group.DoesNotExist:
newgroups.append(group)
return newgroups
class GroupDetailView(CheckedDetailView):
template_name = "dashboard/group-detail.html"
model = Group
read_level = 'operator'
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['future_users'] = FutureMember.objects.filter(
group=self.object)
context['acl'] = AclUpdateView.get_acl_data(
self.object.profile, self.request.user,
'dashboard.views.group-acl')
context['aclform'] = AclUserOrGroupAddForm()
context['addmemberform'] = AddGroupMemberForm()
context['group_profile_form'] = GroupProfileUpdate.get_form_object(
self.request, self.object.profile)
if self.request.user.is_superuser:
context['group_perm_form'] = GroupPermissionForm(
instance=self.object)
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if not self.get_has_level()(request.user, 'operator'):
raise PermissionDenied()
if request.POST.get('new_name'):
return self.__set_name(request)
if request.POST.get('new_member'):
return self.__add_user(request)
if request.POST.get('new_members'):
return self.__add_list(request)
return redirect(reverse_lazy("dashboard.views.group-detail",
kwargs={'pk': self.get_object().pk}))
def __add_user(self, request):
name = request.POST['new_member']
self.__add_username(request, name)
return redirect(reverse_lazy("dashboard.views.group-detail",
kwargs={'pk': self.object.pk}))
def __add_username(self, request, name):
if not name:
return
try:
entity = search_user(name)
self.object.user_set.add(entity)
except User.DoesNotExist:
if saml_available:
FutureMember.objects.get_or_create(org_id=name,
group=self.object)
else:
messages.warning(request, _('User "%s" not found.') % name)
def __add_list(self, request):
userlist = request.POST.get('new_members').split('\r\n')
for line in userlist:
self.__add_username(request, line)
return redirect(reverse_lazy("dashboard.views.group-detail",
kwargs={'pk': self.object.pk}))
def __set_name(self, request):
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 GroupPermissionsView(SuperuserRequiredMixin, UpdateView):
model = Group
form_class = GroupPermissionForm
slug_field = "pk"
slug_url_kwarg = "group_pk"
def get_success_url(self):
return "%s#group-detail-permissions" % (
self.get_object().groupprofile.get_absolute_url())
class GroupAclUpdateView(AclUpdateView):
model = Group
def get_object(self):
return super(GroupAclUpdateView, self).get_object().profile
class GroupList(LoginRequiredMixin, SingleTableView):
template_name = "dashboard/group-list.html"
model = Group
table_class = GroupListTable
table_pagination = False
def get(self, *args, **kwargs):
if self.request.is_ajax():
groups = [{
'url': reverse("dashboard.views.group-detail",
kwargs={'pk': i.pk}),
'name': i.name} for i in self.get_queryset()]
return HttpResponse(
json.dumps(list(groups)),
content_type="application/json",
)
else:
return super(GroupList, self).get(*args, **kwargs)
def get_queryset(self):
logger.debug('GroupList.get_queryset() called. User: %s',
unicode(self.request.user))
profiles = GroupProfile.get_objects_with_level(
'operator', self.request.user)
groups = Group.objects.filter(groupprofile__in=profiles)
s = self.request.GET.get("s")
if s:
groups = groups.filter(name__icontains=s)
return groups
class GroupRemoveUserView(CheckedDetailView, DeleteView):
model = Group
slug_field = 'pk'
slug_url_kwarg = 'group_pk'
read_level = 'operator'
member_key = 'member_pk'
def get_has_level(self):
return self.object.profile.has_level
def get_context_data(self, **kwargs):
context = super(GroupRemoveUserView, self).get_context_data(**kwargs)
try:
context['member'] = User.objects.get(pk=self.member_pk)
except User.DoesNotExist:
raise Http404()
return context
def get_success_url(self):
next = self.request.POST.get('next')
if next:
return next
else:
return reverse_lazy("dashboard.views.group-detail",
kwargs={'pk': self.get_object().pk})
def get(self, request, member_pk, *args, **kwargs):
self.member_pk = member_pk
return super(GroupRemoveUserView, self).get(request, *args, **kwargs)
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-remove.html']
else:
return ['dashboard/confirm/base-remove.html']
def remove_member(self, pk):
container = self.get_object()
container.user_set.remove(User.objects.get(pk=pk))
def get_success_message(self):
return _("Member successfully removed from group.")
def delete(self, request, *args, **kwargs):
object = self.get_object()
if not object.profile.has_level(request.user, 'operator'):
raise PermissionDenied()
self.remove_member(kwargs[self.member_key])
success_url = self.get_success_url()
success_message = self.get_success_message()
if request.is_ajax():
return HttpResponse(
json.dumps({'message': success_message}),
content_type="application/json",
)
else:
messages.success(request, success_message)
return redirect(success_url)
class GroupRemoveFutureUserView(GroupRemoveUserView):
member_key = 'member_org_id'
def get(self, request, member_org_id, *args, **kwargs):
self.member_org_id = member_org_id
return super(GroupRemoveUserView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(GroupRemoveUserView, self).get_context_data(**kwargs)
try:
context['member'] = FutureMember.objects.get(
org_id=self.member_org_id, group=self.get_object())
except FutureMember.DoesNotExist:
raise Http404()
return context
def remove_member(self, org_id):
FutureMember.objects.filter(org_id=org_id,
group=self.get_object()).delete()
def get_success_message(self):
return _("Future user successfully removed from group.")
class GroupDelete(CheckedDetailView, DeleteView):
"""This stuff deletes the group.
"""
model = Group
template_name = "dashboard/confirm/base-delete.html"
read_level = 'operator'
def get_has_level(self):
return self.object.profile.has_level
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/confirm/ajax-delete.html']
else:
return ['dashboard/confirm/base-delete.html']
# 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.profile.has_level(request.user, 'owner'):
raise PermissionDenied()
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 redirect(success_url)
def get_success_url(self):
next = self.request.POST.get('next')
if next:
return next
else:
return reverse_lazy('dashboard.index')
class GroupCreate(GroupCodeMixin, LoginRequiredMixin, TemplateView):
form_class = GroupCreateForm
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):
if not request.user.has_module_perms('auth'):
raise PermissionDenied()
if form is None:
form = self.form_class(
new_groups=self.get_available_group_codes(request))
context = self.get_context_data(**kwargs)
context.update({
'template': 'dashboard/group-create.html',
'box_title': _('Create a Group'),
'form': form,
'ajax_title': True,
})
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
if not request.user.has_module_perms('auth'):
raise PermissionDenied()
form = self.form_class(
request.POST, new_groups=self.get_available_group_codes(request))
if not form.is_valid():
return self.get(request, form, *args, **kwargs)
form.cleaned_data
savedform = form.save()
savedform.profile.set_level(request.user, 'owner')
messages.success(request, _('Group successfully created.'))
if request.is_ajax():
return HttpResponse(json.dumps({'redirect':
savedform.profile.get_absolute_url()}),
content_type="application/json")
else:
return redirect(savedform.profile.get_absolute_url())
class GroupProfileUpdate(SuccessMessageMixin, GroupCodeMixin,
LoginRequiredMixin, UpdateView):
form_class = GroupProfileUpdateForm
model = Group
success_message = _('Group is successfully updated.')
@classmethod
def get_available_group_codes(cls, request, extra=None):
result = super(GroupProfileUpdate, cls).get_available_group_codes(
request)
if extra and extra not in result:
result += [extra]
return result
def get_object(self):
group = super(GroupProfileUpdate, self).get_object()
profile = group.profile
if not profile.has_level(self.request.user, 'owner'):
raise PermissionDenied
else:
return profile
@classmethod
def get_form_object(cls, request, instance, *args, **kwargs):
kwargs['instance'] = instance
kwargs['new_groups'] = cls.get_available_group_codes(
request, instance.org_id)
kwargs['superuser'] = request.user.is_superuser
return cls.form_class(*args, **kwargs)
def get(self, request, form=None, *args, **kwargs):
self.object = self.get_object()
if form is None:
form = self.get_form_object(request, self.object)
return super(GroupProfileUpdate, self).get(
request, form, *args, **kwargs)
def post(self, request, *args, **kwargs):
if not request.user.has_module_perms('auth'):
raise PermissionDenied()
self.object = self.get_object()
form = self.get_form_object(request, self.object, self.request.POST)
if not form.is_valid():
return self.form_invalid(form)
form.save()
return self.form_valid(form)
# Copyright 2014 Budapest University of Technology and Economics (BME IK)
#
# This file is part of CIRCLE Cloud.