Laurent 9 months ago
commit 7d89d37423
  1. 20
      tournaments/models/enums.py
  2. 8
      tournaments/models/match.py
  3. 42
      tournaments/models/team_registration.py
  4. 180
      tournaments/models/tournament.py
  5. 10
      tournaments/static/tournaments/css/style.css
  6. 10
      tournaments/templates/tournaments/broadcast/broadcasted_match.html
  7. 10
      tournaments/templates/tournaments/broadcast/broadcasted_ranking.html
  8. 10
      tournaments/templates/tournaments/match_cell.html
  9. 12
      tournaments/templates/tournaments/team_stats.html
  10. 3
      tournaments/templates/tournaments/tournament_info.html

@ -96,6 +96,26 @@ class FederalMatchCategory(models.IntegerChoices):
SINGLE_SET_OF_FOUR_GAMES = 13, 'Single set of four games' SINGLE_SET_OF_FOUR_GAMES = 13, 'Single set of four games'
SINGLE_SET_OF_FOUR_GAMES_DECISIVE_POINT = 14, 'Single set of four games with decisive point' SINGLE_SET_OF_FOUR_GAMES_DECISIVE_POINT = 14, 'Single set of four games with decisive point'
@property
def format_label_short(self):
format_mapping = {
self.TWO_SETS: "A1",
self.TWO_SETS_SUPER_TIE: "B1",
self.TWO_SETS_FOUR_GAME: "C1",
self.NINE_GAMES: "D1",
self.SUPER_TIE: "E",
self.TWO_SETS_OF_SUPER_TIE: "G",
self.MEGA_TIE: "F",
self.SINGLE_SET: "H1",
self.SINGLE_SET_DECISIVE_POINT: "H2",
self.TWO_SETS_DECISIVE_POINT: "A2",
self.TWO_SETS_DECISIVE_POINT_SUPER_TIE: "B2",
self.TWO_SETS_FOUR_GAME_DECISIVE_POINT: "C2",
self.NINE_GAMES_DECISIVE_POINT: "D2",
self.SINGLE_SET_OF_FOUR_GAMES: "I1"
}
return format_mapping.get(self, "")
def last_set_is_tie_break(value): def last_set_is_tie_break(value):
if value == FederalMatchCategory.TWO_SETS_FOUR_GAME or value == FederalMatchCategory.TWO_SETS_FOUR_GAME_DECISIVE_POINT or value == FederalMatchCategory.TWO_SETS_SUPER_TIE or value == FederalMatchCategory.SUPER_TIE or value == FederalMatchCategory.MEGA_TIE or value == FederalMatchCategory.TWO_SETS_DECISIVE_POINT_SUPER_TIE: if value == FederalMatchCategory.TWO_SETS_FOUR_GAME or value == FederalMatchCategory.TWO_SETS_FOUR_GAME_DECISIVE_POINT or value == FederalMatchCategory.TWO_SETS_SUPER_TIE or value == FederalMatchCategory.SUPER_TIE or value == FederalMatchCategory.MEGA_TIE or value == FederalMatchCategory.TWO_SETS_DECISIVE_POINT_SUPER_TIE:
return True return True

@ -318,7 +318,9 @@ class Match(models.Model):
group_stage_name = self.group_stage.display_name() group_stage_name = self.group_stage.display_name()
ended = self.end_date is not None ended = self.end_date is not None
livematch = LiveMatch(title, date, time_indication, court, self.started(), ended, group_stage_name) live_format = "Format " + FederalMatchCategory(self.format).format_label_short
livematch = LiveMatch(title, date, time_indication, court, self.started(), ended, group_stage_name, live_format)
for team in self.live_teams(): for team in self.live_teams():
livematch.add_team(team) livematch.add_team(team)
@ -369,7 +371,7 @@ class Team:
} }
class LiveMatch: class LiveMatch:
def __init__(self, title, date, time_indication, court, started, ended, group_stage_name): def __init__(self, title, date, time_indication, court, started, ended, group_stage_name, format):
self.title = title self.title = title
self.date = date self.date = date
self.teams = [] self.teams = []
@ -379,6 +381,7 @@ class LiveMatch:
self.ended = ended self.ended = ended
self.has_walk_out = False self.has_walk_out = False
self.group_stage_name = group_stage_name self.group_stage_name = group_stage_name
self.format = format
def add_team(self, team): def add_team(self, team):
self.teams.append(team) self.teams.append(team)
@ -396,6 +399,7 @@ class LiveMatch:
"ended": self.ended, "ended": self.ended,
"has_walk_out": self.has_walk_out, "has_walk_out": self.has_walk_out,
"group_stage_name": self.group_stage_name, "group_stage_name": self.group_stage_name,
"format": self.format
} }
def show_time_indication(self): def show_time_indication(self):

@ -172,10 +172,32 @@ class TeamRegistration(models.Model):
'points_earned': self.get_points_earned(), 'points_earned': self.get_points_earned(),
'initial_stage': self.get_initial_stage(), 'initial_stage': self.get_initial_stage(),
'matches_played': self.count_matches_played(), 'matches_played': self.count_matches_played(),
'victory_ratio': self.calculate_victory_ratio() 'victory_ratio': self.calculate_victory_ratio(),
'team_rank': self.team_rank_label(),
'total_teams': self.total_teams(),
} }
return stats return stats
def team_rank(self):
teams = self.tournament.teams(False) # Get list of TeamItem objects
try:
# Find the TeamItem that corresponds to this TeamRegistration
team_index = next(i for i, team in enumerate(teams)
if team.team_registration.id == self.id)
return team_index + 1
except (StopIteration, ValueError):
return None
def team_rank_label(self):
team_rank = self.team_rank()
if team_rank is None:
return "--"
return f"{team_rank}"
def total_teams(self):
teams = self.tournament.teams(False)
return f"{len(teams)}"
def get_initial_stage(self): def get_initial_stage(self):
matches = self.get_matches().order_by('start_date') matches = self.get_matches().order_by('start_date')
first_match = matches.first() first_match = matches.first()
@ -190,10 +212,22 @@ class TeamRegistration(models.Model):
def get_final_ranking(self): def get_final_ranking(self):
if self.final_ranking: if self.final_ranking:
if self.final_ranking == 1: if self.final_ranking == 1:
return "1er" return "1er" + self.ranking_delta()
return f"{self.final_ranking}e" return f"{self.final_ranking}ème" + self.ranking_delta()
return None return None
def ranking_delta(self):
team_rank = self.team_rank()
if team_rank is None or self.final_ranking is None:
return ""
sign = "-"
if team_rank > self.final_ranking:
sign = "+"
if team_rank == self.final_ranking:
sign = ""
return f" ({sign}"+f"{abs(self.final_ranking - team_rank)})"
def get_points_earned(self): def get_points_earned(self):
return self.points_earned return self.points_earned
@ -220,6 +254,6 @@ class TeamRegistration(models.Model):
total_matches = matches.count() total_matches = matches.count()
if total_matches > 0: if total_matches > 0:
wins = matches.filter(winning_team_id=self.id).count() wins = matches.filter(winning_team_id=self.id).count()
ratio = (wins / total_matches) * 100 # ratio = (wins / total_matches) * 100
return f"{wins}/{total_matches}" return f"{wins}/{total_matches}"
return None return None

@ -324,126 +324,94 @@ class Tournament(models.Model):
print("else", index, self.team_count) print("else", index, self.team_count)
return -1 return -1
def teams(self, includeWaitingList): def teams(self, include_waiting_list):
# print("Starting teams method") """
bracket_teams = [] Get sorted list of teams for the tournament.
group_stage_teams = []
waiting_teams = [] Args:
teams = [] include_waiting_list (bool): Whether to include teams in waiting list
Returns:
list: List of TeamItem objects sorted according to tournament rules
"""
# Initialize team categories
complete_teams = []
wildcard_bracket = [] wildcard_bracket = []
wildcard_group_stage = [] wildcard_group_stage = []
complete_teams = [] waiting_teams = []
closed_registration_date = self.closed_registration_date
# print(f"Closed registration date: {closed_registration_date}")
for team_registration in self.teamregistration_set.all(): # Get registration cutoff date
# print(f"Processing team registration: {team_registration}") closed_date = self.closed_registration_date
is_valid = False
if closed_registration_date is not None and team_registration.registration_date is not None and team_registration.registration_date <= closed_registration_date: # Process each team registration
is_valid = True for team_reg in self.teamregistration_set.all():
if closed_registration_date is None: if team_reg.out_of_tournament():
is_valid = True continue
if team_registration.registration_date is None:
is_valid = True # Create team item
# print(f"Is valid: {is_valid}") team = TeamItem(team_reg)
if team_registration.out_of_tournament() is False:
team = TeamItem(team_registration) # Determine if registration is valid based on date
# print(f"Created team: {team}") is_valid = (
if team_registration.group_stage_position is not None: closed_date is None or
team.set_stage("Poule") team_reg.registration_date is None or
elif team_registration.bracket_position is not None: (team_reg.registration_date and team_reg.registration_date <= closed_date)
team.set_stage("Tableau") )
else:
team.set_stage("Attente")
# print(f"Team stage: {team.stage}")
teams.append(team) # Categorize team
if team_registration.wild_card_bracket: if team_reg.wild_card_bracket:
wildcard_bracket.append(team) wildcard_bracket.append(team)
elif team_registration.wild_card_group_stage: elif team_reg.wild_card_group_stage:
wildcard_group_stage.append(team) wildcard_group_stage.append(team)
elif is_valid is True: elif is_valid:
complete_teams.append(team) complete_teams.append(team)
else: else:
waiting_teams.append(team) waiting_teams.append(team)
# print(f"Total teams: {len(teams)}") # Set initial stage
# print(f"Wildcard bracket: {len(wildcard_bracket)}") if team_reg.group_stage_position is not None:
# print(f"Wildcard group stage: {len(wildcard_group_stage)}")
# print(f"Complete teams: {len(complete_teams)}")
# print(f"Waiting teams: {len(waiting_teams)}")
if len(teams) < self.team_count:
teams.sort(key=lambda s: (s.initial_weight, s.team_registration.id))
return teams
seeds_count = min(self.team_count, len(teams)) - self.group_stage_count * self.teams_per_group_stage - len(wildcard_bracket)
group_stage_members_count = self.group_stage_count * self.teams_per_group_stage - len(wildcard_group_stage)
if group_stage_members_count < 0:
group_stage_members_count = 0
if seeds_count < 0:
seeds_count = 0
# print(f"Seeds count: {seeds_count}")
# print(f"Group stage members count: {group_stage_members_count}")
if self.team_sorting == TeamSortingType.INSCRIPTION_DATE:
complete_teams.sort(key=lambda s: (s.registration_date is None, s.registration_date or datetime.min, s.initial_weight, s.team_registration.id))
else:
complete_teams.sort(key=lambda s: (s.initial_weight, s.team_registration.id))
selected_teams = complete_teams[:self.team_count]
selected_teams.sort(key=lambda s: (s.initial_weight, s.team_registration.id))
if seeds_count > 0:
bracket_teams = selected_teams[:seeds_count] + wildcard_bracket
else:
bracket_teams = []
# print(f"Bracket teams: {len(bracket_teams)}")
if group_stage_members_count:
group_stage_end = seeds_count + group_stage_members_count
group_stage_teams = selected_teams[seeds_count:group_stage_end] + wildcard_group_stage
else:
group_stage_teams = []
# print(f"Group stage teams: {len(group_stage_teams)}")
waiting_list_count = len(teams) - self.team_count
if waiting_list_count < 0:
waiting_list_count = 0
# print(f"Waiting list count: {waiting_list_count}")
if waiting_list_count > 0 or len(waiting_teams) > 0:
if waiting_list_count > 0:
waiting_teams = waiting_teams + complete_teams[-waiting_list_count:]
if self.team_sorting == TeamSortingType.INSCRIPTION_DATE:
waiting_teams.sort(key=lambda s: (s.registration_date is None, s.registration_date or datetime.min, s.initial_weight, s.team_registration.id))
else:
waiting_teams.sort(key=lambda s: (s.initial_weight, s.team_registration.id))
else:
waiting_teams = []
# print(f"Final waiting teams: {len(waiting_teams)}")
bracket_teams.sort(key=lambda s: (s.weight, s.team_registration.id))
group_stage_teams.sort(key=lambda s: (s.weight, s.team_registration.id))
for team in bracket_teams:
if team.stage == "Attente":
team.set_stage("Tableau")
for team in group_stage_teams:
if team.stage == "Attente":
team.set_stage("Poule") team.set_stage("Poule")
elif team_reg.bracket_position is not None:
for team in waiting_teams: team.set_stage("Tableau")
else:
team.set_stage("Attente") team.set_stage("Attente")
if includeWaitingList is True: # Sort teams based on tournament rules
final_teams = bracket_teams + group_stage_teams + waiting_teams if self.team_sorting == TeamSortingType.INSCRIPTION_DATE:
# print(f"Final teams with waiting list: {len(final_teams)}") complete_teams.sort(key=lambda t: (
t.registration_date is None,
t.registration_date or datetime.min,
t.initial_weight,
t.team_registration.id
))
waiting_teams.sort(key=lambda t: (
t.registration_date is None,
t.registration_date or datetime.min,
t.initial_weight,
t.team_registration.id
))
else: else:
final_teams = bracket_teams + group_stage_teams complete_teams.sort(key=lambda t: (t.initial_weight, t.team_registration.id))
# print(f"Final teams without waiting list: {len(final_teams)}") waiting_teams.sort(key=lambda t: (t.initial_weight, t.team_registration.id))
return final_teams
# Determine final team list based on tournament settings
if len(complete_teams) <= self.team_count:
all_teams = wildcard_bracket + wildcard_group_stage + complete_teams
if include_waiting_list:
all_teams.extend(waiting_teams)
return all_teams
# Split teams into main bracket and waiting list
qualified_teams = complete_teams[:self.team_count]
excess_teams = complete_teams[self.team_count:]
# Combine all waiting list teams
waiting_list = excess_teams + waiting_teams
# Return final sorted list
if include_waiting_list:
return wildcard_bracket + wildcard_group_stage + qualified_teams + waiting_list
return wildcard_bracket + wildcard_group_stage + qualified_teams
def match_groups(self, broadcasted, group_stage_id, round_id): def match_groups(self, broadcasted, group_stage_id, round_id):

@ -246,6 +246,7 @@ tr {
font-family: "Anybody-ExtraBold"; font-family: "Anybody-ExtraBold";
font-size: 1.2em; font-size: 1.2em;
color: #1b223a; color: #1b223a;
line-height: 24px; /* Match the height of flex-row */
} }
.title { .title {
@ -816,3 +817,12 @@ h-margin {
.group-stage-link:hover { .group-stage-link:hover {
color: #f39200; /* Or whatever hover color you prefer */ color: #f39200; /* Or whatever hover color you prefer */
} }
.tournament-info a {
color: #f39200;
text-decoration: underline;
font-weight: bold;
}
.tournament-info a:hover {
color: #f39200;
}

@ -2,9 +2,13 @@
<div class="flex-row"> <div class="flex-row">
<label class="left-label matchtitle"> <label class="left-label matchtitle">
<span x-text="match.group_stage_name"></span><span x-show="match.group_stage_name"> :</span> <span x-text="match.title"></span> <span x-text="match.group_stage_name"></span>
<span x-show="match.group_stage_name"> :</span>
<span x-text="match.title"></span>
</label>
<label class="right-label minor-info bold" x-show="match.ended === false">
<span x-text="match.court"></span>
</label> </label>
<!-- <label class="right-label info"><span x-text="match.date"></span></label> -->
</div> </div>
<template x-for="i in match.teams.length"> <template x-for="i in match.teams.length">
@ -59,7 +63,7 @@
<div class="top-margin flex-row"> <div class="top-margin flex-row">
<label class="left-label minor-info bold"><span x-text="match.time_indication"></span></label> <label class="left-label minor-info bold"><span x-text="match.time_indication"></span></label>
<label class="right-label minor-info semibold"><span x-text="match.court"></span></label> <label class="right-label minor-info semibold"><span x-text="match.format"></span></label>
</div> </div>
</div> </div>

@ -3,7 +3,11 @@
<div> <div>
<div class="table-row-3-colums-ranks"> <div class="table-row-3-colums-ranks">
<div class="table-cell"><div class="mybox center"><span x-text="ranking.formatted_ranking"></div></div> <div class="table-cell">
<div class="mybox center">
<span style="font-size: 1.4em;" x-text="ranking.formatted_ranking"></span>
</div>
</div>
<div class="table-cell table-cell-large padding-left semibold"> <div class="table-cell table-cell-large padding-left semibold">
<template x-for="name in ranking.names" > <template x-for="name in ranking.names" >
@ -11,7 +15,9 @@
</template> </template>
</div> </div>
{% if tournament.display_points_earned %} {% if tournament.display_points_earned %}
<div class="table-cell right horizontal-padding numbers"><span x-text="ranking.points"></div> <div class="table-cell right horizontal-padding numbers">
<span style="font-size: 1.4em;" x-text="ranking.points"></span>
</div>
{% endif %} {% endif %}
</div> </div>
<div x-show="index < column.length - 1"> <div x-show="index < column.length - 1">

@ -3,10 +3,12 @@
<div class="cell medium-12 large-3 my-block"> <div class="cell medium-12 large-3 my-block">
<div class="bubble"> <div class="bubble">
<!-- <div class="flex-row"> --> <div class="flex-row">
<label class="matchtitle">{{ match.title }}</label> <label class="matchtitle">{{ match.title }}</label>
<!-- <label class="right-label info">{{ match.date }}</label> --> {% if not match.ended %}
<!-- </div> --> <label class="right-label minor-info bold">{{ match.court }}</label>
{% endif %}
</div>
<div> <div>
@ -62,7 +64,7 @@
</label> </label>
<label class="right-label minor-info"> <label class="right-label minor-info">
{% if not match.ended %} {% if not match.ended %}
{{ match.court }} {{ match.format }}
{% endif %} {% endif %}
</label> </label>
<!-- <a href="" class="right-label">{{ match.court }}</a> --> <!-- <a href="" class="right-label">{{ match.court }}</a> -->

@ -16,6 +16,18 @@
</div> </div>
</div> </div>
<div class="match-result bottom-border">
<div class="player">
<div class="semibold">
<strong>Position initiale</strong>
</div>
</div>
<div class="scores">
<span class="score ws numbers">{{ stats.team_rank }} / {{ stats.total_teams }}</span>
</div>
</div>
{% if stats.final_ranking %} {% if stats.final_ranking %}
<div class="match-result bottom-border"> <div class="match-result bottom-border">
<div class="player"> <div class="player">

@ -99,7 +99,8 @@
{% if tournament.information %} {% if tournament.information %}
<p> <p>
<div class="semibold">Infos</div> <div class="semibold">Infos</div>
<div>{{ tournament.information|linebreaksbr }}</div> <div class="tournament-info">
{{ tournament.information|linebreaksbr|urlize }}</div>
</p> </p>
{% endif %} {% endif %}

Loading…
Cancel
Save