diff --git a/tournaments/migrations/0113_tournament_team_count_limit.py b/tournaments/migrations/0113_tournament_team_count_limit.py new file mode 100644 index 0000000..2b113cb --- /dev/null +++ b/tournaments/migrations/0113_tournament_team_count_limit.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1 on 2025-03-29 08:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tournaments', '0112_tournament_disable_ranking_federal_ruling_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='tournament', + name='team_count_limit', + field=models.BooleanField(default=True), + ), + ] diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index 24af6d5..42ab95b 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -67,6 +67,7 @@ class Tournament(BaseModel): initial_seed_round = models.IntegerField(default=0) initial_seed_count = models.IntegerField(default=0) enable_online_registration = models.BooleanField(default=False) # Equivalent to Bool = false + team_count_limit = models.BooleanField(default=True) registration_date_limit = models.DateTimeField(null=True, blank=True) # Equivalent to Date? = nil opening_registration_date = models.DateTimeField(null=True, blank=True) # Equivalent to Date? = nil waiting_list_limit = models.IntegerField(null=True, blank=True) # Equivalent to Int? = nil @@ -397,7 +398,7 @@ class Tournament(BaseModel): complete_teams.append(team) else: waiting_teams.append(team) - wildcard_bracket = [] + return complete_teams, wildcard_bracket, wildcard_group_stage, waiting_teams def sort_teams(self, include_waiting_list, complete_teams, wildcard_bracket, wildcard_group_stage, waiting_teams): @@ -1051,19 +1052,22 @@ class Tournament(BaseModel): def options_online_registration(self): options = [] + timezone = self.timezone() # Date d'ouverture if self.opening_registration_date: - date = formats.date_format(timezone.localtime(self.opening_registration_date), format='j F Y H:i') + date = formats.date_format(self.opening_registration_date.astimezone(timezone), format='j F Y H:i') options.append(f"Ouverture des inscriptions le {date}") # Date limite if self.registration_date_limit: - date = formats.date_format(timezone.localtime(self.registration_date_limit), format='j F Y H:i') + date = formats.date_format(self.registration_date_limit.astimezone(timezone), format='j F Y H:i') options.append(f"Clôture des inscriptions le {date}") + options.append(self.get_selection_status_localized) + # Cible d'équipes - if self.team_count: + if self.team_count_limit is True: options.append(f"Maximum {self.team_count} équipes") # Liste d'attente @@ -1122,11 +1126,17 @@ class Tournament(BaseModel): return False return True + def get_selection_status_localized(self): + if self.team_sorting == TeamSortingType.RANK: + return "La sélection se fait par le poids de l'équipe" + else: + return "La sélection se fait par date d'inscription" + def get_online_registration_status(self): if self.supposedly_in_progress(): return OnlineRegistrationStatus.ENDED if self.closed_registration_date is not None: - return OnlineRegistrationStatus.ENDED + return OnlineRegistrationStatus.WAITING_LIST_POSSIBLE if self.end_date is not None: return OnlineRegistrationStatus.ENDED_WITH_RESULTS @@ -1140,9 +1150,12 @@ class Tournament(BaseModel): if self.registration_date_limit is not None: timezoned_datetime = timezone.localtime(self.registration_date_limit) if now > timezoned_datetime: - return OnlineRegistrationStatus.ENDED + return OnlineRegistrationStatus.WAITING_LIST_POSSIBLE + + if self.team_sorting == TeamSortingType.RANK: + return OnlineRegistrationStatus.OPEN - if self.team_count is not None: + if self.team_count_limit is True: # Get all team registrations excluding walk_outs current_team_count = self.team_registrations.exclude(walk_out=True).count() if current_team_count >= self.team_count: @@ -1174,8 +1187,14 @@ class Tournament(BaseModel): return True def get_waiting_list_position(self): + current_time = timezone.now() + local_registration_federal_limit = self.local_registration_federal_limit() + if self.team_sorting == TeamSortingType.RANK and local_registration_federal_limit is not None: + if current_time < local_registration_federal_limit: + return -1 + # If no target team count exists, no one goes to waiting list - if self.team_count is None: + if self.team_count_limit is False: return -1 # Get count of active teams (not walked out) @@ -1257,7 +1276,6 @@ class Tournament(BaseModel): current_year += 1 user_age = current_year - int(birth_year) - print("user_age", user_age) # Check age category restrictions if self.federal_age_category == FederalAgeCategory.A11_12 and user_age > 12: @@ -1290,12 +1308,49 @@ class Tournament(BaseModel): def min_player_rank(self): return FederalLevelCategory.min_player_rank(self.federal_level_category, self.federal_category, self.federal_age_category) - def first_waiting_list_team(self, teams): + def local_registration_federal_limit(self): + timezone = self.timezone() + if self.registration_date_limit is not None: + return self.registration_date_limit.astimezone(timezone) + + if self.closed_registration_date is not None: + return self.closed_registration_date.astimezone(timezone) + + local_start_date = self.local_start_date() + + if local_start_date is None: + return None + + if self.federal_level_category == FederalLevelCategory.P500: + # 7 days before at 23:59 + return (local_start_date - timedelta(days=7)).replace(hour=23, minute=59, second=59) + elif self.federal_level_category in [FederalLevelCategory.P1000, + FederalLevelCategory.P1500, + FederalLevelCategory.P2000]: + # 14 days before at 23:59 + return (local_start_date - timedelta(days=14)).replace(hour=23, minute=59, second=59) + return None + + def waiting_list_teams(self, teams): + current_time = timezone.now() + local_registration_federal_limit = self.local_registration_federal_limit() + if self.team_sorting == TeamSortingType.RANK and local_registration_federal_limit is not None: + if current_time < local_registration_federal_limit: + return None + if len(teams)<=self.team_count: return None waiting_teams = [team for team in teams if team.stage == "Attente"] - if len(waiting_teams) > 0: - return waiting_teams[0].team_registration + return waiting_teams + + def first_waiting_list_team(self, teams): + waiting_list_team = self.waiting_list_teams(teams) + if waiting_list_team is None: + return None + if len(waiting_list_team) > 0: + return waiting_list_team[0].team_registration + else: + return None def broadcasted_prog(self): # Get matches from broadcasted_matches_and_group_stages @@ -1418,19 +1473,16 @@ class Tournament(BaseModel): def umpire_contact(self): if self.umpire_custom_contact is not None: - print(self.umpire_custom_contact) return self.umpire_custom_contact return self.event.creator.full_name() def umpire_mail(self): if self.umpire_custom_mail is not None: - print(self.umpire_custom_mail) return self.umpire_custom_mail return self.event.creator.email def umpire_phone(self): if self.umpire_custom_phone is not None: - print(self.umpire_custom_phone) return self.umpire_custom_phone return self.event.creator.phone @@ -1486,6 +1538,12 @@ class TeamSummon: } class TeamItem: + def __str__(self): + return f"TeamItem({self.team_registration.id}, names={self.names}, stage={self.stage})" + + def __repr__(self): + return self.__str__() + def __init__(self, team_registration): self.names = team_registration.team_names() self.date = team_registration.local_call_date() diff --git a/tournaments/services/email_service.py b/tournaments/services/email_service.py index 6a3a59d..c017685 100644 --- a/tournaments/services/email_service.py +++ b/tournaments/services/email_service.py @@ -2,6 +2,7 @@ from django.core.mail import EmailMessage from django.utils import timezone from django.urls import reverse from enum import Enum +from ..models.tournament import TeamSortingType class TeamEmailType(Enum): REGISTERED = "registered" @@ -74,6 +75,13 @@ class TournamentEmailService: body_parts.append(f"Votre inscription en liste d'attente du tournoi {tournament_details_str} est confirmée.") else: body_parts.append(f"Votre inscription au tournoi {tournament_details_str} est confirmée.") + if tournament.team_sort == TeamSortingType.RANK: + cloture_date = tournament.local_registration_federal_limit().strftime("%d/%m/%Y à %H:%M") + loc = "" + if cloture_date is not None: + loc = f", prévu le {cloture_date}" + body_parts.append(f"Attention, la sélection définitive se fera par poids d'équipe à la clôture des inscriptions{loc}.") + absolute_url = f"https://padelclub.app/tournament/{tournament.id}/info" link_text = "informations sur le tournoi" diff --git a/tournaments/signals.py b/tournaments/signals.py index 48859cb..0eacdf9 100644 --- a/tournaments/signals.py +++ b/tournaments/signals.py @@ -113,10 +113,10 @@ def check_waiting_list(sender, instance, **kwargs): teams_out_to_warn = [] teams_in_to_warn = [] + previous_state_teams = previous_state.teams(True) if previous_state.team_count > instance.team_count: teams_to_remove_count = previous_state.team_count - instance.team_count - previous_state_teams = previous_state.teams(True) sorted_teams = sorted( [team for team in previous_state_teams if team.stage != "Attente" and not (team.wildcard_bracket or team.wildcard_groupstage)], key=lambda t: ( @@ -127,7 +127,7 @@ def check_waiting_list(sender, instance, **kwargs): teams_out_to_warn = sorted_teams[-teams_to_remove_count:] elif previous_state.team_count < instance.team_count: teams_in_to_warn = [ - team for team in previous_state.teams(True)[(instance.team_count - previous_state.team_count):] + team for team in previous_state_teams[(instance.team_count - previous_state.team_count):] if team.stage == "Attente" ] diff --git a/tournaments/templates/register_tournament.html b/tournaments/templates/register_tournament.html index 59f9d6d..c8349d1 100644 --- a/tournaments/templates/register_tournament.html +++ b/tournaments/templates/register_tournament.html @@ -26,6 +26,22 @@ Un email de confirmation a été envoyé à l'adresse associée à votre compte Padel Club ({{ user.email }}). Pensez à vérifier vos spams si vous ne recevez pas l'email. En cas de problème, contactez le juge-arbitre.

{% else %} + + {% if team_form.errors %} +
+ {% if team_form.non_field_errors %} + {% for error in team_form.non_field_errors %} +

{{ error }}

+ {% endfor %} + {% endif %} + + {% for field in team_form %} + {% for error in field.errors %} +

{{ error }}

+ {% endfor %} + {% endfor %} +
+ {% endif %}
{% csrf_token %} @@ -109,15 +125,15 @@ {% endif %}
- {% if form.errors %} + {% if add_player_form.errors %}
- {% if form.non_field_errors %} - {% for error in form.non_field_errors %} + {% if add_player_form.non_field_errors %} + {% for error in add_player_form.non_field_errors %}

{{ error }}

{% endfor %} {% endif %} - {% for field in form %} + {% for field in add_player_form %} {% for error in field.errors %}

{{ error }}

{% endfor %}