Commit a1524da0 authored by Flavio TARSETTI's avatar Flavio TARSETTI
Browse files

Merge branch 'cleanup_team' into 'django3_migration'

Cleanup team

See merge request !364
parents 95f2641b 278cf7a2
Pipeline #42679 passed with stage
in 14 minutes and 53 seconds
......@@ -25,4 +25,4 @@
# #
###############################################################################
default_app_config = 'beat.web.team.apps.TeamConfig'
default_app_config = "beat.web.team.apps.TeamConfig"
......@@ -29,7 +29,9 @@ from django.contrib import admin
from .models import Team
class TeamAdmin(admin.ModelAdmin):
filter_horizontal = ['members']
filter_horizontal = ["members"]
admin.site.register(Team, TeamAdmin)
......@@ -26,24 +26,22 @@
###############################################################################
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from django.db.models import Q
from django.shortcuts import get_object_or_404
from rest_framework import exceptions as drf_exceptions
from rest_framework import generics
from rest_framework import permissions
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework import exceptions as drf_exceptions
from ..common.mixins import CommonContextMixin
from .models import Team
from .permissions import HasPrivacyLevel
from .permissions import IsOwner
from .serializers import FullTeamSerializer
from .serializers import SimpleTeamSerializer
from .serializers import TeamCreationSerializer
from .serializers import TeamUpdateSerializer
from .models import Team
from .permissions import IsOwner, HasPrivacyLevel
from ..common.mixins import CommonContextMixin
# ----------------------------------------------------------
......
......@@ -25,15 +25,19 @@
# #
###############################################################################
from ..common.apps import CommonAppConfig
from django.utils.translation import ugettext_lazy as _
from ..common.apps import CommonAppConfig
class TeamConfig(CommonAppConfig):
name = 'beat.web.team'
verbose_name = _('Team')
name = "beat.web.team"
verbose_name = _("Team")
def ready(self):
super(TeamConfig, self).ready()
from .signals.handlers import on_added_to_team
from actstream import registry
registry.register(self.get_model('Team'))
from .signals.handlers import on_added_to_team # noqa: F401
registry.register(self.get_model("Team"))
......@@ -27,8 +27,9 @@
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
from django.db import migrations
from django.db import models
class Migration(migrations.Migration):
......@@ -39,18 +40,44 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='Team',
name="Team",
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=32)),
('short_description', models.TextField(default=b'', max_length=100, blank=True)),
('privacy_level', models.PositiveIntegerField(default=0, choices=[[0, 'Private'], [1, 'Members only'], [2, 'Public']])),
('members', models.ManyToManyField(related_name='teams', to=settings.AUTH_USER_MODEL, blank=True)),
('owner', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
("name", models.CharField(max_length=32)),
(
"short_description",
models.TextField(default=b"", max_length=100, blank=True),
),
(
"privacy_level",
models.PositiveIntegerField(
default=0,
choices=[[0, "Private"], [1, "Members only"], [2, "Public"]],
),
),
(
"members",
models.ManyToManyField(
related_name="teams", to=settings.AUTH_USER_MODEL, blank=True
),
),
(
"owner",
models.ForeignKey(
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
),
),
],
),
migrations.AlterUniqueTogether(
name='team',
unique_together=set([('owner', 'name')]),
name="team", unique_together=set([("owner", "name")]),
),
]
......@@ -25,10 +25,10 @@
# #
###############################################################################
from django.contrib.auth.models import User
from django.db import models
from django.db.models import Q
from django.urls import reverse
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
......
......@@ -29,6 +29,7 @@ from rest_framework import permissions
from .models import Team
class IsOwner(permissions.BasePermission):
"""
The logged in user should also be the owner
......@@ -37,6 +38,7 @@ class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj.owner == request.user
class HasPrivacyLevel(permissions.BasePermission):
"""
Check whether the user has enough rights to view the team
......@@ -49,7 +51,9 @@ class HasPrivacyLevel(permissions.BasePermission):
elif obj.privacy_level == Team.PRIVATE:
return False
elif obj.privacy_level == Team.MEMBERS and obj.members.filter(username=request.user.username):
elif obj.privacy_level == Team.MEMBERS and obj.members.filter(
username=request.user.username
):
return True
else:
......
......@@ -26,16 +26,14 @@
###############################################################################
from django.contrib.auth.models import User
from rest_framework.reverse import reverse
from rest_framework import serializers
from rest_framework.reverse import reverse
from ..common.models import Contribution
from .models import Team
from .signals import added_to_team
#----------------------------------------------------------
# ----------------------------------------------------------
class BasicTeamSerializer(serializers.ModelSerializer):
......@@ -48,91 +46,92 @@ class BasicTeamSerializer(serializers.ModelSerializer):
class Meta:
model = Team
fields = ['short_description', 'is_owner', 'accessibility']
fields = ["short_description", "is_owner", "accessibility"]
def get_name(self, obj):
return obj.fullname()
def get_is_owner(self, obj):
return (obj.owner == self.context.get('user'))
return obj.owner == self.context.get("user")
def get_accessibility(self, obj):
if obj.privacy_level == Team.PUBLIC:
return 'public'
return "public"
elif obj.privacy_level == Team.MEMBERS:
return 'members'
return "members"
else:
return 'private'
return "private"
def get_members(self, obj):
return map(lambda x: x.username, obj.members.iterator())
#----------------------------------------------------------
# ----------------------------------------------------------
class SimpleTeamSerializer(BasicTeamSerializer):
class Meta(BasicTeamSerializer.Meta):
fields = ['name', 'short_description', 'is_owner', 'accessibility']
fields = ["name", "short_description", "is_owner", "accessibility"]
#----------------------------------------------------------
# ----------------------------------------------------------
class FullTeamSerializer(BasicTeamSerializer):
class Meta(BasicTeamSerializer.Meta):
fields = ['name', 'short_description', 'is_owner', 'accessibility', 'members']
fields = ["name", "short_description", "is_owner", "accessibility", "members"]
#----------------------------------------------------------
# ----------------------------------------------------------
class CreatedTeamSerializer(BasicTeamSerializer):
object_view = serializers.SerializerMethodField()
class Meta(BasicTeamSerializer.Meta):
fields = ['name', 'short_description', 'accessibility', 'members', 'object_view']
fields = [
"name",
"short_description",
"accessibility",
"members",
"object_view",
]
def get_object_view(self, obj):
return reverse('teams:view', args=[obj.owner.username, obj.name])
return reverse("teams:view", args=[obj.owner.username, obj.name])
#----------------------------------------------------------
# ----------------------------------------------------------
class UpdatedTeamSerializer(BasicTeamSerializer):
class Meta(BasicTeamSerializer.Meta):
fields = ['short_description', 'accessibility', 'members']
fields = ["short_description", "accessibility", "members"]
#----------------------------------------------------------
# ----------------------------------------------------------
class TeamCreationSerializer(serializers.ModelSerializer):
accessibility = serializers.CharField(source='privacy_level')
accessibility = serializers.CharField(source="privacy_level")
members = serializers.ListField(child=serializers.CharField())
class Meta:
model = Team
fields = ['name', 'short_description', 'accessibility', 'members']
fields = ["name", "short_description", "accessibility", "members"]
def create(self, validated_data):
members = validated_data['members']
del validated_data['members']
members = validated_data["members"]
del validated_data["members"]
team = None
try:
team = Team.objects.get(owner=self.context['request'].user, name=validated_data['name'])
except:
pass
if self.Meta.model.objects.filter(
owner=self.context["request"].user, name=validated_data["name"]
).exists():
raise serializers.ValidationError("A team with this name already exists")
if team is not None:
raise serializers.ValidationError('A team with this name already exists')
team = self.Meta.model.objects.create(owner=self.context['request'].user, **validated_data)
team = self.Meta.model.objects.create(
owner=self.context["request"].user, **validated_data
)
if members:
for member in members:
......@@ -142,14 +141,22 @@ class TeamCreationSerializer(serializers.ModelSerializer):
return team
def update(self, instance, validated_data):
instance.short_description = validated_data.get('short_description', instance.short_description)
instance.privacy_level = validated_data.get('privacy_level', instance.privacy_level)
instance.short_description = validated_data.get(
"short_description", instance.short_description
)
instance.privacy_level = validated_data.get(
"privacy_level", instance.privacy_level
)
instance.save()
members = validated_data.get('members', [])
members = validated_data.get("members", [])
members_to_remove = [user for user in instance.members.all() if user not in members]
members_to_add = [user for user in members if user not in instance.members.all()]
members_to_remove = [
user for user in instance.members.all() if user not in members
]
members_to_add = [
user for user in members if user not in instance.members.all()
]
for user in members_to_remove:
instance.members.remove(user)
......@@ -166,9 +173,9 @@ class TeamCreationSerializer(serializers.ModelSerializer):
return Contribution.sanitize_name(value)
def validate_accessibility(self, value):
if value == 'public':
if value == "public":
return Team.PUBLIC
elif value == 'members':
elif value == "members":
return Team.MEMBERS
else:
return Team.PRIVATE
......@@ -181,13 +188,12 @@ class TeamCreationSerializer(serializers.ModelSerializer):
return serializer.data
#----------------------------------------------------------
# ----------------------------------------------------------
class TeamUpdateSerializer(TeamCreationSerializer):
class Meta(TeamCreationSerializer.Meta):
fields = ['short_description', 'accessibility', 'members']
fields = ["short_description", "accessibility", "members"]
def to_representation(self, obj):
serializer = UpdatedTeamSerializer(obj)
......
......@@ -28,4 +28,3 @@
import django.dispatch
added_to_team = django.dispatch.Signal(providing_args=["members"])
......@@ -25,14 +25,20 @@
# #
###############################################################################
from django.dispatch import receiver
from actstream import action
from django.dispatch import receiver
from ..signals import added_to_team
@receiver(added_to_team)
def on_added_to_team(sender, **kwargs):
members = kwargs['members']
for member in members:
action.send(sender.owner, verb="has added", action_object=member, target=sender, public=False)
members = kwargs["members"]
for member in members:
action.send(
sender.owner,
verb="has added",
action_object=member,
target=sender,
public=False,
)
......@@ -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 %}
......
{% 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 %}
......
......@@ -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 %}
......
......@@ -26,19 +26,18 @@
###############################################################################
from django import template
from django.conf import settings
register = template.Library()
#--------------------------------------------------
# --------------------------------------------------
@register.inclusion_tag('team/panels/table.html', takes_context=True)
@register.inclusion_tag("team/panels/table.html", takes_context=True)
def team_table(context, objects, owner, id):
'''Composes a team list table
"""Composes a team list table
This panel primarily exists for user's team list page.
......@@ -50,22 +49,22 @@ def team_table(context, objects, owner, id):
id: The HTML id to set on the generated table. This is handy for the
filter functionality normally available on list pages.
'''
"""
return {
'request': context['request'],
'objects': objects,
'owner': owner,
'panel_id': id,
"request": context["request"],
"objects": objects,
"owner": owner,
"panel_id": id,
}
#--------------------------------------------------
# --------------------------------------------------
@register.inclusion_tag('team/panels/actions.html', takes_context=True)
@register.inclusion_tag("team/panels/actions.html", takes_context=True)
def team_actions(context, obj, display_count):
'''Composes the action buttons for a particular team
"""Composes the action buttons for a particular team
This panel primarily exists for showing action buttons for a given
team taking into consideration it is being displayed for a given user.
......@@ -78,29 +77,30 @@ def team_actions(context, obj, display_count):
display_count (bool): If true, displays the number of elements in the
team in a button
'''
"""
return {
'request': context['request'],
'display_count': display_count,
'object': obj,
"request": context["request"],
"display_count": display_count,
"object": obj,
}
#--------------------------------------------------
# --------------------------------------------------
@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,
}
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)
return team.total_shares(context["request"].user)
......@@ -25,25 +25,21 @@
# #
###############################################################################
import json
import os
import shutil
import json
from django.conf import settings
from django.contrib.auth.models import User
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from django.contrib.auth.models import User
from django.conf import settings
from ..common.testutils import tearDownModule # noqa test runner will call it
from ..dataformats.models import DataFormat
from .models import Team
from .serializers import SimpleTeamSerializer