From 2e443180e951e9446c1ddcf73e87e1ec32ff6063 Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 27 Mar 2025 22:18:38 +0100 Subject: [PATCH 1/7] fix crash --- tournaments/models/match.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tournaments/models/match.py b/tournaments/models/match.py index 15194b8..302bf17 100644 --- a/tournaments/models/match.py +++ b/tournaments/models/match.py @@ -59,7 +59,8 @@ class Match(SideStoreModel): def court_name(self, index): club = None - if self.tournament().event: + tournament = self.tournament() + if tournament and tournament.event: club = self.tournament().event.club if club: From 3b8a03f92f58fa86bc85806d6b91c2690f85de19 Mon Sep 17 00:00:00 2001 From: Laurent Date: Fri, 28 Mar 2025 11:25:44 +0100 Subject: [PATCH 2/7] fix semi finals labl --- tournaments/models/round.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tournaments/models/round.py b/tournaments/models/round.py index 35d1815..2b8fe95 100644 --- a/tournaments/models/round.py +++ b/tournaments/models/round.py @@ -47,7 +47,7 @@ class Round(SideStoreModel): if self.index == 0: return "Finale" elif self.index == 1: - return "Demis" + return "Demies" elif self.index == 2: return "Quarts" else: From 21b0a8567847a801d9e83959116b70a973934547 Mon Sep 17 00:00:00 2001 From: Laurent Date: Fri, 28 Mar 2025 11:49:16 +0100 Subject: [PATCH 3/7] Fix plural of round names --- tournaments/models/round.py | 12 +++++++++--- tournaments/models/tournament.py | 2 +- tournaments/templates/tournaments/matches.html | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/tournaments/models/round.py b/tournaments/models/round.py index 2b8fe95..6d89a8c 100644 --- a/tournaments/models/round.py +++ b/tournaments/models/round.py @@ -47,13 +47,19 @@ class Round(SideStoreModel): if self.index == 0: return "Finale" elif self.index == 1: - return "Demies" + return "Demie" elif self.index == 2: - return "Quarts" + return "Quart" else: squared = 2 ** self.index return f"{squared}ème" + def plural_name(self): + name = self.name() + if self.parent is None and self.index > 0: + return f'{name}s' + return name + def ranking_matches(self, hide_empty_matches): matches = [] for child in self.children.all(): @@ -165,7 +171,7 @@ class Round(SideStoreModel): if first_half_matches: - name = self.name() + name = self.plural_name() if parent_round and first_half_matches[0].name is not None: name = first_half_matches[0].name match_group = self.tournament.create_match_group( diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index 24af6d5..3fb3fde 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -533,7 +533,7 @@ class Tournament(BaseModel): if round and matches: matches.sort(key=lambda m: m.index) - group = self.create_match_group(round.name(), matches) + group = self.create_match_group(round.plural_name(), matches) groups.append(group) ranking_matches = round.ranking_matches(hide_empty_matches) diff --git a/tournaments/templates/tournaments/matches.html b/tournaments/templates/tournaments/matches.html index 90bdb5e..915e48e 100644 --- a/tournaments/templates/tournaments/matches.html +++ b/tournaments/templates/tournaments/matches.html @@ -21,7 +21,7 @@ {% endif %} {% if tournament.display_matches %} {% for round in rounds %} - {{ round.name }} + {{ round.plural_name }} {% endfor %} {% endif %} From c44a876f8e5eaf1cf34f49e10dd9804009898b85 Mon Sep 17 00:00:00 2001 From: Raz Date: Sat, 29 Mar 2025 10:09:48 +0100 Subject: [PATCH 4/7] fix p500+ stuff --- .../0113_tournament_team_count_limit.py | 18 ++++ tournaments/models/tournament.py | 88 +++++++++++++++---- tournaments/services/email_service.py | 8 ++ tournaments/signals.py | 4 +- .../templates/register_tournament.html | 24 ++++- 5 files changed, 121 insertions(+), 21 deletions(-) create mode 100644 tournaments/migrations/0113_tournament_team_count_limit.py 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 %} From 49123a3b58c3cb41f54ab3cd0f4759cf04ee399a Mon Sep 17 00:00:00 2001 From: Laurent Date: Sat, 29 Mar 2025 11:10:44 +0100 Subject: [PATCH 5/7] fix crash --- tournaments/models/tournament.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index 3fb3fde..a7805e6 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -1038,7 +1038,7 @@ class Tournament(BaseModel): return plural_format("jour", self.day_duration) def has_club_address(self): - if self.event.club: + if self.event and self.event.club: return self.event.club.has_address() else: return False From 810c6578bc5ca90d0a70443b7ecce28db6e102db Mon Sep 17 00:00:00 2001 From: Laurent Date: Sat, 29 Mar 2025 11:19:06 +0100 Subject: [PATCH 6/7] Fix crash --- tournaments/models/tournament.py | 7 +++++-- tournaments/services/email_service.py | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index a7805e6..51dcd7f 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -1416,11 +1416,14 @@ class Tournament(BaseModel): else: return True - def umpire_contact(self): + 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() + if self.event and self.event.creator: + return self.event.creator.full_name() + else: + return None def umpire_mail(self): if self.umpire_custom_mail is not None: diff --git a/tournaments/services/email_service.py b/tournaments/services/email_service.py index 6a3a59d..e24aff8 100644 --- a/tournaments/services/email_service.py +++ b/tournaments/services/email_service.py @@ -334,7 +334,8 @@ class TournamentEmailService: def _format_umpire_contact(tournament): contact_parts = [] creator_full_name = tournament.umpire_contact() - contact_parts.append(creator_full_name) + if creator_full_name: + contact_parts.append(creator_full_name) if not tournament.hide_umpire_mail: creator_email = tournament.umpire_mail() From 15f5aae34fca9cce76447c8d4188aba7eab576f6 Mon Sep 17 00:00:00 2001 From: Raz Date: Sat, 29 Mar 2025 14:22:07 +0100 Subject: [PATCH 7/7] fix signal tournament team reg --- tournaments/signals.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/tournaments/signals.py b/tournaments/signals.py index 0eacdf9..d51286c 100644 --- a/tournaments/signals.py +++ b/tournaments/signals.py @@ -113,21 +113,16 @@ 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 - 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: ( - t.registration_date is None, t.registration_date or datetime.min, t.initial_weight, t.team_registration.id - ) if previous_state.team_sorting == TeamSortingType.INSCRIPTION_DATE else - (t.initial_weight, t.team_registration.id) - ) - teams_out_to_warn = sorted_teams[-teams_to_remove_count:] + teams_that_will_be_out = instance.teams(True)[instance.team_count:] + teams_out_to_warn = [ + team for team in teams_that_will_be_out + if team.stage != "Attente" + ] elif previous_state.team_count < instance.team_count: + teams_that_will_be_in = previous_state.teams(True)[previous_state.team_count:instance.team_count] teams_in_to_warn = [ - team for team in previous_state_teams[(instance.team_count - previous_state.team_count):] + team for team in teams_that_will_be_in if team.stage == "Attente" ]