diff --git a/beat/web/team/__init__.py b/beat/web/team/__init__.py index 2beb954869b2c47b5761d2aaec162447bf964878..370438955207e499e3df48268aa4465997fd43fd 100644 --- a/beat/web/team/__init__.py +++ b/beat/web/team/__init__.py @@ -25,4 +25,4 @@ # # ############################################################################### -default_app_config = 'beat.web.team.apps.TeamConfig' +default_app_config = "beat.web.team.apps.TeamConfig" diff --git a/beat/web/team/admin.py b/beat/web/team/admin.py index 4e981c665c263a0ed01ec24e74f17cfb687477dc..138318f65a444574bab354404137a7fcf060b70b 100644 --- a/beat/web/team/admin.py +++ b/beat/web/team/admin.py @@ -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) diff --git a/beat/web/team/api.py b/beat/web/team/api.py index 6570ec26f45c6265b8953be8ca278deb86292a61..a24ef2cfec5056d9c27223aeaf8848eac1f42acc 100644 --- a/beat/web/team/api.py +++ b/beat/web/team/api.py @@ -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 - # ---------------------------------------------------------- diff --git a/beat/web/team/apps.py b/beat/web/team/apps.py index dcd09cd58ec9f6e4b48467821a583713031423a5..ee11c250e2f8a5848592730ac8cc084214247484 100644 --- a/beat/web/team/apps.py +++ b/beat/web/team/apps.py @@ -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")) diff --git a/beat/web/team/migrations/0001_initial.py b/beat/web/team/migrations/0001_initial.py index 7b0a9ba693d5a33a4fec240486a3b26ac63596ee..8dfd9d7fd372ce067d6fb379df1c769743e62b1f 100644 --- a/beat/web/team/migrations/0001_initial.py +++ b/beat/web/team/migrations/0001_initial.py @@ -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")]), ), ] diff --git a/beat/web/team/models.py b/beat/web/team/models.py index de4b749a6cd856ea28403fd106cf888b84870fc1..f17404cd49160d5a6a63d941fb68d04849b988fa 100644 --- a/beat/web/team/models.py +++ b/beat/web/team/models.py @@ -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 _ diff --git a/beat/web/team/permissions.py b/beat/web/team/permissions.py index 47871a4e2ce68c5cb7f908209f56565dd8604f2d..33de083684b0cd34f38013804b05330cacf36542 100644 --- a/beat/web/team/permissions.py +++ b/beat/web/team/permissions.py @@ -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: diff --git a/beat/web/team/serializers.py b/beat/web/team/serializers.py index 54d09f7532f298a9ca7d1ce94efdd01e1a1a71a3..8a6b1790253bf5a1c984b8aba9c7d2b7cb9e6699 100644 --- a/beat/web/team/serializers.py +++ b/beat/web/team/serializers.py @@ -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) diff --git a/beat/web/team/signals/__init__.py b/beat/web/team/signals/__init__.py index 33b89e014479e94d632bfdc453e8c3c6da7caacb..bfac6da7334ebfb670d6e3e01113d76c89794295 100644 --- a/beat/web/team/signals/__init__.py +++ b/beat/web/team/signals/__init__.py @@ -28,4 +28,3 @@ import django.dispatch added_to_team = django.dispatch.Signal(providing_args=["members"]) - diff --git a/beat/web/team/signals/handlers.py b/beat/web/team/signals/handlers.py index e63239f7db0e141a6d53d02a39d4c5379ab450ac..4634f010475b68c15c2197b064d21129dd5722a9 100644 --- a/beat/web/team/signals/handlers.py +++ b/beat/web/team/signals/handlers.py @@ -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, + ) diff --git a/beat/web/team/templates/team/list.html b/beat/web/team/templates/team/list.html index 0f473cfe1b535945278ac0070f97ae5b38e6a2cb..58f8c7882a1dde50c4d2f06eb57acb0d2fb21a4e 100644 --- a/beat/web/team/templates/team/list.html +++ b/beat/web/team/templates/team/list.html @@ -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 %} diff --git a/beat/web/team/templates/team/panels/table.html b/beat/web/team/templates/team/panels/table.html index 80b1b0c212b5e49a79d8f282f1fcedaf04fada9a..e2d69207965931c76a3cc3477263d0f6f3984c1f 100644 --- a/beat/web/team/templates/team/panels/table.html +++ b/beat/web/team/templates/team/panels/table.html @@ -1,21 +1,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 %} diff --git a/beat/web/team/templates/team/team.html b/beat/web/team/templates/team/team.html index 6234b35a6b37b24ce869e4160463ea486c2b9e94..014e14276b0c8b434d5591b0a8d3db57e0e33167 100644 --- a/beat/web/team/templates/team/team.html +++ b/beat/web/team/templates/team/team.html @@ -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 %} diff --git a/beat/web/team/templatetags/team_tags.py b/beat/web/team/templatetags/team_tags.py index a4a6474d46b9ccd1643803014cc1406f5ad0f00f..f4bed2b28fc2d712b73fca9704306cac20a6e57b 100644 --- a/beat/web/team/templatetags/team_tags.py +++ b/beat/web/team/templatetags/team_tags.py @@ -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) diff --git a/beat/web/team/tests.py b/beat/web/team/tests.py index 8492813715649a131d8b88fffdac6f31e929ccc1..bf2677acda937b3e5ae8289814a0bac334495108 100644 --- a/beat/web/team/tests.py +++ b/beat/web/team/tests.py @@ -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 - # ---------------------------------------------------------- diff --git a/beat/web/team/views.py b/beat/web/team/views.py index 0a1f685afea69ae6f61039e2e15e83c3609bf742..9a6e2d78c4669359397112dae4befa701b61c59d 100644 --- a/beat/web/team/views.py +++ b/beat/web/team/views.py @@ -26,16 +26,15 @@ ############################################################################### from django.conf import settings -from django.shortcuts import get_object_or_404 -from django.shortcuts import render from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User from django.http import Http404 +from django.shortcuts import get_object_or_404 +from django.shortcuts import render from .models import Team - -#---------------------------------------------------------- +# ---------------------------------------------------------- @login_required @@ -45,14 +44,18 @@ def create(request): The user must be authenticated before it can add a new team """ - return render(request, - 'team/edition.html', - { - 'users': User.objects.exclude(username__in=settings.ACCOUNTS_TO_EXCLUDE_FROM_TEAMS).order_by('username'), - }) + return render( + request, + "team/edition.html", + { + "users": User.objects.exclude( + username__in=settings.ACCOUNTS_TO_EXCLUDE_FROM_TEAMS + ).order_by("username"), + }, + ) -#---------------------------------------------------------- +# ---------------------------------------------------------- @login_required @@ -62,23 +65,27 @@ def edit(request, author_name, name): The user must be authenticated before it can edit a team """ - if author_name != request.user.username: raise Http404() + if author_name != request.user.username: + raise Http404() # Retrieves the team - team = get_object_or_404(Team, - owner__username__iexact=author_name, - name__iexact=name - ) + team = get_object_or_404( + Team, owner__username__iexact=author_name, name__iexact=name + ) - return render(request, - 'team/edition.html', - { - 'team': team, - 'users': User.objects.exclude(username__in=settings.ACCOUNTS_TO_EXCLUDE_FROM_TEAMS).order_by('username'), - }) + return render( + request, + "team/edition.html", + { + "team": team, + "users": User.objects.exclude( + username__in=settings.ACCOUNTS_TO_EXCLUDE_FROM_TEAMS + ).order_by("username"), + }, + ) -#---------------------------------------------------------- +# ---------------------------------------------------------- def view(request, author_name, name): @@ -86,18 +93,17 @@ def view(request, author_name, name): """ team = get_object_or_404(Team, name=name, owner__username=author_name) - return render(request, - 'team/team.html', - {'team': team}) + return render(request, "team/team.html", {"team": team}) -#---------------------------------------------------------- +# ---------------------------------------------------------- def ls(request, author_name): - '''List all accessible teams to the request user''' + """List all accessible teams to the request user""" - if not author_name: return public_ls(request) + if not author_name: + return public_ls(request) author = get_object_or_404(User, username=author_name) @@ -108,25 +114,25 @@ def ls(request, author_name): else: objects = Team.objects.for_user(request.user, True).filter(owner=author) - return render(request, - 'team/list.html', - dict( - objects=objects, - author=author, - owner=(request.user == author), - )) + return render( + request, + "team/list.html", + dict(objects=objects, author=author, owner=(request.user == author),), + ) -#---------------------------------------------------------- +# ---------------------------------------------------------- def public_ls(request): - '''List all accessible teams to the request user''' - - return render(request, - 'team/list.html', - dict( - objects=Team.objects.public(), - author=request.user, #anonymous - owner=False, - )) + """List all accessible teams to the request user""" + + return render( + request, + "team/list.html", + dict( + objects=Team.objects.public(), + author=request.user, # anonymous + owner=False, + ), + )