diff --git a/tournaments/models/match.py b/tournaments/models/match.py index a555531..71571c4 100644 --- a/tournaments/models/match.py +++ b/tournaments/models/match.py @@ -206,10 +206,14 @@ class Match(models.Model): teams.append(team) if previous_top_match: names = [f"Gagnant {previous_top_match.computed_name()}"] + if previous_top_match.disabled: + names = ["Perdant tableau principal"] team = self.default_live_team(names) teams.append(team) if previous_bottom_match: names = [f"Gagnant {previous_bottom_match.computed_name()}"] + if previous_bottom_match.disabled: + names = ["Perdant tableau principal"] team = self.default_live_team(names) teams.append(team) elif self.round and self.round.parent is None: diff --git a/tournaments/models/round.py b/tournaments/models/round.py index ea89370..7d740bc 100644 --- a/tournaments/models/round.py +++ b/tournaments/models/round.py @@ -23,10 +23,12 @@ class Round(models.Model): # return f"{self.tournament.display_name()} - {self.name()}" def name(self): - if self.parent: - return "Matchs de classement" + if self.parent and self.parent.parent is None: + return f"Classement {self.parent.name()}" + elif self.parent and self.parent.parent is not None: + return f"{self.parent.name()}" elif self.group_stage_loser_bracket is True: - return "Matchs de classement de poule" + return "Classement de poule" else: if self.index == 0: return "Finale" @@ -92,3 +94,67 @@ class Round(models.Model): return False return True + + def prepare_match_group(self, next_round, parent_round, loser_final, double_butterfly_mode): + matches = self.match_set.filter(disabled=False).order_by('index') + if len(matches) == 0: + return None + if next_round: + next_round_matches = next_round.match_set.filter(disabled=False).order_by('index') + else: + next_round_matches = [] + + if len(matches) < len(next_round_matches): + all_matches = self.match_set.order_by('index') + filtered_matches = [] + + # Process matches in pairs + i = 0 + while i < len(all_matches): + # Get the current match and its pair (if available) + current_match = all_matches[i] + pair_match = all_matches[i+1] if i+1 < len(all_matches) else None + + # Only filter out the pair if both matches are disabled + if current_match.disabled and pair_match and pair_match.disabled: + # Skip one of the matches in the pair + filtered_matches.append(current_match) + pass + else: + # Keep the current match + if current_match.disabled == False: + filtered_matches.append(current_match) + # If there's a pair match, keep it too + if pair_match and pair_match.disabled == False: + filtered_matches.append(pair_match) + + # Move to the next pair + i += 2 + + # Replace the matches list with our filtered list + matches = filtered_matches + + if matches: + if len(matches) > 1 and double_butterfly_mode: + midpoint = int(len(matches) / 2) + first_half_matches = matches[:midpoint] + else: + first_half_matches = list(matches) # Convert QuerySet to a list + if self.index == 0 and loser_final: + loser_match = loser_final.match_set.first() + if loser_match: + first_half_matches.append(loser_match) + + + if first_half_matches: + name = self.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( + name=name, + matches=first_half_matches, + round_id=self.id + ) + return match_group + + return None diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index 5574cc5..9dd1a8c 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -1328,6 +1328,44 @@ class Tournament(models.Model): return ordered_matches + def get_butterfly_bracket_match_group(self, parent_round=None, double_butterfly_mode=False, display_loser_final=False): + loser_final = None + main_rounds_reversed = [] + + # Get main bracket rounds (excluding children/ranking matches) + main_rounds = self.round_set.filter( + parent=parent_round, + group_stage_loser_bracket=False + ).order_by('-index') + + count = main_rounds.count() + if display_loser_final and count > 1: + semi = main_rounds[count - 2] + loser_final = self.round_set.filter( + parent=semi, + group_stage_loser_bracket=False + ).order_by('index').first() + + # Create serializable match groups data + serializable_match_groups = [] + + # Add first half of each round (from last to semi-finals) + for round in main_rounds: + next_round = main_rounds.filter(index=round.index - 1).first() + match_group = round.prepare_match_group(next_round, parent_round, loser_final, double_butterfly_mode) + if match_group: + serializable_match_groups.append(match_group) + + if double_butterfly_mode: + main_rounds_reversed = list(main_rounds) + main_rounds_reversed.reverse() + for round in main_rounds_reversed: + next_round = main_rounds.filter(index=round.index - 1).first() + match_group = round.prepare_match_group(next_round, parent_round, None, double_butterfly_mode) + if match_group: + serializable_match_groups.append(match_group) + + return serializable_match_groups class MatchGroup: def __init__(self, name, matches, formatted_schedule, round_id=None): diff --git a/tournaments/templates/tournaments/tournament_bracket.html b/tournaments/templates/tournaments/tournament_bracket.html index a3eeaa0..418f757 100644 --- a/tournaments/templates/tournaments/tournament_bracket.html +++ b/tournaments/templates/tournaments/tournament_bracket.html @@ -20,6 +20,7 @@ data-disabled="{{ match.disabled|lower }}" data-match-group-name="{{ match_group.name }}" data-match-format="{{ match.format }}" + data-match-title="{{ match.title }}" data-round-id="{{ match_group.round_id }}" class="match-template"> {% include 'tournaments/bracket_match_cell.html' %} @@ -89,7 +90,7 @@ function renderBracket() { let nameSpan = document.createElement('div'); nameSpan.className = 'round-name'; nameSpan.textContent = matchGroupName; - if (roundIndex == finalRoundIndex) { + if (roundIndex == finalRoundIndex || roundIndex == finalRoundIndex - 1 && displayLoserFinal ||roundIndex == finalRoundIndex + 1 && displayLoserFinal) { } else { nameSpan = document.createElement('a'); nameSpan.className = 'round-name btn small-button'; @@ -124,6 +125,7 @@ function renderBracket() { matchPositions[roundIndex] = []; matchDisabled[roundIndex] = []; // Initialize array for this round roundMatches.forEach((matchTemplate, matchIndex) => { + const matchTitle = matchTemplate.dataset.matchTitle; const matchDiv = document.createElement('div'); matchDiv.className = 'butterfly-match'; @@ -146,7 +148,7 @@ function renderBracket() { if (roundCount > 1) { const nextMatchesCount = rounds[roundIndex + 1].length; - if (currentMatchesCount == nextMatchesCount) { + if (currentMatchesCount == nextMatchesCount && roundCount > 2) { nextMatchDistance = 0; } } else { @@ -186,12 +188,19 @@ function renderBracket() { const parentPos2 = matchPositions[roundIndex - 1][parentIndex2]; top = (parentPos1 + parentPos2) / 2; nextMatchDistance = 0; + + if (displayLoserFinal == true) { + if (matchIndex == 1) { + top = matchPositions[roundIndex][0] + baseDistance + 80; + isIncomingLineIsDisabled = true; + } + } } } else if (roundIndex < finalRoundIndex) { const previousMatchesCount = rounds[roundIndex - 1].length; const nextMatchesCount = rounds[roundIndex + 1].length; - if (currentMatchesCount == nextMatchesCount) { + if (currentMatchesCount == nextMatchesCount && displayLoserFinal == false) { nextMatchDistance = 0; } else if (matchPositions.length > roundIndex - 1) { nextMatchDistance = (matchPositions[roundIndex - 1][1] - matchPositions[roundIndex - 1][0]); @@ -273,12 +282,28 @@ function renderBracket() { matchesContainer.appendChild(titleDiv); } + if (roundIndex == finalRoundIndex && matchIndex === 1 && displayLoserFinal == true && doubleButterflyMode == false) { + let nameSpan = document.createElement('div'); + nameSpan.className = 'round-name'; + nameSpan.textContent = matchTitle; + const formatSpan = document.createElement('div'); + formatSpan.className = 'round-format'; + formatSpan.textContent = matchFormat; + const titleDiv = document.createElement('div'); + titleDiv.className = 'round-title'; + titleDiv.appendChild(nameSpan); + titleDiv.appendChild(formatSpan); + titleDiv.style.top = `${top - 80}px`; // Adjust the 60px offset as needed + titleDiv.style.position = 'absolute'; + matchesContainer.appendChild(titleDiv); + } + matchDiv.innerHTML = `