Commit 3cf09597 by Bach Dániel

Merge branch 'feature-group-details-fixed'

Conflicts:
	circle/dashboard/static/dashboard/dashboard.js
	circle/dashboard/urls.py
parents 2d8071b0 9871de77
......@@ -19,11 +19,11 @@ from __future__ import absolute_import
from datetime import timedelta
from django.contrib.auth.models import User
from django.contrib.auth.forms import (
AuthenticationForm, PasswordResetForm, SetPasswordForm,
PasswordChangeForm,
)
from django.contrib.auth.models import User, Group
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
......@@ -312,6 +312,55 @@ class VmCustomizeForm(forms.Form):
)
class GroupCreateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(GroupCreateForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_show_labels = False
self.helper.layout = Layout(
Div(
Div(
AnyTag(
'h4',
HTML(_("Name")),
),
css_class="col-sm-10",
),
css_class="row",
),
Div(
Div(
Field('name', id="group-create-name"),
css_class="col-sm-10",
),
css_class="row",
),
Div( # buttons
Div(
AnyTag( # tip: don't try to use Button class
"button",
AnyTag(
"i",
css_class="icon-play"
),
HTML(" Create"),
css_id="vm-create-submit",
css_class="btn btn-success",
),
css_class="col-sm-5",
),
css_class="row",
),
)
class Meta:
model = Group
fields = ['name', ]
class HostForm(forms.ModelForm):
def setowner(self, user):
......
......@@ -26,7 +26,7 @@ from django.contrib.auth.signals import user_logged_in
from django.core.urlresolvers import reverse
from django.db.models import (
Model, ForeignKey, OneToOneField, CharField, IntegerField, TextField,
DateTimeField,
DateTimeField, permalink,
)
from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _, override, ugettext
......@@ -111,6 +111,11 @@ class GroupProfile(AclBase):
except cls.DoesNotExist:
return Group.objects.get(name=name)
@permalink
def get_absolute_url(self):
return ('dashboard.views.group-detail', None,
{'pk': self.group.pk})
def get_or_create_profile(self):
obj, created = GroupProfile.objects.get_or_create(group_id=self.pk)
......
......@@ -607,3 +607,26 @@ footer a, footer a:hover, footer a:visited {
#notifications-button {
margin: 0;
}
#group-detail-user-table td:first-child, #group-detail-user-table th:last-child,
#group-detail-user-table td:last-child,
#group-detail-perm-table td:first-child, #group-detail-perm-table th:last-child,
#group-detail-perm-table td:last-child {
text-align: center;
width: 100px;
}
#group-detail-perm-header {
margin-top: 25px;
}
textarea[name="list-new-namelist"] {
max-width: 500px;
min-height: 80px;
margin-bottom: 10px;
}
/* 2px border bottom for all bootstrap tables */
.table thead>tr>th {
border-bottom: 1px;
}
......@@ -34,6 +34,22 @@ $(function () {
return false;
});
$('.group-create').click(function(e) {
$.ajax({
type: 'GET',
url: '/dashboard/group/create/',
success: function(data) {
$('body').append(data);
addSliderMiscs();
$('#create-modal').modal('show');
$('#create-modal').on('hidden.bs.modal', function() {
$('#create-modal').remove();
});
}
});
return false;
});
$('.template-choose').click(function(e) {
$.ajax({
type: 'GET',
......
/* rename */
$("#group-details-h1-name, .group-details-rename-button").click(function() {
$("#group-details-h1-name").hide();
......@@ -30,4 +29,38 @@
$(".group-details-help").stop().slideToggle();
});
/* for Node removes buttons */
$('.delete-from-group').click(function() {
var href = $(this).attr('href');
var tr = $(this).closest('tr');
var group = $(this).data('group_pk');
var member = $(this).data('member_pk');
var dir = window.location.pathname.indexOf('list') == -1;
addModalConfirmation(removeMember,
{ 'url': href,
'data': [],
'tr': tr,
'group_pk': group,
'member_pk': member,
'type': "user",
'redirect': dir});
return false;
});
function removeMember(data) {
$.ajax({
type: 'POST',
url: data['url'],
headers: {"X-CSRFToken": getCookie('csrftoken')},
success: function(re, textStatus, xhr) {
data['tr'].fadeOut(function() {
$(this).remove();});
},
error: function(xhr, textStatus, error) {
addMessage('Uh oh :(', 'danger')
}
});
}
......@@ -99,7 +99,7 @@ $(function() {
$("#group-list-column-name", row).html(
$("<a/>", {
'class': "real-link",
href: "/dashboard/group/" + data['node_pk'] + "/",
href: "/dashboard/group/" + data['group_pk'] + "/",
text: data['new_name']
})
).show();
......
......@@ -134,16 +134,19 @@ class GroupListTable(Table):
)
number_of_users = TemplateColumn(
orderable=False,
template_name='dashboard/group-list/column-users.html',
attrs={'th': {'class': 'group-list-table-admin'}},
)
admin = TemplateColumn(
orderable=False,
template_name='dashboard/group-list/column-admin.html',
attrs={'th': {'class': 'group-list-table-admin'}},
)
actions = TemplateColumn(
orderable=False,
attrs={'th': {'class': 'group-list-table-thin'}},
template_code=('{% include "dashboard/group-list/column-'
'actions.html" with btn_size="btn-xs" %}'),
......
{% load i18n %}
<div class="modal fade" id="confirmation-modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
{% if text %}
{{ text }}
{% else %}
{%blocktrans with object=object%}
Are you sure you want to remove <strong>{{ member }}</strong> from <strong>{{ object }}</strong>?
{%endblocktrans%}
{% endif %}
<br />
<div class="pull-right" style="margin-top: 15px;">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button id="confirmation-modal-button" type="button" class="btn btn-warning">Remove</button>
</div>
<div class="clearfix"></div>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div>
{% load crispy_forms_tags %}
<style>
.row {
margin-bottom: 15px;
}
</style>
<form method="POST" action="/dashboard/group/create/">
{% csrf_token %}
{% crispy form %}
</form>
......@@ -37,51 +37,92 @@
<div class="row">
<div class="col-md-12" id="group-detail-pane">
<div class="panel panel-default" id="group-detail-panel">
<div class="tab-content panel-body">
<h3>{% trans "User list"|capfirst %}</h3>
<table class="table table-striped table-with-form-fields">
<div class="tab-content panel-body" id="group-form-body">
<h3>{% trans "User list"|capfirst %}</h3>
<form action="" method="post">{% csrf_token %}
<table class="table table-striped table-with-form-fields table-bordered" id="group-detail-user-table">
<tbody>
<thead><tr><th></th><th>{% trans "Who" %}</th><th></th><th></th></tr></thead>
<thead><tr><th></th><th>{% trans "Who" %}</th><th>{% trans "Remove" %}</th></tr></thead>
{% for i in users %}
<tr><td><i class="icon-user"></i></td><td>{{i.username}}</td>
<td><a data-group-pk="{{ i.pk }}" href="#" class="real-link groupuser-delete btn btn-link btn-xs"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr>
<tr>
<td><i class="icon-user"></i></td><td>{{i.username}}</td>
<td>
<a data-group_pk="{{ group.pk }}" data-member_pk="{{i.pk}}" href="{% url "dashboard.views.remove-user" member_pk=i.pk group_pk=group.pk %}" class="real-link delete-from-group btn btn-link btn-xs"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a>
</td>
</tr>
{% endfor %}
<tr><td><i class="icon-plus"></i></td>
<td><input type="text" class="form-control" name="perm-new-name"
placeholder="{% trans "Name of group or user" %}"></td>
<tr>
<td><i class="icon-plus"></i></td>
<td colspan="2">
<input type="text" class="form-control" name="list-new-name"placeholder="{% trans "Name of user" %}">
</td>
</tr>
</tbody>
</table>
<textarea name="list-new-namelist" class="form-control"
placeholder="{% trans "List of usernames (one per line)." %}"></textarea>
<div class="form-actions">
<button type="submit" class="btn btn-success">{% trans "Save" %}</button>
</div>
</form>
<h3>{% trans "Permissions"|capfirst %}</h3>
<h3 id="group-detail-perm-header">{% trans "Permissions"|capfirst %}</h3>
<form action="{{acl.url}}" method="post">{% csrf_token %}
<table class="table table-striped table-with-form-fields">
<thead><tr><th></th><th>{% trans "Who" %}</th><th>{% trans "What" %}</th><th></th></tr></thead>
<table class="table table-striped table-with-form-fields table-bordered" id="group-detail-perm-table">
<thead>
<tr>
<th></th><th>{% trans "Who" %}</th><th>{% trans "What" %}</th><th>{% trans "Remove" %}</th>
</tr>
</thead>
<tbody>
{% for i in acl.users %}
<tr><td><i class="icon-user"></i></td><td>{{i.user}}</td>
<td><select class="form-control" name="perm-u-{{i.user.id}}">
{% for id, name in acl.levels %}
<option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
{% endfor %}
</select></td>
<td class="user-remove"><a href="#" class="btn btn-link btn-xs"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td></tr>
{% for i in acl.users %}
<tr>
<td><i class="icon-user"></i></td><td>{{i.user}}</td>
<td>
<select class="form-control" name="perm-u-{{i.user.id}}">
{% for id, name in acl.levels %}
<option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
{% endfor %}
<tr><td><i class="icon-plus"></i></td>
<td><input type="text" class="form-control" name="perm-new-name"
placeholder="{% trans "Name of group or user" %}"></td>
<td><select class="form-control" name="perm-new">
{% for id, name in acl.levels %}
<option value="{{id}}">{{name}}</option>
{% endfor %}
</select></td><td></td>
</tr>
</select>
</td>
<td class="user-remove"><a data-group_pk="{{ group.pk }}" data-member_pk="{{i.user.pk }}" href="{% url "dashboard.views.remove-acluser" member_pk=i.user.pk group_pk=group.pk %}" class="real-link delete-from-group btn btn-link btn-xs"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a></td>
</tr>
{% endfor %}
{% for i in acl.groups %}
<tr>
<td><i class="icon-group"></i></td><td>{{ i.group }}</td>
<td>
<select class="form-control" name="perm-g-{{ i.group.pk }}">
{% for id, name in acl.levels %}
<option{%if id = i.level%} selected="selected"{%endif%} value="{{id}}">{{name}}</option>
{% endfor %}
</select>
</td>
<td class="user-remove"><a data-group_pk="{{ i.pk }}"data-member_pk="{{i.group.pk }}" href="{% url "dashboard.views.remove-aclgroup" member_pk=i.group.pk group_pk=group.pk %}" class="real-link delete-from-group btn btn-link btn-xs"><i class="icon-remove"><span class="sr-only">{% trans "remove" %}</span></i></a>
</td>
</tr>
{% endfor %}
<tr>
<td><i class="icon-plus"></i></td>
<td>
<input type="text" class="form-control" name="perm-new-name"
placeholder="{% trans "Name of group or user" %}">
</td>
<td>
<select class="form-control" name="perm-new">
{% for id, name in acl.levels %}
<option value="{{id}}">{{name}}</option>
{% endfor %}
</select>
</td>
<td></td>
</tr>
</tbody>
</table>
<textarea class="form-control"></textarea>
<div class="form-actions panel-body">
<div class="form-actions">
<button type="submit" class="btn btn-success">{% trans "Save" %}</button>
</div>
</form>
......
......@@ -26,7 +26,7 @@
<i class="icon-chevron-sign-right"></i> <strong>{{ more_groups }}</strong> more
</a>
{% endif %}
<a class="btn btn-success btn-xs group-create" href="#"><i class="icon-upload-alt"></i> {% trans "new" %} </a>
<a class="btn btn-success btn-xs group-create" href="{% url "dashboard.views.group-create" %}"><i class="icon-upload-alt"></i> {% trans "new" %} </a>
</div>
</div>
</div>
......
......@@ -17,7 +17,7 @@
</div>
{% endif %}
{% if perms.group %}
{% if perms.auth %}
<div class="col-lg-4 col-sm-6">
{% include "dashboard/index-groups.html" %}
</div>
......
......@@ -871,6 +871,611 @@ class NodeDetailTest(LoginMixin, TestCase):
self.assertEqual(node_enabled, not Node.objects.get(pk=1).enabled)
class GroupCreateTest(LoginMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self):
# u0 - user with creating group permissions
self.u0 = User.objects.create(username='user0')
self.u0.set_password('password')
self.u0.save()
permlist = Permission.objects.all()
self.u0.user_permissions.add(
filter(lambda element: 'group' in element.name and
'add' in element.name, permlist)[0])
# u1 simple user without permissions
self.u1 = User.objects.create(username='user1')
self.u1.set_password('password')
self.u1.save()
self.us = User.objects.create(username='superuser', is_superuser=True)
self.us.set_password('password')
self.us.save()
self.g1 = Group.objects.create(name='group1')
self.g1.save()
def tearDown(self):
super(GroupCreateTest, self).tearDown()
self.g1.delete()
self.u0.delete()
self.u1.delete()
self.us.delete()
def test_anon_group_page(self):
c = Client()
response = c.get('/dashboard/group/create/')
self.assertEqual(response.status_code, 302)
def test_superuser_group_page(self):
c = Client()
self.login(c, 'superuser')
response = c.get('/dashboard/group/create/')
self.assertEqual(response.status_code, 200)
def test_permitted_group_page(self):
c = Client()
self.login(c, 'user0')
response = c.get('/dashboard/group/create/')
self.assertEqual(response.status_code, 200)
def test_unpermitted_group_page(self):
c = Client()
self.login(c, 'user1')
response = c.get('/dashboard/group/create/')
self.assertEqual(response.status_code, 403)
def test_anon_group_create(self):
c = Client()
groupnum = Group.objects.count()
response = c.post('/dashboard/group/create/', {'name': 'newgroup'})
self.assertEqual(response.status_code, 302)
self.assertEqual(Group.objects.count(), groupnum)
def test_unpermitted_group_create(self):
c = Client()
groupnum = Group.objects.count()
self.login(c, 'user1')
response = c.post('/dashboard/group/create/', {'name': 'newgroup'})
self.assertEqual(response.status_code, 403)
self.assertEqual(Group.objects.count(), groupnum)
def test_permitted_group_create(self):
c = Client()
groupnum = Group.objects.count()
self.login(c, 'user0')
response = c.post('/dashboard/group/create/', {'name': 'newgroup'})
self.assertEqual(response.status_code, 302)
self.assertEqual(Group.objects.count(), groupnum + 1)
def test_superuser_group_create(self):
c = Client()
groupnum = Group.objects.count()
self.login(c, 'superuser')
response = c.post('/dashboard/group/create/', {'name': 'newgroup'})
self.assertEqual(response.status_code, 302)
self.assertEqual(Group.objects.count(), groupnum + 1)
def test_namecollision_group_create(self):
# hint: group1 is in setUp, the tests checks creating group with the
# same name
c = Client()
groupnum = Group.objects.count()
self.login(c, 'superuser')
response = c.post('/dashboard/group/create/', {'name': 'group1'})
self.assertEqual(response.status_code, 200)
self.assertEqual(Group.objects.count(), groupnum)
def test_creator_is_owner_when_group_create(self):
# has owner rights in the group the user who created the group?
c = Client()
self.login(c, 'user0')
c.post('/dashboard/group/create/', {'name': 'newgroup'})
newgroup = Group.objects.get(name='newgroup')
self.assertTrue(newgroup.profile.has_level(self.u0, 'owner'))
class GroupDeleteTest(LoginMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self):
# u0 - user with creating group permissions
self.u0 = User.objects.create(username='user0')
self.u0.set_password('password')
self.u0.save()
permlist = Permission.objects.all()
self.u0.user_permissions.add(
filter(lambda element: 'group' in element.name and
'delete' in element.name, permlist)[0])
# u1 simple user without permissions
self.u1 = User.objects.create(username='user1')
self.u1.set_password('password')
self.u1.save()
self.us = User.objects.create(username='superuser', is_superuser=True)
self.us.set_password('password')
self.us.save()
self.g1 = Group.objects.create(name='group1')
self.g1.profile.set_user_level(self.u0, 'owner')
self.g1.save()
def tearDown(self):
super(GroupDeleteTest, self).tearDown()
self.g1.delete()
self.u0.delete()
self.u1.delete()
self.us.delete()
def test_anon_group_page(self):
c = Client()
response = c.get('/dashboard/group/delete/' + str(self.g1.pk) + '/')
self.assertEqual(response.status_code, 302)
def test_superuser_group_page(self):
c = Client()
self.login(c, 'superuser')
response = c.get('/dashboard/group/delete/' + str(self.g1.pk) + '/')
self.assertEqual(response.status_code, 200)
def test_permitted_group_page(self):
c = Client()
self.login(c, 'user0')
response = c.get('/dashboard/group/delete/' + str(self.g1.pk) + '/')
self.assertEqual(response.status_code, 200)
def test_unpermitted_group_page(self):
c = Client()
self.login(c, 'user1')
response = c.get('/dashboard/group/delete/' + str(self.g1.pk) + '/')
self.assertEqual(response.status_code, 403)
def test_anon_group_delete(self):
c = Client()
groupnum = Group.objects.count()
response = c.post('/dashboard/group/delete/' + str(self.g1.pk) + '/')
self.assertEqual(response.status_code, 302)
self.assertEqual(Group.objects.count(), groupnum)
def test_unpermitted_group_delete(self):
c = Client()
groupnum = Group.objects.count()
self.login(c, 'user1')
response = c.post('/dashboard/group/delete/' + str(self.g1.pk) + '/')
self.assertEqual(response.status_code, 403)
self.assertEqual(Group.objects.count(), groupnum)
def test_permitted_group_delete(self):
c = Client()
groupnum = Group.objects.count()
self.login(c, 'user0')
response = c.post('/dashboard/group/delete/' + str(self.g1.pk) + '/')
self.assertEqual(response.status_code, 302)
self.assertEqual(Group.objects.count(), groupnum - 1)
def test_superuser_group_delete(self):
c = Client()
groupnum = Group.objects.count()
self.login(c, 'superuser')
response = c.post('/dashboard/group/delete/' + str(self.g1.pk) + '/')
self.assertEqual(response.status_code, 302)
self.assertEqual(Group.objects.count(), groupnum - 1)
class GroupDetailTest(LoginMixin, TestCase):
fixtures = ['test-vm-fixture.json', 'node.json']
def setUp(self):
Instance.get_remote_queue_name = Mock(return_value='test')
# u0 - owner for group1
self.u0 = User.objects.create(username='user0')
self.u0.set_password('password')
self.u0.save()
self.u1 = User.objects.create(username='user1')
self.u1.set_password('password')
self.u1.save()
self.u2 = User.objects.create(username='user2', is_staff=True)
self.u2.set_password('password')
self.u2.save()
self.u3 = User.objects.create(username='user3')
self.u3.set_password('password')
self.u3.save()
# u4 - removable user for group1
self.u4 = User.objects.create(username='user4')
self.u4.set_password('password')
self.u4.save()
self.us = User.objects.create(username='superuser', is_superuser=True)
self.us.set_password('password')
self.us.save()
self.g1 = Group.objects.create(name='group1')
self.g1.profile.set_user_level(self.u0, 'owner')
self.g1.profile.set_user_level(self.u4, 'operator')
self.g1.user_set.add(self.u4)
self.g1.save()
self.g2 = Group.objects.create(name='group2')
self.g2.save()
self.g3 = Group.objects.create(name='group3')
self.g3.save()
self.g1.profile.set_group_level(self.g3, 'operator')
settings["default_vlangroup"] = 'public'
VlanGroup.objects.create(name='public')
def tearDown(self):
super(GroupDetailTest, self).tearDown()
self.g1.delete()
self.g2.delete()
self.g3.delete()
self.u0.delete()
self.u1.delete()
self.u2.delete()
self.us.delete()
self.u3.delete()
self.u4.delete()
def test_404_superuser_group_page(self):
c = Client()
self.login(c, 'superuser')
response = c.get('/dashboard/group/25555/')
self.assertEqual(response.status_code, 404)
def test_404_user_group_page(self):
c = Client()
self.login(c, 'user0')
response = c.get('/dashboard/group/25555/')
self.assertEqual(response.status_code, 404)
def test_anon_group_page(self):
c = Client()
response = c.get('/dashboard/group/' + str(self.g1.pk) + '/')
self.assertEqual(response.status_code, 302)
def test_superuser_group_page(self):
c = Client()
self.login(c, 'superuser')
response = c.get('/dashboard/group/' + str(self.g1.pk) + '/')
self.assertEqual(response.status_code, 200)
def test_acluser_group_page(self):
c = Client()
self.login(c, 'user0')
response = c.get('/dashboard/group/' + str(self.g1.pk) + '/')
self.assertEqual(response.status_code, 200)
def test_acluser2_group_page(self):
self.g1.profile.set_user_level(self.u1, 'operator')
c = Client()
self.login(c, 'user1')
response = c.get('/dashboard/group/' + str(self.g1.pk) + '/')
self.assertEqual(response.status_code, 200)
def test_unpermitted_user_group_page(self):
c = Client()
self.login(c, 'user1')
response = c.get('/dashboard/group/' + str(self.g1.pk) + '/')
self.assertEqual(response.status_code, 403)
def test_user_in_userlist_group_page(self):
self.g1.user_set.add(self.u1)
c = Client()
self.login(c, 'user1')
response = c.get('/dashboard/group/' + str(self.g1.pk) + '/')
self.assertEqual(response.status_code, 403)
def test_groupmember_group_page(self):
self.g2.user_set.add(self.u1)
self.g1.profile.set_group_level(self.g2, 'owner')
c = Client()
self.login(c, 'user1')
response = c.get('/dashboard/group/' + str(self.g1.pk) + '/')
self.assertEqual(response.status_code, 200)
def test_superuser_group_delete(self):
num_of_groups = Group.objects.count()
c = Client()
self.login(c, 'superuser')
response = c.post('/dashboard/group/delete/' + str(self.g1.pk) + '/')
self.assertEqual(response.status_code, 302)
self.assertEqual(Group.objects.count(), num_of_groups - 1)
def test_unpermitted_group_delete(self):
num_of_groups = Group.objects.count()
c = Client()
self.login(c, 'user3')
response = c.post('/dashboard/group/delete/' + str(self.g1.pk) + '/')
self.assertEqual(response.status_code, 403)
self.assertEqual(Group.objects.count(), num_of_groups)
def test_acl_group_delete(self):
num_of_groups = Group.objects.count()
c = Client()
self.login