Skip to content
Snippets Groups Projects
Commit b1446a4e authored by André Anjos's avatar André Anjos :speech_balloon:
Browse files

Merge branch 'issue_406' into 'master'

Issue 406

Closes #406 

See merge request !206
parents 38ee6c6a d191f60e
No related branches found
No related tags found
1 merge request!206Issue 406
Pipeline #
......@@ -42,7 +42,7 @@ from .serializers import TeamUpdateSerializer
from .models import Team
from .permissions import IsOwner, HasPrivacyLevel
from ..common.responses import BadRequestResponse
from ..common.responses import BadRequestResponse, ForbiddenResponse
from ..common.mixins import CommonContextMixin
......@@ -117,6 +117,11 @@ class TeamDetailView(CommonContextMixin, generics.RetrieveUpdateDestroyAPIView):
def delete(self, request, owner_name, team_name):
team = self.get_queryset()
# Check that the team can still be deleted
if not(team.deletable()):
return ForbiddenResponse("The team isn't deletable (it has been used to share %d objects with its members)" % team.total_shares())
team.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
......
......@@ -93,6 +93,57 @@ class Team(models.Model):
return (self.owner.username, self.name)
def total_shares(self, user=None):
'''Count number of objects shared with this team
If ``user`` is passed, counts objects shared with this team that the
given user has access to.
'''
used_at = 0
if user is None:
used_at += self.shared_algorithms.count()
used_at += self.shared_databases.count()
used_at += self.shared_dataformats.count()
used_at += self.shared_environments.count()
used_at += self.shared_experiments.count()
used_at += self.shared_librarys.count()
used_at += self.shared_plotterparameters.count()
used_at += self.shared_plotters.count()
used_at += self.shared_searchs.count()
used_at += self.shared_toolchains.count()
used_at += self.usable_algorithms.count()
used_at += self.usable_librarys.count()
used_at += self.usable_plotterparameters.count()
used_at += self.usable_plotters.count()
else:
used_at += self.shared_algorithms.for_user(user, add_public=True).count()
used_at += self.shared_databases.for_user(user, add_public=True).count()
used_at += self.shared_dataformats.for_user(user, add_public=True).count()
used_at += self.shared_environments.for_user(user, add_public=True).count()
used_at += self.shared_experiments.for_user(user, add_public=True).count()
used_at += self.shared_librarys.for_user(user, add_public=True).count()
used_at += self.shared_plotterparameters.for_user(user, add_public=True).count()
used_at += self.shared_plotters.for_user(user, add_public=True).count()
used_at += self.shared_searchs.for_user(user, add_public=True).count()
used_at += self.shared_toolchains.for_user(user, add_public=True).count()
used_at += self.usable_algorithms.for_user(user, add_public=True).count()
used_at += self.usable_librarys.for_user(user, add_public=True).count()
used_at += self.usable_plotterparameters.for_user(user, add_public=True).count()
used_at += self.usable_plotters.for_user(user, add_public=True).count()
return used_at
def deletable(self):
return not self.total_shares()
#_____ Overrides __________
def __unicode__(self):
......
......@@ -2,21 +2,21 @@
{% comment %}
* Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/
* Contact: beat.support@idiap.ch
*
*
* This file is part of the beat.web module of the BEAT platform.
*
*
* Commercial License Usage
* Licensees holding valid commercial BEAT licenses may use this file in
* accordance with the terms contained in a written agreement between you
* and Idiap. For further information contact tto@idiap.ch
*
*
* Alternatively, this file may be used under the terms of the GNU Affero
* Public License version 3 as published by the Free Software and appearing
* in the file LICENSE.AGPL included in the packaging of this file.
* The BEAT platform 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.
*
*
* You should have received a copy of the GNU Affero Public License along
* with the BEAT platform. If not, see http://www.gnu.org/licenses/.
{% endcomment %}
......@@ -125,8 +125,8 @@
</div>{# column #}
</div>{# row #}
<script type="text/javascript">
{% csrf_token %}
<script type="text/javascript">
jQuery(document).ready(function() {
//sets-up chosen box
......
{% comment %}
* Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/
* Contact: beat.support@idiap.ch
*
*
* This file is part of the beat.web module of the BEAT platform.
*
*
* Commercial License Usage
* Licensees holding valid commercial BEAT licenses may use this file in
* accordance with the terms contained in a written agreement between you
* and Idiap. For further information contact tto@idiap.ch
*
*
* Alternatively, this file may be used under the terms of the GNU Affero
* Public License version 3 as published by the Free Software and appearing
* in the file LICENSE.AGPL included in the packaging of this file.
* The BEAT platform 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.
*
*
* You should have received a copy of the GNU Affero Public License along
* with the BEAT platform. If not, see http://www.gnu.org/licenses/.
{% endcomment %}
{% load team_tags %}
<div class="btn-group btn-group-sm action-buttons pull-right">
{% if display_count %}
<a class="btn btn-default btn-references" href="{% url 'teams:view' object.owner.username object.name %}" data-toggle="tooltip" data-placement="bottom" title="Number of members in this team"><span class="badge">{{ object.members.count }}</span></a>
<a class="btn btn-default btn-references" href="{% url 'teams:view' object.owner.username object.name %}" data-toggle="tooltip" data-placement="bottom" title="Number of objects shared through this team"><span class="badge">{% total_shares_for_user object %}</span></a>
<a class="btn btn-default btn-references" href="{% url 'teams:view' object.owner.username object.name %}" data-toggle="tooltip" data-placement="bottom" title="Number of members in this team">{{ object.members.count }} <i class="fa fa-group"></i></a>
{% endif %}
{% ifequal request.user object.owner %}
<!-- Delete, needs to be the owner -->
{% if object.deletable %}
<a class="btn btn-default btn-delete" onclick="modal_delete('team', '{{ object.name }}', '{% url 'api_teams:user_teamlist' request.user.username %}', '{% url 'teams:list' request.user.username %}');" data-toggle="tooltip" data-placement="bottom" title="Delete"><i class="fa fa-times fa-lg"></i></a>
{% endif %}
<!-- Edit -->
<a class="btn btn-default btn-edit" href="{% url 'teams:edit' object.owner.username object.name %}" data-toggle="tooltip" data-placement="bottom" title="Edit"><i class="fa fa-edit fa-lg"></i></a>
......
......@@ -86,10 +86,21 @@ def team_actions(context, obj, display_count):
}
register.inclusion_tag('ui/contribution_list.html')
#--------------------------------------------------
@register.inclusion_tag('ui/contribution_list.html')
def user_list(id):
return { 'panel_id': id,
'URL_PREFIX': settings.URL_PREFIX,
'finder_placeholder': 'Find an User...',
'owner': False,
}
#--------------------------------------------------
@register.simple_tag(takes_context=True)
def total_shares_for_user(context, team):
return team.total_shares(context['request'].user)
......@@ -31,11 +31,15 @@ from rest_framework import status
from rest_framework.test import APITestCase
from django.contrib.auth.models import User
from django.conf import settings
import os
import shutil
import simplejson as json
from ..common.testutils import tearDownModule
from .models import Team
from ..dataformats.models import DataFormat
from .serializers import SimpleTeamSerializer
......@@ -57,6 +61,19 @@ class TeamTestCase(APITestCase):
self.invalid_username = 'janedoe'
# object to test sharing/deletion
if os.path.exists(settings.DATAFORMATS_ROOT):
shutil.rmtree(settings.DATAFORMATS_ROOT)
(dataformat, errors) = DataFormat.objects.create_dataformat(
author=self.johndoe,
name='data_format_shared_with_private_team',
)
assert dataformat, errors
dataformat.share(teams=[self.teamdoe_private])
self.dataformat = dataformat
#----------------------------------------------------------
......@@ -377,14 +394,31 @@ class TeamDeletionTestCase(TeamTestCase):
options['owner_name'] = self.johndoe.username
url = self.get_url(options)
self.assertEqual(self.teamdoe.total_shares(), 0)
response = self.client.delete(url, format='json')
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
def test_logged_in_user_own_team_shared(self):
self.client.login(username=self.johndoe.username, password=self.password)
options = self.options
options['owner_name'] = self.johndoe.username
options['team_name'] = 'teamdoe_private'
url = self.get_url(options)
self.assertEqual(self.teamdoe_private.total_shares(), 1)
response = self.client.delete(url, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_logged_in_user_team_from_other(self):
self.client.login(username=self.jackdoe, password=self.password)
url = self.get_url(self.options)
self.assertEqual(self.teamdoe.total_shares(), 0)
response = self.client.delete(url, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
......@@ -396,6 +430,8 @@ class TeamDeletionTestCase(TeamTestCase):
options['owner_name'] = self.invalid_username
url = self.get_url(options)
self.assertEqual(self.teamdoe.total_shares(), 0)
response = self.client.delete(url, format='json')
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment