diff --git a/api/serializers.py b/api/serializers.py index 0057fdf..380a849 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers from tournaments.models.court import Court -from tournaments.models import Club, LiveMatch, TeamScore, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamRegistration, PlayerRegistration, Purchase, FailedApiCall, DateInterval, Log, DeviceToken +from tournaments.models import Club, LiveMatch, TeamScore, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamRegistration, PlayerRegistration, Purchase, FailedApiCall, DateInterval, Log, DeviceToken, UnregisteredTeam, UnregisteredPlayer from django.contrib.auth import password_validation from django.utils.translation import gettext_lazy as _ # email @@ -236,3 +236,20 @@ class DrawLogSerializer(serializers.ModelSerializer): class Meta: model = DrawLog fields = '__all__' + +class UnregisteredTeamSerializer(serializers.ModelSerializer): + class Meta: + # match_id = serializers.PrimaryKeyRelatedField(queryset=Match.objects.all()) + # group_stage_id = serializers.PrimaryKeyRelatedField(queryset=GroupStage.objects.all()) + model = UnregisteredTeam + fields = '__all__' + # ['id', 'group_stage_id', 'registration_date', 'call_date', 'bracket_position', + # 'group_stage_position', 'logo'] + +class UnregisteredPlayerSerializer(serializers.ModelSerializer): + class Meta: + # team_registration_id = serializers.PrimaryKeyRelatedField(queryset=TeamRegistration.objects.all()) + # team_state_id = serializers.PrimaryKeyRelatedField(queryset=TeamState.objects.all()) + model = UnregisteredPlayer + fields = '__all__' + # ['id', 'team_registration_id', 'first_name', 'last_name', 'licence_id', 'rank', 'has_paid'] diff --git a/api/urls.py b/api/urls.py index b221163..fa7499c 100644 --- a/api/urls.py +++ b/api/urls.py @@ -22,6 +22,8 @@ router.register(r'draw-logs', views.DrawLogViewSet) router.register(r'failed-api-calls', views.FailedApiCallViewSet) router.register(r'logs', views.LogViewSet) router.register(r'device-token', views.DeviceTokenViewSet) +router.register(r'unregistered-teams', views.UnregisteredTeamViewSet) +router.register(r'unregistered-players', views.UnregisteredPlayerViewSet) urlpatterns = [ path('', include(router.urls)), diff --git a/api/views.py b/api/views.py index f004f5d..a1c7fee 100644 --- a/api/views.py +++ b/api/views.py @@ -1,7 +1,7 @@ from pandas.io.feather_format import pd from tournaments.models.draw_log import DrawLog -from .serializers import ClubSerializer, CourtSerializer, DateIntervalSerializer, DrawLogSerializer, TournamentSerializer, UserSerializer, ChangePasswordSerializer, EventSerializer, RoundSerializer, GroupStageSerializer, MatchSerializer, TeamScoreSerializer, TeamRegistrationSerializer, PlayerRegistrationSerializer, LiveMatchSerializer, PurchaseSerializer, UserUpdateSerializer, FailedApiCallSerializer, LogSerializer, DeviceTokenSerializer -from tournaments.models import Club, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamScore, TeamRegistration, PlayerRegistration, Court, DateInterval, Purchase, FailedApiCall, Log, DeviceToken +from .serializers import ClubSerializer, CourtSerializer, DateIntervalSerializer, DrawLogSerializer, TournamentSerializer, UserSerializer, ChangePasswordSerializer, EventSerializer, RoundSerializer, GroupStageSerializer, MatchSerializer, TeamScoreSerializer, TeamRegistrationSerializer, PlayerRegistrationSerializer, LiveMatchSerializer, PurchaseSerializer, UserUpdateSerializer, FailedApiCallSerializer, LogSerializer, DeviceTokenSerializer, UnregisteredTeamSerializer, UnregisteredPlayerSerializer +from tournaments.models import Club, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamScore, TeamRegistration, PlayerRegistration, Court, DateInterval, Purchase, FailedApiCall, Log, DeviceToken, UnregisteredTeam, UnregisteredPlayer from rest_framework import viewsets, permissions from rest_framework.authtoken.models import Token @@ -301,3 +301,27 @@ class DrawLogViewSet(viewsets.ModelViewSet): if self.request.user: return self.queryset.filter(tournament__event__creator=self.request.user) return [] + +class UnregisteredTeamViewSet(viewsets.ModelViewSet): + queryset = UnregisteredTeam.objects.all() + serializer_class = UnregisteredTeamSerializer + + def get_queryset(self): + tournament_id = self.request.query_params.get('tournament') + if tournament_id: + return self.queryset.filter(tournament=tournament_id) + if self.request.user: + return self.queryset.filter(tournament__event__creator=self.request.user) + return [] + +class UnregisteredPlayerViewSet(viewsets.ModelViewSet): + queryset = UnregisteredPlayer.objects.all() + serializer_class = UnregisteredPlayerSerializer + + def get_queryset(self): + tournament_id = self.request.query_params.get('tournament') + if tournament_id: + return self.queryset.filter(unregistered_team__tournament=tournament_id) + if self.request.user: + return self.queryset.filter(unregistered_team__tournament__event__creator=self.request.user) + return [] diff --git a/tournaments/admin.py b/tournaments/admin.py index 8ccb493..419d14b 100644 --- a/tournaments/admin.py +++ b/tournaments/admin.py @@ -3,8 +3,10 @@ from django.contrib import admin from tournaments.models import team_registration from tournaments.models.device_token import DeviceToken from tournaments.models.draw_log import DrawLog +from tournaments.models.unregistered_player import UnregisteredPlayer +from tournaments.models.unregistered_team import UnregisteredTeam -from .models import Club, TeamScore, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamRegistration, PlayerRegistration, Purchase, Court, DateInterval, FailedApiCall, Log +from .models import Club, TeamScore, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamRegistration, PlayerRegistration, Purchase, Court, DateInterval, FailedApiCall, Log, UnregisteredTeam, UnregisteredPlayer from django.contrib.auth.admin import UserAdmin from django.contrib.auth.forms import UserCreationForm, UserChangeForm @@ -111,6 +113,16 @@ class DrawLogAdmin(admin.ModelAdmin): list_filter = [SimpleTournamentListFilter] ordering = ['draw_date'] +class UnregisteredTeamAdmin(admin.ModelAdmin): + list_display = ['player_names', 'tournament'] + list_filter = [SimpleTournamentListFilter] + +class UnregisteredPlayerAdmin(admin.ModelAdmin): + list_display = ['first_name', 'last_name', 'licence_id'] + search_fields = ('first_name', 'last_name') + list_filter = [] + ordering = ['last_name', 'first_name'] + admin.site.register(CustomUser, CustomUserAdmin) admin.site.register(Club, ClubAdmin) admin.site.register(Event, EventAdmin) @@ -128,3 +140,5 @@ admin.site.register(FailedApiCall, FailedApiCallAdmin) admin.site.register(Log, LogAdmin) admin.site.register(DeviceToken, DeviceTokenAdmin) admin.site.register(DrawLog, DrawLogAdmin) +admin.site.register(UnregisteredTeam, UnregisteredTeamAdmin) +admin.site.register(UnregisteredPlayer, UnregisteredPlayerAdmin) diff --git a/tournaments/migrations/0101_unregisteredteam_unregisteredplayer.py b/tournaments/migrations/0101_unregisteredteam_unregisteredplayer.py new file mode 100644 index 0000000..8685d6c --- /dev/null +++ b/tournaments/migrations/0101_unregisteredteam_unregisteredplayer.py @@ -0,0 +1,38 @@ +# Generated by Django 4.2.11 on 2024-12-16 08:57 + +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('tournaments', '0100_playerregistration_coach_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='UnregisteredTeam', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('unregistration_date', models.DateTimeField(blank=True, null=True)), + ('comment', models.CharField(blank=True, max_length=200, null=True)), + ('source', models.CharField(blank=True, max_length=20, null=True)), + ('source_value', models.CharField(blank=True, max_length=200, null=True)), + ('reason', models.IntegerField(blank=True, null=True)), + ('tournament', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tournaments.tournament')), + ], + ), + migrations.CreateModel( + name='UnregisteredPlayer', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('first_name', models.CharField(blank=True, max_length=50)), + ('last_name', models.CharField(blank=True, max_length=50)), + ('licence_id', models.CharField(blank=True, max_length=50, null=True)), + ('reason', models.IntegerField(blank=True, null=True)), + ('unregistered_team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tournaments.unregisteredteam')), + ], + ), + ] diff --git a/tournaments/migrations/0102_remove_teamregistration_unregistered_and_more.py b/tournaments/migrations/0102_remove_teamregistration_unregistered_and_more.py new file mode 100644 index 0000000..4138357 --- /dev/null +++ b/tournaments/migrations/0102_remove_teamregistration_unregistered_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.11 on 2024-12-16 13:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('tournaments', '0101_unregisteredteam_unregisteredplayer'), + ] + + operations = [ + migrations.RemoveField( + model_name='teamregistration', + name='unregistered', + ), + migrations.RemoveField( + model_name='teamregistration', + name='unregistration_date', + ), + ] diff --git a/tournaments/models/__init__.py b/tournaments/models/__init__.py index d385a4e..3920dde 100644 --- a/tournaments/models/__init__.py +++ b/tournaments/models/__init__.py @@ -16,3 +16,5 @@ from .purchase import Purchase from .failed_api_call import FailedApiCall from .log import Log from .device_token import DeviceToken +from .unregistered_team import UnregisteredTeam +from .unregistered_player import UnregisteredPlayer diff --git a/tournaments/models/team_registration.py b/tournaments/models/team_registration.py index 148a2d2..d59781b 100644 --- a/tournaments/models/team_registration.py +++ b/tournaments/models/team_registration.py @@ -29,8 +29,6 @@ class TeamRegistration(models.Model): final_ranking = models.IntegerField(null=True, blank=True) points_earned = models.IntegerField(null=True, blank=True) - unregistered = models.BooleanField(default=False) - unregistration_date = models.DateTimeField(null=True, blank=True) def __str__(self): if self.name: @@ -118,7 +116,7 @@ class TeamRegistration(models.Model): return None def out_of_tournament(self): - return self.walk_out or self.unregistered + return self.walk_out def get_other_player(self, player): for p in self.playerregistration_set.all(): diff --git a/tournaments/models/unregistered_player.py b/tournaments/models/unregistered_player.py new file mode 100644 index 0000000..8dd9201 --- /dev/null +++ b/tournaments/models/unregistered_player.py @@ -0,0 +1,25 @@ +from django.db import models +from . import UnregisteredTeam +import uuid + +class UnregisteredPlayer(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) + unregistered_team = models.ForeignKey(UnregisteredTeam, on_delete=models.CASCADE) + first_name = models.CharField(max_length=50, blank=True) + last_name = models.CharField(max_length=50, blank=True) + licence_id = models.CharField(max_length=50, null=True, blank=True) + + def __str__(self): + return self.name() + + def name(self): + return f"{self.first_name} {self.last_name}" + + def shortened_name(self): + name = self.name() + if len(name) > 20 and self.first_name: + name = f"{self.first_name[0]}. {self.last_name}" + if len(name) > 20: + name_parts = self.last_name.split(" ") + name = f"{self.first_name[0]}. {name_parts[0]}" + return name diff --git a/tournaments/models/unregistered_team.py b/tournaments/models/unregistered_team.py new file mode 100644 index 0000000..583210b --- /dev/null +++ b/tournaments/models/unregistered_team.py @@ -0,0 +1,40 @@ +from django.db import models +from django.db.models.sql.query import Q +from . import Tournament +import uuid +from django.utils import timezone + +class UnregisteredTeam(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) + tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE) + unregistration_date = models.DateTimeField(null=True, blank=True) + + def __str__(self): + return self.player_names() + + def player_names_as_list(self): + return [pr.name() for pr in self.unregisteredplayer_set.all()] + + + def team_names(self): + return self.player_names_as_list() + + def shortened_team_names(self): + players = list(self.unregisteredplayer_set.all()) + if len(players) == 1: + return [players[0].shortened_name(), ''] + else: + return [pr.shortened_name() for pr in players] + + @property + def players(self): + # Fetch related PlayerRegistration objects + return self.unregisteredplayer_set.all().order_by('last_name') + + def player_names(self): + names = self.player_names_as_list() + str = " - ".join(names) + if len(str) > 0: + return str + else: + return "no players" diff --git a/tournaments/views.py b/tournaments/views.py index bc9eeb4..2700533 100644 --- a/tournaments/views.py +++ b/tournaments/views.py @@ -53,6 +53,8 @@ from .models import ( FailedApiCall, TeamSummon, FederalCategory, + UnregisteredTeam, + UnregisteredPlayer ) from .forms import ( SimpleForm, @@ -135,7 +137,6 @@ def tournament_info(request, tournament_id): licence_id__startswith=stripped_license, team_registration__tournament=tournament, team_registration__walk_out=False, - team_registration__unregistered=False, ).first() # If the user is registered, retrieve their team registration @@ -771,8 +772,10 @@ def register_tournament(request, tournament_id): email = team_form.cleaned_data['email'], phone_number = team_form.cleaned_data['mobile_number'] ) + player_registration.save() team_registration.set_weight() + team_registration.save() request.session['team_registration'] = [] registration_successful = True @@ -887,22 +890,34 @@ def unregister_tournament(request, tournament_id): messages.error(request, "Vous ne pouvez pas vous désinscrire car vous n'avez pas de numéro de licence associé.") return redirect('tournament-info', tournament_id=tournament_id) - player_registration = PlayerRegistration.objects.filter( licence_id__startswith=user_licence_id, team_registration__tournament_id=tournament_id, - team_registration__walk_out=False, - team_registration__unregistered=False, + source=PlayerDataSource.ONLINE_REGISTRATION, ).first() # Get the first match, if any - other_player = None if player_registration: team_registration = player_registration.team_registration # Get the related TeamRegistration other_player = team_registration.get_other_player(player_registration) - team_registration.unregistered = True # Delete the team registration - team_registration.unregistration_date = timezone.now() - team_registration.save() + + unregistered_team = UnregisteredTeam.objects.create( + tournament=tournament, + unregistration_date=timezone.now(), + ) + + for player in team_registration.playerregistration_set.all(): + unregistered_player = UnregisteredPlayer.objects.create( + unregistered_team=unregistered_team, + first_name=player.first_name, + last_name=player.last_name, + licence_id=player.licence_id, + ) + unregistered_player.save() + + unregistered_team.save() + team_registration.delete() + else: messages.error(request, "La désincription a échouée. Veuillez contacter le juge-arbitre.") return redirect('tournament-info', tournament_id=tournament_id) @@ -938,7 +953,7 @@ def validate_license_id(licence_id, tournament): teams = TeamRegistration.objects.filter(tournament=tournament) # Filter out walkouts and unregistered teams - teams = teams.filter(walk_out=False, unregistered=False) + teams = teams.filter(walk_out=False) # Check if any player in any team in the tournament already has this licence_id # Normalize the licence ID before querying