diff --git a/insalan/tournament/tests.py b/insalan/tournament/tests.py index 438e0726..27935bf4 100644 --- a/insalan/tournament/tests.py +++ b/insalan/tournament/tests.py @@ -4,6 +4,7 @@ from types import NoneType from django.db.utils import IntegrityError +from django.contrib.auth.models import Permission from django.core.exceptions import ValidationError from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase, TransactionTestCase @@ -915,7 +916,6 @@ def test_example(self): reverse("tournament/details-full", args=[tourneyobj_one.id]), format="json" ) self.assertEqual(request.status_code, 200) - print(request.data) self.assertEqual( request.data, { @@ -1278,3 +1278,141 @@ def test_payment_status_set(self): man_reg.payment_status = PaymentStatus.PAY_LATER self.assertEqual(PaymentStatus.PAY_LATER, man_reg.payment_status) + + +class TournamentTeamEndpoints(TestCase): + """Tournament Registration Endpoint Test Class""" + + # TODO Test all endpoints + + def setUp(self): + """Setup method for Tournament Registrations Unit Tests""" + + # Basic setup for a one-tournamnent game event + event = Event.objects.create( + name="InsaLan Test", year=2023, month=3, description="" + ) + game = Game.objects.create(name="Test Game") + trnm = Tournament.objects.create(game=game, event=event) + team_one = Team.objects.create(name="La Team Test", tournament=trnm) + + # user_one = User.objects.create_user( + # username="testplayer", + # email="player.user.test@insalan.fr", + # password="^ThisIsAnAdminPassword42$", + # first_name="Iam", + # last_name="Staff", + # ) + + invalid_email_user: User = User.objects.create_user( + username="invalidemail", + email="randomplayer@gmail.com", + password="IUseAVerySecurePassword", + first_name="Random", + last_name="Player", + ) + + valid_email_user: User = User.objects.create_user( + username="validemail", + password="ThisIsPassword", + ) + + valid_email_user.set_email_active() + + # Player.objects.create(team=team_one, user=user_one) + # Player.objects.create(team=team_one, user=another_player) + # Manager.objects.create(team=team_one, user=random_player) + + def test_can_create_a_team_with_valid_email(self): + """Try to create a team with a valid email""" + user: User = User.objects.get(username="validemail") + self.client.force_login(user=user) + + event = Event.objects.get(name="InsaLan Test") + trnm = event.get_tournaments()[0] + + request = self.client.post( + "/v1/tournament/team/", + { + "name": "Les emails valides", + "tournament": trnm.id, + }, + format="json", + ) + + self.assertEquals(request.status_code, 201) + + def test_cant_create_a_team_with_no_valid_email(self): + """Try to create a team with email not validated""" + user: User = User.objects.get(username="invalidemail") + self.client.force_login(user=user) + + event = Event.objects.get(name="InsaLan Test") + trnm = event.get_tournaments()[0] + + request = self.client.post( + "/v1/tournament/team/", + { + "name": "Flemme de valider", + "tournament": trnm.id, + }, + format="json", + ) + + self.assertEquals(request.status_code, 403) + + def test_can_join_a_team_with_a_valid_email(self): + """Try to join an existing team with a valid email""" + user: User = User.objects.get(username="validemail") + self.client.force_login(user=user) + team: Team = Team.objects.get(name="La Team Test") + event: Event = Event.objects.get(name="InsaLan Test") + + trnm = event.get_tournaments()[0] + + request = self.client.post( + f"/v1/tournament/player/", + { + "team": team.id, + }, + format="json", + ) + self.assertEquals(request.status_code, 201) + + Player.objects.filter(user=user.id).delete() + + request = self.client.post( + f"/v1/tournament/manager/", + { + "team": team.id, + }, + format="json", + ) + self.assertEquals(request.status_code, 201) + + def test_cant_join_a_team_with_no_valid_email(self): + """Try to join an existing team with no valid email""" + user: User = User.objects.get(username="invalidemail") + self.client.force_login(user=User.objects.get(username="invalidemail")) + team: Team = Team.objects.get(name="La Team Test") + event: Event = Event.objects.get(name="InsaLan Test") + + trnm = event.get_tournaments()[0] + + request = self.client.post( + f"/v1/tournament/player/", + { + "team": team.id, + }, + format="json", + ) + self.assertEquals(request.status_code, 403) + + request = self.client.post( + f"/v1/tournament/manager/", + { + "team": user.id, + }, + format="json", + ) + self.assertEquals(request.status_code, 403) diff --git a/insalan/tournament/views.py b/insalan/tournament/views.py index 8e6ee115..e50ed0ea 100644 --- a/insalan/tournament/views.py +++ b/insalan/tournament/views.py @@ -4,7 +4,9 @@ # "Too few public methods" # pylint: disable=R0903 +from django.core.exceptions import PermissionDenied from django.http import Http404 +from django.utils.translation import gettext_lazy as _ from rest_framework import generics, permissions, status from rest_framework.exceptions import NotFound from rest_framework.permissions import BasePermission, SAFE_METHODS @@ -15,7 +17,7 @@ from insalan.user.models import User import insalan.tournament.serializers as serializers -from .models import Player, Manager, Event, Tournament, Game, Team +from .models import Player, Manager, Event, Tournament, Game, Team, PaymentStatus class ReadOnly(BasePermission): @@ -51,6 +53,7 @@ class EventDetails(generics.RetrieveUpdateDestroyAPIView): queryset = Event.objects.all().order_by("id") permission_classes = [permissions.IsAdminUser | ReadOnly] + class EventDetailsSomeDeref(APIView): """Details about an Event that dereferences tournaments, but nothing else""" @@ -64,10 +67,14 @@ def get(self, request, primary_key: int): event = candidates[0] - event_serialized = serializers.EventSerializer(event, context={"request": request}).data + event_serialized = serializers.EventSerializer( + event, context={"request": request} + ).data event_serialized["tournaments"] = [ - serializers.TournamentSerializer(Tournament.objects.get(id=id), context={"request": request}).data + serializers.TournamentSerializer( + Tournament.objects.get(id=id), context={"request": request} + ).data for id in event_serialized["tournaments"] ] @@ -76,8 +83,10 @@ def get(self, request, primary_key: int): return Response(event_serialized, status=status.HTTP_200_OK) + class EventByYear(generics.ListAPIView): """Get all of the events of a year""" + pagination_class = None serializer_class = serializers.EventSerializer @@ -85,6 +94,7 @@ def get_queryset(self): """Return the queryset""" return Event.objects.filter(year=int(self.kwargs["year"])) + # Games class GameList(generics.ListCreateAPIView): """List all known games""" @@ -136,30 +146,36 @@ def get(self, request, primary_key: int): tourney = tourneys[0] - tourney_serialized = serializers.TournamentSerializer(tourney, context={"request": request}).data + tourney_serialized = serializers.TournamentSerializer( + tourney, context={"request": request} + ).data # Dereference the event event = tourney.event - tourney_serialized["event"] = serializers.EventSerializer(event, context={"request": request}).data + tourney_serialized["event"] = serializers.EventSerializer( + event, context={"request": request} + ).data del tourney_serialized["event"]["tournaments"] # Dereference the game - tourney_serialized["game"] = serializers.GameSerializer(tourney.game, context={"request": request}).data + tourney_serialized["game"] = serializers.GameSerializer( + tourney.game, context={"request": request} + ).data # Dereference the teams teams_serialized = [] for team in tourney_serialized["teams"]: - team_preser = serializers.TeamSerializer(Team.objects.get(id=team), context={"request": request}).data + team_preser = serializers.TeamSerializer( + Team.objects.get(id=team), context={"request": request} + ).data del team_preser["tournament"] # Dereference players/managers to users (username) team_preser["players"] = [ - User.objects.get(id=pid).username - for pid in team_preser["players"] + User.objects.get(id=pid).username for pid in team_preser["players"] ] team_preser["managers"] = [ - User.objects.get(id=pid).username - for pid in team_preser["managers"] + User.objects.get(id=pid).username for pid in team_preser["managers"] ] teams_serialized.append(team_preser) @@ -176,7 +192,30 @@ class TeamList(generics.ListCreateAPIView): queryset = Team.objects.all().order_by("id") serializer_class = serializers.TeamSerializer - permission_classes = [permissions.IsAdminUser | ReadOnly] + permission_classes = [permissions.IsAuthenticated | ReadOnly] + + def post(this, request, *args, **kwargs): + user = request.user + data = request.data + + if user is None or not user.is_authenticated or "name" not in data: + raise PermissionDenied() + + if not user.is_email_active(): + raise PermissionDenied( + { + "email": [ + _( + "Veuillez activer votre courriel pour vous inscrire à un tournoi" + ) + ] + } + ) + mut = data._mutable + data._mutable = True + # data["players"] = [user.id] + data._mutable = mut + return super().post(request, *args, **kwargs) class TeamDetails(generics.RetrieveUpdateDestroyAPIView): @@ -195,13 +234,42 @@ class PlayerRegistration(generics.RetrieveAPIView): queryset = Player.objects.all().order_by("id") -class PlayerRegistrationList(generics.ListAPIView): +class PlayerRegistrationList(generics.ListCreateAPIView): """Get all player registrations""" pagination_class = None serializer_class = serializers.PlayerSerializer queryset = Player.objects.all().order_by("id") + def post(this, request, *args, **kwargs): + user = request.user + data = request.data + + if ( + user is None + or not user.is_authenticated + or "team" not in data + or "payment" in data + ): + raise PermissionDenied() + + if not user.is_email_active(): + raise PermissionDenied( + { + "email": [ + _( + "Veuillez activer votre courriel pour vous inscrire à un tournoi" + ) + ] + } + ) + mut = data._mutable + data._mutable = True + data["user"] = user.id + data["payment"] = PaymentStatus.NOT_PAID + data._mutable = mut + return super().post(request, *args, **kwargs) + class PlayerRegistrationListId(generics.ListAPIView): """Find all player registrations of a user from their ID""" @@ -237,13 +305,37 @@ class ManagerRegistration(generics.RetrieveAPIView): queryset = Manager.objects.all().order_by("id") -class ManagerRegistrationList(generics.ListAPIView): +class ManagerRegistrationList(generics.ListCreateAPIView): """Show all manager registrations""" pagination_class = None serializer_class = serializers.ManagerSerializer queryset = Manager.objects.all().order_by("id") + def post(this, request, *args, **kwargs): + user = request.user + data = request.data + + if user is None or "team" not in data or "payment" in data: + raise PermissionDenied() + + if not user.is_email_active(): + raise PermissionDenied( + { + "email": [ + _( + "Veuillez activer votre courriel pour vous inscrire à un tournoi" + ) + ] + } + ) + mut = data._mutable + data._mutable = True + data["user"] = user.id + data["payment"] = PaymentStatus.NOT_PAID + data._mutable = mut + return super().post(request, *args, **kwargs) + class ManagerRegistrationListId(generics.ListAPIView): """Find all player registrations of a user from their ID""" diff --git a/insalan/user/admin.py b/insalan/user/admin.py index 26ad9f08..9fe8b4b2 100644 --- a/insalan/user/admin.py +++ b/insalan/user/admin.py @@ -1,6 +1,7 @@ """Register our models in Django admin panel""" from django.contrib import admin from django.contrib.auth.admin import UserAdmin +from django.contrib.auth.models import Permission from .models import User class CustomUserAdmin(UserAdmin): @@ -11,4 +12,4 @@ class CustomUserAdmin(UserAdmin): ) admin.site.register(User, CustomUserAdmin) -# Register your models here. +admin.site.register(Permission) diff --git a/insalan/user/models.py b/insalan/user/models.py index 551e3155..039e75f7 100644 --- a/insalan/user/models.py +++ b/insalan/user/models.py @@ -6,7 +6,7 @@ from datetime import datetime from django.contrib.auth.base_user import BaseUserManager -from django.contrib.auth.models import AbstractUser, PermissionsMixin +from django.contrib.auth.models import AbstractUser, PermissionsMixin, Permission from django.contrib.auth.tokens import ( PasswordResetTokenGenerator, default_token_generator, @@ -19,6 +19,7 @@ from django.utils.translation import gettext_lazy as _ from django.core.validators import FileExtensionValidator + class UserManager(BaseUserManager): """ Managers the User objects (kind of like a serializer but not quite that) @@ -56,7 +57,6 @@ def create_superuser(self, email, username, password, **extra_fields): user = self.create_user(email, username, password, **extra_fields) user.is_superuser = True user.is_staff = True - user.email_active = True user.is_active = True user.save() @@ -74,6 +74,7 @@ class Meta: verbose_name = _("Utilisateur⋅rice") verbose_name_plural = _("Utilisateur⋅ices") + permissions = [("email_active", _("L'utilisateur⋅ice a activé son courriel"))] def __init__(self, *args, **kwargs): AbstractUser.__init__(self, *args, **kwargs) @@ -94,9 +95,6 @@ def __init__(self, *args, **kwargs): email = models.EmailField( verbose_name=_("Courriel"), max_length=255, unique=True, blank=False ) - email_active = models.BooleanField( - verbose_name=_("Courriel vérifié"), default=False - ) first_name = models.CharField(max_length=50, blank=True) last_name = models.CharField(max_length=50, blank=True) is_staff = models.BooleanField( @@ -108,6 +106,18 @@ def __init__(self, *args, **kwargs): is_active = models.BooleanField(default=True) object = UserManager() + def is_email_active(self): + return self.has_perm("user.email_active") + + def set_email_active(self, active=True): + if active: + self.user_permissions.add(Permission.objects.get(codename="email_active")) + else: + self.user_permissions.remove( + Permission.objects.get(codename="email_active") + ) + self.save() + class EmailConfirmationTokenGenerator(PasswordResetTokenGenerator): """ diff --git a/insalan/user/serializers.py b/insalan/user/serializers.py index cc9b7b0d..13ca52b6 100644 --- a/insalan/user/serializers.py +++ b/insalan/user/serializers.py @@ -57,7 +57,6 @@ class Meta: "is_superuser", "email", "image", - "email_active", "password", "password_validation", ] @@ -98,8 +97,8 @@ def check_validity(self, data): """ user = authenticate(username=data["username"], password=data["password"]) if user is not None: - if not user.is_active or not user.email_active: - raise serializers.ValidationError(_("Compte non activé")) + if not user.is_active: + raise serializers.ValidationError(_("Compte supprimé")) return user diff --git a/insalan/user/tests.py b/insalan/user/tests.py index 3e205af6..86c45af7 100644 --- a/insalan/user/tests.py +++ b/insalan/user/tests.py @@ -105,7 +105,6 @@ def setUp(self): first_name="Random", last_name="Player", is_active=True, - email_active=True, ) def test_register_invalid_data(self): @@ -153,7 +152,6 @@ def send_valid_data(data, check_fields=[]): ("is_staff", False), ("is_superuser", False), ("is_active", True), - ("email_active", False), ("email", "email@example.com"), ], ) @@ -173,7 +171,6 @@ def send_valid_data(data, check_fields=[]): ("is_staff", False), ("is_superuser", False), ("is_active", True), - ("email_active", False), ("email", "mario@mushroom.kingdom"), ], ) @@ -209,7 +206,6 @@ def send_valid_data(data, check_fields=[]): ("is_staff", False), ("is_superuser", False), ("is_active", True), - ("email_active", False), ("email", "email@example.com"), ], ) @@ -230,7 +226,6 @@ def send_valid_data(data, check_fields=[]): ("is_staff", False), ("is_superuser", False), ("is_active", True), - ("email_active", False), ("email", "mario@mushroom.kingdom"), ], ) @@ -268,8 +263,7 @@ def test_register_can_confirm_email(self): request = self.client.post("/v1/user/register/", data, format="json") - self.assertFalse(User.objects.get(username=data["username"]).email_active) - + self.assertFalse(User.objects.get(username=data["username"]).is_email_active()) match = re.match( ".*https?://[^ ]*/(?P[^ /]*)/(?P[^ /]*)", mail.outbox[0].body, @@ -283,7 +277,7 @@ def test_register_can_confirm_email(self): request = self.client.get(f"/v1/user/confirm/{username}/{token}") self.assertEqual(request.status_code, 200) - self.assertTrue(User.objects.get(username=data["username"]).email_active) + self.assertTrue(User.objects.get(username=data["username"]).is_email_active()) def test_can_confirm_email_only_once(self): """ @@ -392,7 +386,6 @@ def test_login_account(self): email="test@test.com", password="1111qwer!", is_active=True, - email_active=True, ) def send_valid_data(data): @@ -442,12 +435,13 @@ def test_cant_resend_confirmation_if_already_valid(self): Test that an user cannot resend a confirmation email if they already confirmed their email """ - User.objects.create_user( + user_object = User.objects.create_user( username="newplayer", email="test@example.com", password="1111qwer!", - email_active=True, ) + user_object.set_email_active() + user_object.save() request = self.client.post( "/v1/user/resend-email/", {"username": "ILoveEmail"}, format="json" @@ -494,9 +488,6 @@ def test_can_reset_password(self): username = match["username"] token = match["token"] - # self.assertFalse(User.objects.get(username=data["username"]).email_active) - - # self.assertEqual(username, data["username"]) data = { "user": username, "token": token, diff --git a/insalan/user/views.py b/insalan/user/views.py index 0e0aebff..ea7cba54 100644 --- a/insalan/user/views.py +++ b/insalan/user/views.py @@ -168,7 +168,7 @@ def get(self, request, user=None, token=None): user_object, token, ): - user_object.email_active = True + user_object.set_email_active() user_object.last_login = timezone.make_aware(datetime.now()) user_object.save() return Response() @@ -287,11 +287,8 @@ def post(self, request): status=status.HTTP_400_BAD_REQUEST, ) - if user_object.email_active: - return Response( - {"user": [error_text]}, - status=status.HTTP_400_BAD_REQUEST, - ) + if user_object.has_perm("email_active"): + return Response({"msg": error_text}, status=status.HTTP_400_BAD_REQUEST) UserMailer.send_email_confirmation(user_object) return Response()