From a408a56b69821d67b3206b36fb0b0435ae49a651 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Thu, 13 Mar 2025 19:23:00 +0100 Subject: [PATCH 01/10] fix nav menu gap --- tournaments/static/tournaments/css/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tournaments/static/tournaments/css/style.css b/tournaments/static/tournaments/css/style.css index c7a7bd2..30c0c3d 100644 --- a/tournaments/static/tournaments/css/style.css +++ b/tournaments/static/tournaments/css/style.css @@ -69,10 +69,10 @@ nav { /* Allow items to wrap onto multiple lines */ justify-content: flex-start; /* Align items to the start */ + gap: 6px; /* This adds spacing between items in all directions */ } nav a { - margin-right: 6px; color: #707070; padding: 8px 12px; background-color: #fae7ce; From 9cf9e07bd95aef4286a1735933af2c3f2ff37232 Mon Sep 17 00:00:00 2001 From: Laurent Date: Fri, 14 Mar 2025 18:27:38 +0100 Subject: [PATCH 02/10] hide bracket --- tournaments/templates/tournaments/navigation_tournament.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tournaments/templates/tournaments/navigation_tournament.html b/tournaments/templates/tournaments/navigation_tournament.html index fd5995f..e144d9a 100644 --- a/tournaments/templates/tournaments/navigation_tournament.html +++ b/tournaments/templates/tournaments/navigation_tournament.html @@ -2,9 +2,9 @@ Informations - {% if tournament.display_matches %} + {% if tournament.display_matches or tournament.display_group_stages %} Matches From 14abf81de5bd52baf0d75ed48e648f8c70145f3f Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Fri, 14 Mar 2025 19:32:44 +0100 Subject: [PATCH 03/10] add wip bracket broadcast and bug fixes --- tournaments/models/match.py | 10 + tournaments/models/round.py | 4 +- tournaments/models/tournament.py | 18 +- tournaments/static/tournaments/css/style.css | 8 + .../tournaments/css/tournament_bracket.css | 231 +++++++++ .../tournaments/js/tournament_bracket.js | 333 ++++++++++++ tournaments/templates/tournaments/base.html | 1 + .../tournaments/bracket_match_cell.html | 2 +- .../tournaments/broadcast/broadcast_club.html | 3 +- .../broadcast/broadcasted_bracket.html | 160 ++++++ .../tournaments/tournament_bracket.html | 483 +----------------- tournaments/urls.py | 2 + tournaments/views.py | 43 +- 13 files changed, 816 insertions(+), 482 deletions(-) create mode 100644 tournaments/static/tournaments/css/tournament_bracket.css create mode 100644 tournaments/static/tournaments/js/tournament_bracket.js create mode 100644 tournaments/templates/tournaments/broadcast/broadcasted_bracket.html diff --git a/tournaments/models/match.py b/tournaments/models/match.py index 71571c4..329fab6 100644 --- a/tournaments/models/match.py +++ b/tournaments/models/match.py @@ -268,6 +268,16 @@ class Match(models.Model): elif len(team_scores) == 2: # Both team scores present teams.extend([team_score.live_team(self) for team_score in team_scores]) + + if self.round is not None and self.round.parent is None: + pos1 = team_scores[0].team_registration.bracket_position if hasattr(team_scores[0], 'team_registration') and team_scores[0].team_registration else None + pos2 = team_scores[1].team_registration.bracket_position if hasattr(team_scores[1], 'team_registration') and team_scores[1].team_registration else None + if pos1 is not None and pos2 is not None and pos1 // 2 == self.index and pos2 // 2 == self.index: + if pos1 > pos2: + teams = [team_scores[1].live_team(self), team_scores[0].live_team(self)] + else: + teams = [team_scores[0].live_team(self), team_scores[1].live_team(self)] + else: teams.extend([team_score.live_team(self) for team_score in team_scores if team_score.walk_out != 1]) diff --git a/tournaments/models/round.py b/tournaments/models/round.py index 7d740bc..0115a33 100644 --- a/tournaments/models/round.py +++ b/tournaments/models/round.py @@ -95,7 +95,7 @@ class Round(models.Model): return True - def prepare_match_group(self, next_round, parent_round, loser_final, double_butterfly_mode): + def prepare_match_group(self, next_round, parent_round, loser_final, double_butterfly_mode, secondHalf): matches = self.match_set.filter(disabled=False).order_by('index') if len(matches) == 0: return None @@ -138,6 +138,8 @@ class Round(models.Model): if len(matches) > 1 and double_butterfly_mode: midpoint = int(len(matches) / 2) first_half_matches = matches[:midpoint] + if secondHalf: + first_half_matches = matches[midpoint:] else: first_half_matches = list(matches) # Convert QuerySet to a list if self.index == 0 and loser_final: diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index 270530a..2a69060 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -1362,7 +1362,7 @@ class Tournament(models.Model): # 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) + match_group = round.prepare_match_group(next_round, parent_round, loser_final, double_butterfly_mode, False) if match_group: serializable_match_groups.append(match_group) @@ -1370,10 +1370,11 @@ class Tournament(models.Model): 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) + if round.index > 0: + next_round = main_rounds.filter(index=round.index - 1).first() + match_group = round.prepare_match_group(next_round, parent_round, None, double_butterfly_mode, True) + if match_group: + serializable_match_groups.append(match_group) return serializable_match_groups @@ -1390,6 +1391,13 @@ class MatchGroup: def add_matches(self, matches): self.matches = matches + def to_dict(self): + return { + 'name': self.name, + 'round_id': self.round_id, + 'matches': [match.to_dict() for match in self.matches] + } + class TeamSummon: def __init__(self, id, names, date, weight, stage, court, image, day_duration): self.id = str(id) diff --git a/tournaments/static/tournaments/css/style.css b/tournaments/static/tournaments/css/style.css index 30c0c3d..6e006b6 100644 --- a/tournaments/static/tournaments/css/style.css +++ b/tournaments/static/tournaments/css/style.css @@ -869,3 +869,11 @@ h-margin { transform: translate(-50%, -50%); white-space: nowrap; } + +.even-row { + background-color: #ffffff; /* White */ +} + +.odd-row { + background-color: #e6f2ff; /* Light blue */ +} diff --git a/tournaments/static/tournaments/css/tournament_bracket.css b/tournaments/static/tournaments/css/tournament_bracket.css new file mode 100644 index 0000000..6632ca8 --- /dev/null +++ b/tournaments/static/tournaments/css/tournament_bracket.css @@ -0,0 +1,231 @@ +.round-logo img { + width: 50px; + height: auto; + display: block; + margin: 0 auto; + position: relative; + top: -100px; /* Increased negative value to move it higher up */ +} + +.round-logo img { + width: 100px; /* Adjust size as needed */ + height: auto; + display: block; + margin: 0 auto; +} +.butterfly-match.same-level::before { + display: none; +} + +/* Adjust styling for matches with single parent */ +.match-content.disabled { + visibility: hidden; +} + +.incoming-line.disabled, +.butterfly-match:has(.match-content.disabled)::after, +.butterfly-match:has(.match-content.disabled)::before { + visibility: hidden; +} + +.butterfly-bracket { + display: flex; + gap: 40px; /* Increased to account for horizontal lines (20px on each side) */ + position: relative; + margin-bottom: 80px; +} + +.round-title { + position: absolute; + top: 0px; /* Adjust this value to position the title where you want it */ + left: 50%; /* Center horizontally */ + transform: translateX(-50%); /* Center it exactly */ + text-align: center; + font-weight: bold; + width: auto; /* Change from 100% to auto */ + padding: 5px 10px; + + white-space: nowrap; /* Prevent text from wrapping */ + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.round-name { + color: #707070; + font-size: 1.5em; + padding: 8px 12px; + white-space: nowrap; /* Prevent text wrapping */ + display: block; /* Ensure proper centering */ +} + +.round-format { + font-size: 0.9em; + color: #707070; + margin-top: -5px; /* Reduced from -10px to bring it closer */ + white-space: nowrap; /* Prevent text wrapping */ + display: block; /* Ensure proper centering */ +} + +.round-name.button { + border-radius: 16px; + width: 100%; + display: inline-block; + background-color: #fae7ce; +} + +.button:hover { + color: white; + background-color: #f39200; +} + +.matches-container { + position: relative; + width: 100%; + flex-grow: 1; +} + +.butterfly-round { + display: flex; + flex-direction: column; + align-items: center; + gap: 20px; /* Space between title and matches */ + position: relative; + width: var(--match-width); + flex-shrink: 0; + margin-top: 100px; /* Add padding to account for absolute positioned title */ +} + +.butterfly-match { + position: absolute; + width: 100%; + padding: 10px 10px; +} + +/* Horizontal line after match */ +.butterfly-match::after { + content: ""; + position: absolute; + left: 100%; /* Start from end of match cell */ + top: 50%; + width: 20px; + height: 2px; + background: #707070; +} + +.semi-final::after { + content: ""; + position: absolute; + left: calc(100% + 20px); /* After horizontal line */ + width: 2px; + height: calc((var(--next-match-distance)) / 2); + background: #707070; +} + +/* Vertical line connecting pair of matches */ +.butterfly-match:nth-child(2n)::before { + content: ""; + position: absolute; + left: calc(100% + 20px); /* After horizontal line */ + top: 50%; + width: 2px; + height: calc((var(--next-match-distance)) / 2); + background: #707070; +} + +.butterfly-match:nth-child(2n + 1)::before { + content: ""; + position: absolute; + left: calc(100% + 20px); + bottom: calc(50% - 2px); /* Account for half of horizontal line height */ + width: 2px; + height: calc( + ((var(--next-match-distance)) / 2) + ); /* Add half of horizontal line height */ + background: #707070; +} + +/* Vertical line connecting pair of matches */ +.butterfly-match.reverse-bracket:nth-child(2n)::before { + content: ""; + position: absolute; + left: calc(0% - 20px); /* After horizontal line */ + top: 50%; + width: 2px; + height: calc((var(--next-match-distance)) / 2); + background: #707070; +} + +.butterfly-match.reverse-bracket:nth-child(2n + 1)::before { + content: ""; + position: absolute; + left: calc(0% - 20px); + bottom: 50%; /* Account for half of horizontal line height */ + width: 2px; + height: calc( + (var(--next-match-distance)) / 2 + ); /* Add half of horizontal line height */ + background: #707070; +} + +/* Horizontal line to next round match */ +.butterfly-match .incoming-line { + position: absolute; + left: -20px; + top: 50%; + width: 20px; + height: 2px; + background: #707070; +} + +.inward .incoming-line { + display: none; +} + +.butterfly-match.outward::after { + display: none; +} + +.butterfly-round:last-child .butterfly-match::after, +.butterfly-round:first-child .incoming-line { + display: none; +} + +.broadcast-mode .round-name, +.broadcast-mode .round-format { + padding: 0px; + color: #707070; +} + +.broadcast-mode .round-title { + padding: 8px 20px; /* Slightly more horizontal padding */ + background-color: white; + align-content: center; + border-radius: 24px; +} +/* Broadcast mode styling for all lines */ +.broadcast-mode .butterfly-match::after, +.broadcast-mode .butterfly-match:nth-child(2n)::before, +.broadcast-mode .butterfly-match:nth-child(2n + 1)::before, +.broadcast-mode .butterfly-match.reverse-bracket:nth-child(2n)::before, +.broadcast-mode .butterfly-match.reverse-bracket:nth-child(2n + 1)::before, +.broadcast-mode .butterfly-match .incoming-line, +.broadcast-mode .semi-final::after { + background-color: black; /* Bright yellow - change to your preferred color */ +} + +.bubble.match-running { + position: relative; +} + +.bubble.match-running::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 20px; /* Height of the green indicator */ + background-color: #90ee90; /* Light green color */ + border-radius: 0 0 24px 24px; /* Match the bubble's bottom corners */ +} diff --git a/tournaments/static/tournaments/js/tournament_bracket.js b/tournaments/static/tournaments/js/tournament_bracket.js new file mode 100644 index 0000000..159c70b --- /dev/null +++ b/tournaments/static/tournaments/js/tournament_bracket.js @@ -0,0 +1,333 @@ +function renderBracket(options) { + const bracket = document.getElementById("bracket"); + const matchTemplates = document.getElementById("match-templates").children; + const rounds = []; + const matchPositions = []; + const matchDisabled = []; + const doubleButterflyMode = options.doubleButterflyMode; + const displayLoserFinal = options.displayLoserFinal; + const tournamentId = options.tournamentId; + const isBroadcast = options.isBroadcast; + // Group matches by round + Array.from(matchTemplates).forEach((template) => { + const roundIndex = parseInt(template.dataset.matchRound); + if (!rounds[roundIndex]) { + rounds[roundIndex] = []; + } + rounds[roundIndex].push(template); + }); + + // First create a test match to get natural height + const firstMatch = document.createElement("div"); + firstMatch.className = "butterfly-match"; + firstMatch.innerHTML = `
${rounds[0][0].innerHTML}
`; + bracket.appendChild(firstMatch); + const matchHeight = firstMatch.offsetHeight; + const matchSpacing = 10; + const baseDistance = matchHeight + matchSpacing; + bracket.innerHTML = ""; + const roundCount = rounds.length; + let finalRoundIndex = roundCount - 1; + if (doubleButterflyMode == true) { + finalRoundIndex = finalRoundIndex / 2; + } + let nextMatchDistance = baseDistance; + let minimumMatchDistance = 1; + + const totalRounds = document.querySelectorAll(".butterfly-round").length; + const screenWidth = window.innerWidth; + let roundTotalCount = roundCount; + if (doubleButterflyMode == true && roundCount > 1) { + roundTotalCount = roundCount - 1; + } + const padding = 50 * roundTotalCount; // Account for some padding/margin + const availableWidth = screenWidth - padding; + let responsiveMatchWidth = Math.min( + 365, + Math.floor(availableWidth / roundTotalCount), + ); + + rounds.forEach((roundMatches, roundIndex) => { + if (rounds[0].length <= 2) { + minimumMatchDistance = 2; + nextMatchDistance = baseDistance * 2; + } + const roundDiv = document.createElement("div"); + roundDiv.className = "butterfly-round"; + roundDiv.style.setProperty("--match-width", `${responsiveMatchWidth}px`); + + // Create title + const titleDiv = document.createElement("div"); + titleDiv.className = "round-title"; + + // Get the match group name and format + const firstMatchTemplate = roundMatches[0].closest(".match-template"); + const matchGroupName = firstMatchTemplate.dataset.matchGroupName; + const matchFormat = firstMatchTemplate.dataset.matchFormat; + const roundId = firstMatchTemplate.dataset.roundId; // Add this line + + let nameSpan = document.createElement("div"); + nameSpan.className = "round-name"; + nameSpan.textContent = matchGroupName; + if ( + roundIndex == finalRoundIndex || + (roundIndex == finalRoundIndex - 1 && displayLoserFinal) || + (roundIndex == finalRoundIndex + 1 && displayLoserFinal) || + isBroadcast + ) { + } else { + nameSpan = document.createElement("a"); + nameSpan.className = "round-name"; + nameSpan.classList.add("button"); + nameSpan.textContent = matchGroupName; + if (roundId) { + nameSpan.href = `/tournament/${tournamentId}/round/${roundId}/bracket/`; + nameSpan.style.cursor = "pointer"; + } + } + + const formatSpan = document.createElement("div"); + formatSpan.className = "round-format"; + formatSpan.textContent = matchFormat; + + titleDiv.appendChild(nameSpan); + titleDiv.appendChild(formatSpan); + + // Create matches container + const matchesContainer = document.createElement("div"); + matchesContainer.className = "matches-container"; + if (roundCount > 5 && doubleButterflyMode == true) { + if (roundIndex >= finalRoundIndex - 1) { + matchesContainer.style.transform = `translateX(-50%)`; + if (roundIndex >= finalRoundIndex + 2) { + matchesContainer.style.transform = `translateX(-100%)`; + } + } + } + roundDiv.appendChild(matchesContainer); + + 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"; + + matchDiv.style.position = "absolute"; + const isDisabled = matchTemplate.dataset.disabled === "true"; + matchDisabled[roundIndex][matchIndex] = isDisabled; + let isIncomingLineIsDisabled = isDisabled; + + let top; + let left; + let right; + const currentMatchesCount = roundMatches.length; + if (roundIndex > finalRoundIndex) { + matchDiv.classList.add("reverse-bracket"); + top = matchPositions[roundCount - roundIndex - 1][matchIndex]; + } + + if (roundIndex === 0) { + if (roundCount > 1) { + const nextMatchesCount = rounds[roundIndex + 1].length; + + if (currentMatchesCount == nextMatchesCount && roundCount > 2) { + nextMatchDistance = 0; + } + } else { + nextMatchDistance = 0; + } + + top = matchIndex * (matchHeight + matchSpacing) * minimumMatchDistance; + + if (roundCount == 3 && doubleButterflyMode) { + top = top + (matchHeight + matchSpacing) / 2; + } + } else if (roundIndex === roundCount - 1 && doubleButterflyMode == true) { + const nextMatchesCount = rounds[roundIndex - 1].length; + if (currentMatchesCount == nextMatchesCount) { + nextMatchDistance = 0; + } + } else if (roundIndex == finalRoundIndex) { + if (doubleButterflyMode == true) { + let lgth = matchPositions[0].length / 2; + let index = lgth + matchIndex - 1; + // If index goes negative, use 0 instead + if (displayLoserFinal == true) { + if (matchIndex == 0) { + top = matchPositions[roundIndex - 1][0] - baseDistance / 2; + } else { + top = matchPositions[roundIndex - 1][0] + baseDistance / 2; + } + nextMatchDistance = baseDistance; + } else { + top = matchPositions[roundIndex - 1][0]; + nextMatchDistance = 0; + } + } else { + const parentIndex1 = matchIndex * 2; + const parentIndex2 = parentIndex1 + 1; + const parentPos1 = matchPositions[roundIndex - 1][parentIndex1]; + 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 && + displayLoserFinal == false + ) { + nextMatchDistance = 0; + } else if (matchPositions.length > roundIndex - 1) { + nextMatchDistance = + matchPositions[roundIndex - 1][1] - + matchPositions[roundIndex - 1][0]; + nextMatchDistance = + nextMatchDistance * (previousMatchesCount / currentMatchesCount); + } + + if (currentMatchesCount == previousMatchesCount) { + if (matchDisabled[roundIndex - 1][matchIndex] == true) { + isIncomingLineIsDisabled = true; + } + top = matchPositions[roundIndex - 1][matchIndex]; + } else { + const parentIndex1 = matchIndex * 2; + const parentIndex2 = parentIndex1 + 1; + const parentPos1 = matchPositions[roundIndex - 1][parentIndex1]; + const parentPos2 = matchPositions[roundIndex - 1][parentIndex2]; + top = (parentPos1 + parentPos2) / 2; + } + } else if (roundIndex < roundCount) { + const nextMatchesCount = rounds[roundIndex - 1].length; + const previousMatchesCount = rounds[roundIndex + 1].length; + + if (currentMatchesCount == nextMatchesCount) { + nextMatchDistance = 0; + } else if (matchPositions.length > roundCount - roundIndex - 1 - 1) { + nextMatchDistance = + matchPositions[roundCount - roundIndex - 1 - 1][1] - + matchPositions[roundCount - roundIndex - 1 - 1][0]; + nextMatchDistance = + nextMatchDistance * (previousMatchesCount / currentMatchesCount); + } + } + + if (roundCount > 5 && doubleButterflyMode == true) { + if (roundIndex >= finalRoundIndex - 2) { + if (roundIndex == finalRoundIndex - 1) { + matchDiv.classList.add("inward"); + } + if (roundIndex == finalRoundIndex + 1) { + matchDiv.classList.add("outward"); + } + if ( + roundIndex === finalRoundIndex - 2 || + roundIndex === finalRoundIndex + 2 + ) { + nextMatchDistance = nextMatchDistance - baseDistance; + } + } + } + + if (displayLoserFinal == true) { + if ( + doubleButterflyMode == true && + (roundIndex == finalRoundIndex - 1 || + roundIndex == finalRoundIndex + 1) + ) { + nextMatchDistance = baseDistance; + } + } + + matchDiv.style.setProperty( + "--next-match-distance", + `${nextMatchDistance}px`, + ); + matchDiv.style.top = `${top}px`; + matchPositions[roundIndex][matchIndex] = top; + if (matchIndex === 0) { + // // Add logo for final round + // if (roundIndex == finalRoundIndex) { + // const logoDiv = document.createElement('div'); + // logoDiv.className = 'round-logo'; + // const logoImg = document.createElement('img'); + // logoImg.src = '/static/tournaments/images/PadelClub_logo_512.png'; + // logoImg.alt = 'PadelClub Logo'; + // logoDiv.appendChild(logoImg); + // logoDiv.style.transform = `translateX(-50%)`; + // matchesContainer.appendChild(logoDiv); + // } + + // Position title above the first match + titleDiv.style.top = `${top - 80}px`; // Adjust the 60px offset as needed + titleDiv.style.position = "absolute"; + if (roundCount >= 5 && doubleButterflyMode == true) { + if (roundIndex == finalRoundIndex - 1) { + titleDiv.style.marginLeft = "60px"; + } else if (roundIndex == finalRoundIndex + 1) { + titleDiv.style.marginLeft = "-60px"; + } + } + 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 = ` +
+
${matchTemplate.innerHTML}
+ `; + + if ( + roundIndex == finalRoundIndex - 1 && + displayLoserFinal == true && + doubleButterflyMode == true + ) { + const matchDiv2 = document.createElement("div"); + matchDiv2.className = "butterfly-match"; + matchDiv2.classList.add("inward"); + matchDiv2.classList.add("semi-final"); + matchDiv2.style.setProperty( + "--next-match-distance", + `${baseDistance}px`, + ); + matchDiv2.style.top = `${top}px`; + matchDiv2.innerHTML = `
${rounds[0][0].innerHTML}
`; + matchesContainer.appendChild(matchDiv2); // Append to matchesContainer instead of roundDiv + } + matchesContainer.appendChild(matchDiv); // Append to matchesContainer instead of roundDiv + }); + + bracket.appendChild(roundDiv); + }); +} diff --git a/tournaments/templates/tournaments/base.html b/tournaments/templates/tournaments/base.html index ee1dec2..a7d2b24 100644 --- a/tournaments/templates/tournaments/base.html +++ b/tournaments/templates/tournaments/base.html @@ -13,6 +13,7 @@ /> + {% block extra_css %}{% endblock %} {% for team in match.teams %}
-
+
{% if team.id %} {% endif %} diff --git a/tournaments/templates/tournaments/broadcast/broadcast_club.html b/tournaments/templates/tournaments/broadcast/broadcast_club.html index 6b3d617..8f5f87e 100644 --- a/tournaments/templates/tournaments/broadcast/broadcast_club.html +++ b/tournaments/templates/tournaments/broadcast/broadcast_club.html @@ -13,7 +13,7 @@ {% for tournament in tournaments %} -
+
{{ tournament.formatted_start_date }}
@@ -31,6 +31,7 @@
Automatic | + Programmation | Matchs | Poules | diff --git a/tournaments/templates/tournaments/broadcast/broadcasted_bracket.html b/tournaments/templates/tournaments/broadcast/broadcasted_bracket.html new file mode 100644 index 0000000..06fb3b3 --- /dev/null +++ b/tournaments/templates/tournaments/broadcast/broadcasted_bracket.html @@ -0,0 +1,160 @@ + +{% load static %} +{% load qr_code %} + + + + + + + + + + + + Tableau + + + + + + + + +
+ +
+ +
+
+
+
+
+
+
+ + + + + diff --git a/tournaments/templates/tournaments/tournament_bracket.html b/tournaments/templates/tournaments/tournament_bracket.html index b334bfd..fadaf9e 100644 --- a/tournaments/templates/tournaments/tournament_bracket.html +++ b/tournaments/templates/tournaments/tournament_bracket.html @@ -1,4 +1,9 @@ {% extends 'tournaments/base.html' %} +{% load static %} + +{% block extra_css %} + +{% endblock %} {% block head_title %}Matchs du {{ tournament.display_name }}{% endblock %} {% block first_title %}{{ tournament.event.display_name }}{% endblock %} @@ -30,479 +35,15 @@ {% endfor %}
+ - - {% endif %} {% endblock %} diff --git a/tournaments/urls.py b/tournaments/urls.py index 2bede6a..4f315c2 100644 --- a/tournaments/urls.py +++ b/tournaments/urls.py @@ -31,6 +31,8 @@ urlpatterns = [ path('broadcast/json/', views.broadcast_json, name='broadcast-json'), path('broadcast/group-stages/', views.tournament_broadcasted_group_stages, name='broadcasted-group-stages'), path('broadcast/prog/', views.tournament_broadcasted_prog, name='broadcasted-prog'), + path('broadcast/bracket/', views.tournament_broadcasted_bracket, name='broadcasted-bracket'), + path('bracket/json/', views.tournament_bracket_json, name='tournament-bracket-json'), path('group-stages/', views.tournament_group_stages, name='group-stages'), path('group-stages/json/', views.tournament_live_group_stage_json, name='group-stages-json'), path('rankings/', views.tournament_rankings, name='tournament-rankings'), diff --git a/tournaments/views.py b/tournaments/views.py index 3de6532..1a5df8a 100644 --- a/tournaments/views.py +++ b/tournaments/views.py @@ -449,6 +449,31 @@ def tournament_broadcasted_prog(request, tournament_id): 'qr_code_options': qr_code_options(), }) +def tournament_broadcasted_bracket(request, tournament_id): + tournament = get_object_or_404(Tournament, pk=tournament_id) + return render(request, 'tournaments/broadcast/broadcasted_bracket.html', { + 'tournament': tournament, + 'qr_code_url': qr_code_url(request, tournament_id), + 'qr_code_options': qr_code_options(), + }) + + +def tournament_bracket_json(request, tournament_id): + """ + JSON endpoint for tournament bracket data. + """ + tournament = get_object_or_404(Tournament, pk=tournament_id) + context = get_butterfly_bracket_view_context(tournament, parent_round=None, double_butterfly_mode=True, display_loser_final=True) + + data = { + 'match_groups': [match_group.to_dict() for match_group in context['match_groups']], + 'double_butterfly_mode': context['double_butterfly_mode'], + 'display_loser_final': context['display_loser_final'] + } + + return JsonResponse(data) + + def tournament_live_group_stage_json(request, tournament_id): tournament = get_object_or_404(Tournament, pk=tournament_id) @@ -578,8 +603,20 @@ def xls_to_csv(request): file_path = os.path.join(directory, uploaded_file.name) file_name = default_storage.save(file_path, ContentFile(uploaded_file.read())) - # convert to csv and save - data_xls = pd.read_excel(file_name, sheet_name=0, index_col=None) + # Check available sheets and look for 'inscriptions' + xls = pd.ExcelFile(file_name) + sheet_names = xls.sheet_names + + # Determine which sheet to use + target_sheet = 0 # Default to first sheet + if 'inscriptions' in [name.lower() for name in sheet_names]: + for i, name in enumerate(sheet_names): + if name.lower() == 'inscriptions': + target_sheet = i # or use the name directly: target_sheet = name + break + + # Convert to csv and save + data_xls = pd.read_excel(file_name, sheet_name=target_sheet, index_col=None) csv_file_name = create_random_filename('players', 'csv') output_path = os.path.join(directory, csv_file_name) data_xls.to_csv(output_path, sep=';', index=False, encoding='utf-8') @@ -1018,7 +1055,7 @@ def round_bracket(request, tournament_id, round_id): context = get_butterfly_bracket_view_context(tournament, round, double_butterfly_mode=False, display_loser_final=False) return render(request, 'tournaments/tournament_bracket.html', context) -def get_butterfly_bracket_view_context(tournament, parent_round=None, double_butterfly_mode=False, display_loser_final=False): +def get_butterfly_bracket_view_context(tournament, parent_round=None, double_butterfly_mode=False, display_loser_final=True): if parent_round: double_butterfly_mode = False display_loser_final = True From 67a2a3bdcef6c44a9671925eeb05d4122a794f1b Mon Sep 17 00:00:00 2001 From: Raz Date: Sat, 15 Mar 2025 15:17:05 +0100 Subject: [PATCH 04/10] fix bracket --- tournaments/models/match.py | 6 +- tournaments/models/round.py | 4 +- .../tournaments/css/tournament_bracket.css | 117 ++++---- .../tournaments/js/tournament_bracket.js | 267 ++++++++++++------ .../broadcast/broadcasted_bracket.html | 1 + .../tournaments/navigation_tournament.html | 4 +- .../tournaments/tournament_bracket.html | 1 + 7 files changed, 251 insertions(+), 149 deletions(-) diff --git a/tournaments/models/match.py b/tournaments/models/match.py index 329fab6..0414ae1 100644 --- a/tournaments/models/match.py +++ b/tournaments/models/match.py @@ -412,7 +412,7 @@ class Match(models.Model): ended = self.end_date is not None live_format = "Format " + FederalMatchCategory(self.format).format_label_short - livematch = LiveMatch(title, date, time_indication, court, self.started(), ended, group_stage_name, live_format, self.start_date, self.court_index, self.disabled) + livematch = LiveMatch(self.index, title, date, time_indication, court, self.started(), ended, group_stage_name, live_format, self.start_date, self.court_index, self.disabled) for team in self.live_teams(): livematch.add_team(team) @@ -473,7 +473,8 @@ class Team: } class LiveMatch: - def __init__(self, title, date, time_indication, court, started, ended, group_stage_name, format, start_date, court_index, disabled): + def __init__(self, index, title, date, time_indication, court, started, ended, group_stage_name, format, start_date, court_index, disabled): + self.index = index self.title = title self.date = date self.teams = [] @@ -495,6 +496,7 @@ class LiveMatch: def to_dict(self): return { + "index": self.index, "title": self.title, "date": self.date, "teams": [team.to_dict() for team in self.teams], diff --git a/tournaments/models/round.py b/tournaments/models/round.py index 0115a33..1e23535 100644 --- a/tournaments/models/round.py +++ b/tournaments/models/round.py @@ -118,7 +118,9 @@ class Round(models.Model): # 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) + if next_round_matches.filter(index=current_match.index // 2).exists(): + filtered_matches.append(current_match) + filtered_matches.append(pair_match) pass else: # Keep the current match diff --git a/tournaments/static/tournaments/css/tournament_bracket.css b/tournaments/static/tournaments/css/tournament_bracket.css index 6632ca8..f78e603 100644 --- a/tournaments/static/tournaments/css/tournament_bracket.css +++ b/tournaments/static/tournaments/css/tournament_bracket.css @@ -23,6 +23,7 @@ } .incoming-line.disabled, +.outgoing-line.disabled, .butterfly-match:has(.match-content.disabled)::after, .butterfly-match:has(.match-content.disabled)::before { visibility: hidden; @@ -104,90 +105,89 @@ } /* Horizontal line after match */ -.butterfly-match::after { +.outgoing-line { content: ""; position: absolute; left: 100%; /* Start from end of match cell */ top: 50%; width: 20px; height: 2px; - background: #707070; + background: orange; } -.semi-final::after { +.butterfly-match::before { content: ""; position: absolute; - left: calc(100% + 20px); /* After horizontal line */ + left: calc(0% - 20px); width: 2px; - height: calc((var(--next-match-distance)) / 2); - background: #707070; + top: calc(50% - (var(--next-match-distance) / 2)); + height: calc(var(--next-match-distance) + 2px); + background: pink; } /* Vertical line connecting pair of matches */ -.butterfly-match:nth-child(2n)::before { +.butterfly-match.reverse-bracket::before { content: ""; position: absolute; - left: calc(100% + 20px); /* After horizontal line */ - top: 50%; + left: calc(100% + 20px); width: 2px; - height: calc((var(--next-match-distance)) / 2); - background: #707070; + top: calc(50% - (var(--next-match-distance) / 2)); + height: calc(var(--next-match-distance) + 2px); + background: red; } -.butterfly-match:nth-child(2n + 1)::before { - content: ""; +/* Horizontal line to next round match */ +.incoming-line { position: absolute; - left: calc(100% + 20px); - bottom: calc(50% - 2px); /* Account for half of horizontal line height */ - width: 2px; - height: calc( - ((var(--next-match-distance)) / 2) - ); /* Add half of horizontal line height */ - background: #707070; + left: -20px; + top: 50%; + width: 20px; + height: 2px; + background: blue; } -/* Vertical line connecting pair of matches */ -.butterfly-match.reverse-bracket:nth-child(2n)::before { - content: ""; +/* Horizontal line to next round match */ +.butterfly-match .outgoing-line-downward { position: absolute; - left: calc(0% - 20px); /* After horizontal line */ - top: 50%; + right: -20px; + top: 50%; /* Start from middle of match */ width: 2px; - height: calc((var(--next-match-distance)) / 2); - background: #707070; + height: 120px; + background: black; } -.butterfly-match.reverse-bracket:nth-child(2n + 1)::before { - content: ""; +/* Horizontal line to next round match */ +.butterfly-match .outgoing-line-upward { position: absolute; - left: calc(0% - 20px); - bottom: 50%; /* Account for half of horizontal line height */ + right: -20px; + bottom: 50%; width: 2px; - height: calc( - (var(--next-match-distance)) / 2 - ); /* Add half of horizontal line height */ - background: #707070; + height: 120px; + background: black; } /* Horizontal line to next round match */ -.butterfly-match .incoming-line { +.butterfly-match.reverse-bracket .outgoing-line-downward { position: absolute; left: -20px; - top: 50%; - width: 20px; - height: 2px; - background: #707070; -} - -.inward .incoming-line { - display: none; + top: 50%; /* Start from middle of match */ + width: 2px; + height: 120px; + background: black; } -.butterfly-match.outward::after { - display: none; +/* Horizontal line to next round match */ +.butterfly-match.reverse-bracket .outgoing-line-upward { + position: absolute; + left: -20px; + bottom: 50%; + width: 2px; + height: 120px; + background: black; } -.butterfly-round:last-child .butterfly-match::after, +.butterfly-round:last-child .butterfly-match.reverse-bracket::before, +.butterfly-round:last-child .outgoing-line, .butterfly-round:first-child .incoming-line { display: none; } @@ -205,16 +205,25 @@ border-radius: 24px; } /* Broadcast mode styling for all lines */ -.broadcast-mode .butterfly-match::after, -.broadcast-mode .butterfly-match:nth-child(2n)::before, -.broadcast-mode .butterfly-match:nth-child(2n + 1)::before, -.broadcast-mode .butterfly-match.reverse-bracket:nth-child(2n)::before, -.broadcast-mode .butterfly-match.reverse-bracket:nth-child(2n + 1)::before, -.broadcast-mode .butterfly-match .incoming-line, -.broadcast-mode .semi-final::after { +.broadcast-mode .butterfly-match::before, +.broadcast-mode .butterfly-match.reverse-bracket::before, +.broadcast-mode .incoming-line, +.broadcast-mode .outgoing-line, +.broadcast-mode .outgoing-line-upward, +.broadcast-mode .outgoing-line-downward { background-color: black; /* Bright yellow - change to your preferred color */ } +/* Broadcast mode styling for all lines */ +.butterfly-match::before, +.butterfly-match.reverse-bracket::before, +.incoming-line, +.outgoing-line, +.outgoing-line-upward, +.outgoing-line-downward { + background-color: #707070 !important; /* Bright yellow - change to your preferred color */ +} + .bubble.match-running { position: relative; } diff --git a/tournaments/static/tournaments/js/tournament_bracket.js b/tournaments/static/tournaments/js/tournament_bracket.js index 159c70b..0c6a172 100644 --- a/tournaments/static/tournaments/js/tournament_bracket.js +++ b/tournaments/static/tournaments/js/tournament_bracket.js @@ -42,13 +42,20 @@ function renderBracket(options) { } const padding = 50 * roundTotalCount; // Account for some padding/margin const availableWidth = screenWidth - padding; - let responsiveMatchWidth = Math.min( + responsiveMatchWidth = Math.min( 365, - Math.floor(availableWidth / roundTotalCount), + Math.max(365, Math.floor(availableWidth / roundTotalCount)), ); + if (isBroadcast) { + responsiveMatchWidth = Math.min( + 365, + Math.floor(availableWidth / roundTotalCount), + ); + } + rounds.forEach((roundMatches, roundIndex) => { - if (rounds[0].length <= 2) { + if (rounds[0].length <= 2 && doubleButterflyMode) { minimumMatchDistance = 2; nextMatchDistance = baseDistance * 2; } @@ -105,29 +112,39 @@ function renderBracket(options) { } } roundDiv.appendChild(matchesContainer); - - matchPositions[roundIndex] = []; + if (matchPositions[roundIndex] == undefined) { + matchPositions[roundIndex] = {}; + } matchDisabled[roundIndex] = []; // Initialize array for this round roundMatches.forEach((matchTemplate, matchIndex) => { const matchTitle = matchTemplate.dataset.matchTitle; + const matchRealIndex = matchTemplate.dataset.matchRealIndex; const matchDiv = document.createElement("div"); matchDiv.className = "butterfly-match"; matchDiv.style.position = "absolute"; const isDisabled = matchTemplate.dataset.disabled === "true"; - matchDisabled[roundIndex][matchIndex] = isDisabled; + matchDisabled[roundIndex][matchRealIndex] = isDisabled; let isIncomingLineIsDisabled = isDisabled; - + let isOutgoingLineIsDisabled = isDisabled; let top; - let left; - let right; const currentMatchesCount = roundMatches.length; if (roundIndex > finalRoundIndex) { matchDiv.classList.add("reverse-bracket"); - top = matchPositions[roundCount - roundIndex - 1][matchIndex]; + + if (roundIndex <= finalRoundIndex + 2) { + const values = Object.values( + matchPositions[roundCount - roundIndex - 1], + ); + top = values[matchIndex]; + } else { + top = matchPositions[roundIndex][matchRealIndex]; + console.log(matchTitle, top); + } } if (roundIndex === 0) { + nextMatchDistance = 0; if (roundCount > 1) { const nextMatchesCount = rounds[roundIndex + 1].length; @@ -144,33 +161,34 @@ function renderBracket(options) { top = top + (matchHeight + matchSpacing) / 2; } } else if (roundIndex === roundCount - 1 && doubleButterflyMode == true) { - const nextMatchesCount = rounds[roundIndex - 1].length; - if (currentMatchesCount == nextMatchesCount) { - nextMatchDistance = 0; - } + nextMatchDistance = 0; } else if (roundIndex == finalRoundIndex) { + const values = Object.values(matchPositions[roundIndex - 1]); + const parentPos1 = values[0]; + const parentPos2 = values[1]; + if (doubleButterflyMode == true) { let lgth = matchPositions[0].length / 2; let index = lgth + matchIndex - 1; // If index goes negative, use 0 instead if (displayLoserFinal == true) { if (matchIndex == 0) { - top = matchPositions[roundIndex - 1][0] - baseDistance / 2; + top = parentPos1 - baseDistance / 2; } else { - top = matchPositions[roundIndex - 1][0] + baseDistance / 2; + top = parentPos1 + baseDistance / 2; } - nextMatchDistance = baseDistance; + nextMatchDistance = 0; } else { - top = matchPositions[roundIndex - 1][0]; + top = parentPos1; nextMatchDistance = 0; } } else { - const parentIndex1 = matchIndex * 2; - const parentIndex2 = parentIndex1 + 1; - const parentPos1 = matchPositions[roundIndex - 1][parentIndex1]; - const parentPos2 = matchPositions[roundIndex - 1][parentIndex2]; top = (parentPos1 + parentPos2) / 2; - nextMatchDistance = 0; + if (matchIndex == 0) { + nextMatchDistance = parentPos2 - parentPos1; + } else { + nextMatchDistance = 0; + } if (displayLoserFinal == true) { if (matchIndex == 1) { @@ -180,82 +198,115 @@ function renderBracket(options) { } } } else if (roundIndex < finalRoundIndex) { - const previousMatchesCount = rounds[roundIndex - 1].length; - const nextMatchesCount = rounds[roundIndex + 1].length; - + const parentIndex1 = matchRealIndex * 2 + 1; + const parentIndex2 = matchRealIndex * 2 + 2; + const parentPos1 = matchPositions[roundIndex - 1][parentIndex1]; + const parentPos2 = matchPositions[roundIndex - 1][parentIndex2]; + const parent1Disable = matchDisabled[roundIndex - 1][parentIndex1]; + const parent2Disable = matchDisabled[roundIndex - 1][parentIndex2]; if ( - currentMatchesCount == nextMatchesCount && - displayLoserFinal == false + (parent1Disable == undefined || parent1Disable == true) && + (parent2Disable == undefined || parent2Disable == true) ) { - nextMatchDistance = 0; - } else if (matchPositions.length > roundIndex - 1) { - nextMatchDistance = - matchPositions[roundIndex - 1][1] - - matchPositions[roundIndex - 1][0]; - nextMatchDistance = - nextMatchDistance * (previousMatchesCount / currentMatchesCount); + isIncomingLineIsDisabled = true; } - - if (currentMatchesCount == previousMatchesCount) { - if (matchDisabled[roundIndex - 1][matchIndex] == true) { - isIncomingLineIsDisabled = true; + if ( + matchPositions[roundIndex - 1][parentIndex1] != undefined && + matchPositions[roundIndex - 1][parentIndex2] != undefined + ) { + top = (parentPos1 + parentPos2) / 2; + if (parent1Disable && parent2Disable) { + nextMatchDistance = 0; + } else { + nextMatchDistance = parentPos2 - parentPos1; } - top = matchPositions[roundIndex - 1][matchIndex]; + } else if (matchPositions[roundIndex - 1][parentIndex1] != undefined) { + nextMatchDistance = 0; + top = matchPositions[roundIndex - 1][parentIndex1]; + } else if (matchPositions[roundIndex - 1][parentIndex2] != undefined) { + nextMatchDistance = 0; + top = matchPositions[roundIndex - 1][parentIndex2]; } else { - const parentIndex1 = matchIndex * 2; - const parentIndex2 = parentIndex1 + 1; - const parentPos1 = matchPositions[roundIndex - 1][parentIndex1]; - const parentPos2 = matchPositions[roundIndex - 1][parentIndex2]; - top = (parentPos1 + parentPos2) / 2; + nextMatchDistance = 0; + top = 0; } } else if (roundIndex < roundCount) { - const nextMatchesCount = rounds[roundIndex - 1].length; - const previousMatchesCount = rounds[roundIndex + 1].length; + const parentIndex1 = matchRealIndex * 2 + 1; + const parentIndex2 = matchRealIndex * 2 + 2; + const parentMatch1 = rounds[roundIndex + 1].find( + (match) => parseInt(match.dataset.matchRealIndex) === parentIndex1, + ); + const parentMatch2 = rounds[roundIndex + 1].find( + (match) => parseInt(match.dataset.matchRealIndex) === parentIndex2, + ); - if (currentMatchesCount == nextMatchesCount) { + if (matchPositions[roundIndex + 1] == undefined) { + matchPositions[roundIndex + 1] = {}; + } + if ( + parentMatch1 != undefined && + parentMatch2 != undefined && + parentMatch1.dataset.disabled == "false" && + parentMatch2.dataset.disabled == "false" + ) { + console.log( + roundIndex, + matchTitle, + parentMatch1.dataset.matchTitle, + parentMatch2.dataset.matchTitle, + parentMatch1.dataset.disabled, + parentMatch2.dataset.disabled, + top, + ); + nextMatchDistance = baseDistance; + matchPositions[roundIndex + 1][parentIndex1] = top - baseDistance / 2; + matchPositions[roundIndex + 1][parentIndex2] = top + baseDistance / 2; + console.log(matchPositions[roundIndex + 1]); + // } else if (parentMatch1 != undefined) { + // matchPositions[roundIndex + 1][parentIndex1] = top; + // nextMatchDistance = 0; + // } else if (parentMatch2 != undefined) { + // matchPositions[roundIndex + 1][parentIndex1] = top; + // nextMatchDistance = 0; + } else if ( + parentMatch2 != undefined && + parentMatch2.dataset.disabled == "false" + ) { nextMatchDistance = 0; - } else if (matchPositions.length > roundCount - roundIndex - 1 - 1) { - nextMatchDistance = - matchPositions[roundCount - roundIndex - 1 - 1][1] - - matchPositions[roundCount - roundIndex - 1 - 1][0]; - nextMatchDistance = - nextMatchDistance * (previousMatchesCount / currentMatchesCount); + matchPositions[roundIndex + 1][parentIndex2] = top; + } else if ( + parentMatch1 != undefined && + parentMatch1.dataset.disabled == "false" + ) { + nextMatchDistance = 0; + matchPositions[roundIndex + 1][parentIndex1] = top; + } else { + isOutgoingLineIsDisabled = true; } } if (roundCount > 5 && doubleButterflyMode == true) { if (roundIndex >= finalRoundIndex - 2) { if (roundIndex == finalRoundIndex - 1) { - matchDiv.classList.add("inward"); + matchDiv.classList.add("reverse-bracket"); + isIncomingLineIsDisabled = true; + nextMatchDistance = nextMatchDistance / 2; } if (roundIndex == finalRoundIndex + 1) { - matchDiv.classList.add("outward"); - } - if ( - roundIndex === finalRoundIndex - 2 || - roundIndex === finalRoundIndex + 2 - ) { - nextMatchDistance = nextMatchDistance - baseDistance; + matchDiv.classList.remove("reverse-bracket"); + isOutgoingLineIsDisabled = true; + nextMatchDistance = nextMatchDistance; } } } - if (displayLoserFinal == true) { - if ( - doubleButterflyMode == true && - (roundIndex == finalRoundIndex - 1 || - roundIndex == finalRoundIndex + 1) - ) { - nextMatchDistance = baseDistance; - } - } - matchDiv.style.setProperty( "--next-match-distance", `${nextMatchDistance}px`, ); matchDiv.style.top = `${top}px`; - matchPositions[roundIndex][matchIndex] = top; + matchPositions[roundIndex][matchRealIndex] = top; + if (matchIndex === 0) { // // Add logo for final round // if (roundIndex == finalRoundIndex) { @@ -270,7 +321,14 @@ function renderBracket(options) { // } // Position title above the first match - titleDiv.style.top = `${top - 80}px`; // Adjust the 60px offset as needed + if ( + roundIndex < finalRoundIndex - 1 || + roundIndex > finalRoundIndex + 1 + ) { + titleDiv.style.top = `${-80}px`; // Adjust the 60px offset as needed + } else { + titleDiv.style.top = `${top - 80}px`; // Adjust the 60px offset as needed + } titleDiv.style.position = "absolute"; if (roundCount >= 5 && doubleButterflyMode == true) { if (roundIndex == finalRoundIndex - 1) { @@ -285,6 +343,7 @@ function renderBracket(options) { if ( roundIndex == finalRoundIndex && matchIndex === 1 && + matchDisabled[roundIndex][matchRealIndex] == false && displayLoserFinal == true && doubleButterflyMode == false ) { @@ -306,25 +365,53 @@ function renderBracket(options) { matchDiv.innerHTML = `
${matchTemplate.innerHTML}
+
`; - if ( - roundIndex == finalRoundIndex - 1 && - displayLoserFinal == true && - doubleButterflyMode == true - ) { - const matchDiv2 = document.createElement("div"); - matchDiv2.className = "butterfly-match"; - matchDiv2.classList.add("inward"); - matchDiv2.classList.add("semi-final"); - matchDiv2.style.setProperty( - "--next-match-distance", - `${baseDistance}px`, - ); - matchDiv2.style.top = `${top}px`; - matchDiv2.innerHTML = `
${rounds[0][0].innerHTML}
`; - matchesContainer.appendChild(matchDiv2); // Append to matchesContainer instead of roundDiv + if (roundCount > 5 && doubleButterflyMode == true) { + if (roundIndex >= finalRoundIndex - 2) { + if (roundIndex === finalRoundIndex - 2) { + if (matchIndex === 0) { + const outgoingLine = document.createElement("div"); + outgoingLine.className = "outgoing-line-downward"; + matchDiv.appendChild(outgoingLine); + } else { + const outgoingLine = document.createElement("div"); + outgoingLine.className = "outgoing-line-upward"; + matchDiv.appendChild(outgoingLine); + } + } + if (roundIndex === finalRoundIndex + 2) { + if (matchIndex === 0) { + const outgoingLine = document.createElement("div"); + outgoingLine.className = "outgoing-line-downward"; + matchDiv.appendChild(outgoingLine); + } else { + const outgoingLine = document.createElement("div"); + outgoingLine.className = "outgoing-line-upward"; + matchDiv.appendChild(outgoingLine); + } + } + } } + + // if ( + // roundIndex == finalRoundIndex - 1 && + // displayLoserFinal == true && + // doubleButterflyMode == true + // ) { + // const matchDiv2 = document.createElement("div"); + // matchDiv2.className = "butterfly-match"; + // matchDiv2.classList.add("inward"); + // matchDiv2.classList.add("semi-final"); + // matchDiv2.style.setProperty( + // "--next-match-distance", + // `${baseDistance}px`, + // ); + // matchDiv2.style.top = `${top}px`; + // matchDiv2.innerHTML = `
${rounds[0][0].innerHTML}
`; + // matchesContainer.appendChild(matchDiv2); // Append to matchesContainer instead of roundDiv + // } matchesContainer.appendChild(matchDiv); // Append to matchesContainer instead of roundDiv }); diff --git a/tournaments/templates/tournaments/broadcast/broadcasted_bracket.html b/tournaments/templates/tournaments/broadcast/broadcasted_bracket.html index 06fb3b3..4c6e842 100644 --- a/tournaments/templates/tournaments/broadcast/broadcasted_bracket.html +++ b/tournaments/templates/tournaments/broadcast/broadcasted_bracket.html @@ -126,6 +126,7 @@ template.dataset.matchFormat = match.format; template.dataset.matchTitle = match.title; template.dataset.roundId = group.round_id; + template.dataset.matchRealIndex = match.index; // Create the match content using our HTML generator template.innerHTML = `
${createMatchHTML(match)}
`; diff --git a/tournaments/templates/tournaments/navigation_tournament.html b/tournaments/templates/tournaments/navigation_tournament.html index e144d9a..fd5995f 100644 --- a/tournaments/templates/tournaments/navigation_tournament.html +++ b/tournaments/templates/tournaments/navigation_tournament.html @@ -2,9 +2,9 @@ Informations - + {% endif %} {% if tournament.display_matches or tournament.display_group_stages %} Matches diff --git a/tournaments/templates/tournaments/tournament_bracket.html b/tournaments/templates/tournaments/tournament_bracket.html index fadaf9e..daf44df 100644 --- a/tournaments/templates/tournaments/tournament_bracket.html +++ b/tournaments/templates/tournaments/tournament_bracket.html @@ -26,6 +26,7 @@ data-match-group-name="{{ match_group.name }}" data-match-format="{{ match.format }}" data-match-title="{{ match.title }}" + data-match-real-index="{{ match.index }}" data-round-id="{{ match_group.round_id }}" class="match-template"> {% include 'tournaments/bracket_match_cell.html' %} From 5c3301a9a5945432c9ea8661fb7dd7ce5b348892 Mon Sep 17 00:00:00 2001 From: Raz Date: Sat, 15 Mar 2025 15:24:06 +0100 Subject: [PATCH 05/10] fix 2 lines player name --- tournaments/static/tournaments/css/style.css | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tournaments/static/tournaments/css/style.css b/tournaments/static/tournaments/css/style.css index 6e006b6..4421f9d 100644 --- a/tournaments/static/tournaments/css/style.css +++ b/tournaments/static/tournaments/css/style.css @@ -339,10 +339,13 @@ tr { /* For single player teams */ .player.single-player .semibold { - line-height: 1.4em; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; overflow: hidden; - position: relative; + line-height: 1.4em; text-overflow: ellipsis; + max-height: 2.8em; /* 2 lines × 1.4em line-height */ } /* For two player teams */ From 787f2d25d1038515ff6be43847fad18edadfe96d Mon Sep 17 00:00:00 2001 From: Raz Date: Sat, 15 Mar 2025 15:29:30 +0100 Subject: [PATCH 06/10] fix bracket --- .../tournaments/css/tournament_bracket.css | 28 +++++++++---------- .../tournaments/js/tournament_bracket.js | 4 +++ .../tournaments/broadcast/broadcast.html | 3 +- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/tournaments/static/tournaments/css/tournament_bracket.css b/tournaments/static/tournaments/css/tournament_bracket.css index f78e603..1be8f50 100644 --- a/tournaments/static/tournaments/css/tournament_bracket.css +++ b/tournaments/static/tournaments/css/tournament_bracket.css @@ -152,7 +152,7 @@ right: -20px; top: 50%; /* Start from middle of match */ width: 2px; - height: 120px; + height: var(--semi-final-distance); background: black; } @@ -162,7 +162,7 @@ right: -20px; bottom: 50%; width: 2px; - height: 120px; + height: var(--semi-final-distance); background: black; } @@ -172,7 +172,7 @@ left: -20px; top: 50%; /* Start from middle of match */ width: 2px; - height: 120px; + height: var(--semi-final-distance); background: black; } @@ -182,7 +182,7 @@ left: -20px; bottom: 50%; width: 2px; - height: 120px; + height: var(--semi-final-distance); background: black; } @@ -204,17 +204,7 @@ align-content: center; border-radius: 24px; } -/* Broadcast mode styling for all lines */ -.broadcast-mode .butterfly-match::before, -.broadcast-mode .butterfly-match.reverse-bracket::before, -.broadcast-mode .incoming-line, -.broadcast-mode .outgoing-line, -.broadcast-mode .outgoing-line-upward, -.broadcast-mode .outgoing-line-downward { - background-color: black; /* Bright yellow - change to your preferred color */ -} -/* Broadcast mode styling for all lines */ .butterfly-match::before, .butterfly-match.reverse-bracket::before, .incoming-line, @@ -224,6 +214,16 @@ background-color: #707070 !important; /* Bright yellow - change to your preferred color */ } +/* Broadcast mode styling for all lines */ +.broadcast-mode .butterfly-match::before, +.broadcast-mode .butterfly-match.reverse-bracket::before, +.broadcast-mode .incoming-line, +.broadcast-mode .outgoing-line, +.broadcast-mode .outgoing-line-upward, +.broadcast-mode .outgoing-line-downward { + background-color: black !important; /* Bright yellow - change to your preferred color */ +} + .bubble.match-running { position: relative; } diff --git a/tournaments/static/tournaments/js/tournament_bracket.js b/tournaments/static/tournaments/js/tournament_bracket.js index 0c6a172..3f610f1 100644 --- a/tournaments/static/tournaments/js/tournament_bracket.js +++ b/tournaments/static/tournaments/js/tournament_bracket.js @@ -299,6 +299,10 @@ function renderBracket(options) { } } } + matchDiv.style.setProperty( + "--semi-final-distance", + `${baseDistance / 2}px`, + ); matchDiv.style.setProperty( "--next-match-distance", diff --git a/tournaments/templates/tournaments/broadcast/broadcast.html b/tournaments/templates/tournaments/broadcast/broadcast.html index 61b7dea..e090ec9 100644 --- a/tournaments/templates/tournaments/broadcast/broadcast.html +++ b/tournaments/templates/tournaments/broadcast/broadcast.html @@ -12,11 +12,12 @@ From b651ff0bb5ff36a3a07143c0e7694660cb0a213a Mon Sep 17 00:00:00 2001 From: Raz Date: Sat, 15 Mar 2025 16:12:45 +0100 Subject: [PATCH 07/10] fix broadcast not displaying any matches --- tournaments/models/tournament.py | 8 ++++++-- tournaments/static/tournaments/css/style.css | 1 + .../templates/tournaments/broadcast/broadcast_club.html | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index 2a69060..b30a4f6 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -661,8 +661,8 @@ class Tournament(models.Model): matches.extend(first_round.get_matches_recursive(True)) else: current_round = self.round_to_show() - # print(f'current_round = {current_round.index} / parent = {current_round.parent}') if current_round: + print(f'current_round = {current_round.index} / parent = {current_round.parent}') all_upper_matches_are_over = current_round.all_matches_are_over() if all_upper_matches_are_over is False: matches.extend(current_round.get_matches_recursive(True)) @@ -689,8 +689,12 @@ class Tournament(models.Model): previous_previous_matches = [m for m in previous_previous_matches if m.end_date is None] matches.extend(previous_previous_matches) else: - # print('group_stages') + print('group_stages') group_stages = [gs.live_group_stages() for gs in self.last_group_stage_step()] + else: + first_round = self.first_round() + if first_round: + matches.extend(first_round.get_matches_recursive(True)) return matches, group_stages diff --git a/tournaments/static/tournaments/css/style.css b/tournaments/static/tournaments/css/style.css index 4421f9d..efd1af6 100644 --- a/tournaments/static/tournaments/css/style.css +++ b/tournaments/static/tournaments/css/style.css @@ -664,6 +664,7 @@ h-margin { /* Summons */ .table-row-3-colums-summons { + grid-template-columns: 35% 1fr 25%; display: flex; align-items: center; } diff --git a/tournaments/templates/tournaments/broadcast/broadcast_club.html b/tournaments/templates/tournaments/broadcast/broadcast_club.html index 8f5f87e..77196a4 100644 --- a/tournaments/templates/tournaments/broadcast/broadcast_club.html +++ b/tournaments/templates/tournaments/broadcast/broadcast_club.html @@ -31,8 +31,8 @@
Automatic | - - Programmation | + (beta) Tableau | + (beta) Programmation | Matchs | Poules | Convocations | From 1dd69b95195e47b38043e099f63043c800b2b5e2 Mon Sep 17 00:00:00 2001 From: Raz Date: Sat, 15 Mar 2025 16:24:19 +0100 Subject: [PATCH 08/10] fix broadcast not displaying any matches --- tournaments/static/tournaments/css/style.css | 4 ++-- .../tournaments/broadcast/broadcasted_summon.html | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/tournaments/static/tournaments/css/style.css b/tournaments/static/tournaments/css/style.css index efd1af6..8498c51 100644 --- a/tournaments/static/tournaments/css/style.css +++ b/tournaments/static/tournaments/css/style.css @@ -664,9 +664,9 @@ h-margin { /* Summons */ .table-row-3-colums-summons { - grid-template-columns: 35% 1fr 25%; - display: flex; + grid-template-columns: 50% 25% 25%; align-items: center; + display: grid; } .summons-left, diff --git a/tournaments/templates/tournaments/broadcast/broadcasted_summon.html b/tournaments/templates/tournaments/broadcast/broadcasted_summon.html index f3272fa..de2d2d8 100644 --- a/tournaments/templates/tournaments/broadcast/broadcasted_summon.html +++ b/tournaments/templates/tournaments/broadcast/broadcasted_summon.html @@ -9,13 +9,11 @@
-
-
-
-
-
-
+
+
+
+
From 9d75185c9dc0cf338526a5dc3326b52c7c2bcb45 Mon Sep 17 00:00:00 2001 From: Laurent Date: Mon, 17 Mar 2025 08:56:56 +0100 Subject: [PATCH 09/10] update prices --- tournaments/templates/tournaments/download.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tournaments/templates/tournaments/download.html b/tournaments/templates/tournaments/download.html index 9ab5d6a..b2e01c6 100644 --- a/tournaments/templates/tournaments/download.html +++ b/tournaments/templates/tournaments/download.html @@ -264,21 +264,21 @@
À L'UNITÉ
-
15€
+
15€ HT
par tournoi
ABONNEMENT MENSUEL
-
50€
+
50€ HT
jusqu'à 5 tournois
ABONNEMENT MENSUEL
-
95€
+
99,99€ HT
illimité
From b6624ca2ca3ef30f4acc55db5de209010c4cad79 Mon Sep 17 00:00:00 2001 From: Raz Date: Mon, 17 Mar 2025 13:48:59 +0100 Subject: [PATCH 10/10] fix signal and success page --- tournaments/repositories.py | 6 +- tournaments/services/email_service.py | 92 ++++++++----------- .../services/tournament_registration.py | 22 +++++ tournaments/signals.py | 24 +---- .../registration/activation_failed.html | 23 +++++ .../registration/activation_success.html | 18 ++++ .../tournaments/tournament_info.html | 2 + tournaments/urls.py | 2 + tournaments/views.py | 12 ++- 9 files changed, 119 insertions(+), 82 deletions(-) create mode 100644 tournaments/templates/registration/activation_failed.html create mode 100644 tournaments/templates/registration/activation_success.html diff --git a/tournaments/repositories.py b/tournaments/repositories.py index cd394bd..634a835 100644 --- a/tournaments/repositories.py +++ b/tournaments/repositories.py @@ -53,12 +53,10 @@ class TournamentRegistrationRepository: rank=rank, computed_rank=computed_rank, licence_id=player_data['licence_id'], + email=player_data.get('email'), + phone_number=player_data.get('mobile_number'), ) - if is_captain is True: - player_registration.email=team_form_data['email'] - player_registration.phone_number=team_form_data['mobile_number'] - player_registration.save() team_registration.set_weight() diff --git a/tournaments/services/email_service.py b/tournaments/services/email_service.py index 8349693..8495d9c 100644 --- a/tournaments/services/email_service.py +++ b/tournaments/services/email_service.py @@ -4,6 +4,8 @@ from django.urls import reverse from enum import Enum class TeamEmailType(Enum): + REGISTERED = "registered" + WAITING_LIST = "waiting_list" UNREGISTERED = "unregistered" OUT_OF_WAITING_LIST = "out_of_waiting_list" TOURNAMENT_CANCELED = "tournament_canceled" @@ -16,6 +18,8 @@ class TeamEmailType(Enum): def email_subject(self) -> str: subjects = { + self.REGISTERED: "Participation confirmée", + self.WAITING_LIST: "Liste d'attente", self.UNREGISTERED: "Désistement", self.OUT_OF_WAITING_LIST: "Participation confirmée", self.TOURNAMENT_CANCELED: "Tournoi annulé", @@ -47,52 +51,37 @@ class TournamentEmailService: @staticmethod def send_registration_confirmation(request, tournament, team_registration, waiting_list_position): - tournament_details_str = tournament.build_tournament_details_str() - - email_subject = TournamentEmailService._build_email_subject( - tournament, - tournament_details_str, - waiting_list_position - ) - - email_body = TournamentEmailService._build_email_body( - request, - tournament, - team_registration, - tournament_details_str, - waiting_list_position - ) - TournamentEmailService._send_email(request.user.email, email_subject, email_body) - - @staticmethod - def _build_email_subject(tournament, tournament_details_str, waiting_list_position): if waiting_list_position >= 0: - base_subject = "Liste d'attente" + TournamentEmailService.notify_team(team_registration, tournament, TeamEmailType.WAITING_LIST) else: - base_subject = "Participation confirmée" - return TournamentEmailService.email_subject(tournament, base_subject) + TournamentEmailService.notify_team(team_registration, tournament, TeamEmailType.REGISTERED) @staticmethod - def _build_email_body(request, tournament, team_registration, tournament_details_str, waiting_list_position): - inscription_date = team_registration.local_registration_date().strftime("%d/%m/%Y à %H:%M") - team_members = [player.name() for player in team_registration.playerregistration_set.all()] - team_members_str = " et ".join(team_members) + def _build_registration_confirmation_email_body(tournament, captain, tournament_details_str, other_player): + return TournamentEmailService._build_registration_email_body(tournament, captain, tournament_details_str, other_player, False) + @staticmethod + def _build_waiting_list_confirmation_email_body(tournament, captain, tournament_details_str, other_player): + return TournamentEmailService._build_registration_email_body(tournament, captain, tournament_details_str, other_player, True) + + @staticmethod + def _build_registration_email_body(tournament, captain, tournament_details_str, other_player, waiting_list): + inscription_date = captain.team_registration.local_registration_date().strftime("%d/%m/%Y à %H:%M") body_parts = [] body_parts.append("Bonjour,\n") - if waiting_list_position >= 0: + if waiting_list: 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.") - absolute_url = f"{request.build_absolute_uri(f'/tournament/{tournament.id}/')}" + absolute_url = f"https://padelclub.app/tournament/{tournament.id}/info" link_text = "informations sur le tournoi" absolute_url = f'{link_text}' body_parts.extend([ f"\nDate d'inscription: {inscription_date}", - f"\nÉquipe inscrite: {team_members_str}", + f"\nÉquipe inscrite: {captain.name()} et {other_player.name()}", f"\nLe tournoi commencera le {tournament.formatted_start_date()} au club {tournament.event.club.name}", f"\nVoir les {absolute_url}", "\nPour toute question, veuillez contacter votre juge-arbitre. Si vous n'êtes pas à l'origine de cette inscription, merci de le contacter rapidement.", @@ -343,6 +332,7 @@ class TournamentEmailService: @staticmethod def notify(captain, other_player, tournament, message_type: TeamEmailType): + print("TournamentEmailService.notify", captain.email, captain.registered_online, tournament, message_type) if not captain or not captain.registered_online or not captain.email: return @@ -360,8 +350,16 @@ class TournamentEmailService: TournamentEmailService._send_email(captain.email, email_subject, email_body) @staticmethod - def _build_email_content(message_type, recipient, tournament, tournament_details_str, other_player): - if message_type == TeamEmailType.OUT_OF_WAITING_LIST: + def _build_email_content(message_type, recipient, tournament, tournament_details_str, other_player, request=None, waiting_list_position=None): + if message_type == TeamEmailType.REGISTERED: + body = TournamentEmailService._build_registration_confirmation_email_body( + tournament, recipient, tournament_details_str, other_player + ) + elif message_type == TeamEmailType.WAITING_LIST: + body = TournamentEmailService._build_waiting_list_confirmation_email_body( + tournament, recipient, tournament_details_str, other_player + ) + elif message_type == TeamEmailType.OUT_OF_WAITING_LIST: body = TournamentEmailService._build_out_of_waiting_list_email_body( tournament, recipient, tournament_details_str, other_player ) @@ -415,24 +413,14 @@ class TournamentEmailService: @staticmethod def notify_team(team, tournament, message_type: TeamEmailType): - captain = None - other_player = None - - for player in team.playerregistration_set.all(): - if player.captain: - captain = player - else: - other_player = player - - if captain: - TournamentEmailService.notify(captain, other_player, tournament, message_type) - else: - # Notify both players separately if there is no captain or the captain is unavailable - players = list(team.playerregistration_set.all()) - if len(players) == 2: - first_player, second_player = players - TournamentEmailService.notify(first_player, second_player, tournament, message_type) - TournamentEmailService.notify(second_player, first_player, tournament, message_type) - elif len(players) == 1: - # If there's only one player, just send them the notification - TournamentEmailService.notify(players[0], None, tournament, message_type) + # Notify both players separately if there is no captain or the captain is unavailable + players = list(team.playerregistration_set.all()) + if len(players) == 2: + print("TournamentEmailService.notify_team 2p", team) + first_player, second_player = players + TournamentEmailService.notify(first_player, second_player, tournament, message_type) + TournamentEmailService.notify(second_player, first_player, tournament, message_type) + elif len(players) == 1: + print("TournamentEmailService.notify_team 1p", team) + # If there's only one player, just send them the notification + TournamentEmailService.notify(players[0], None, tournament, message_type) diff --git a/tournaments/services/tournament_registration.py b/tournaments/services/tournament_registration.py index 8f033f0..6d01eb9 100644 --- a/tournaments/services/tournament_registration.py +++ b/tournaments/services/tournament_registration.py @@ -290,6 +290,28 @@ class TournamentRegistrationService: 'phone': None, }) + from django.contrib.auth import get_user_model + User = get_user_model() + + # Get the license ID from player_data + licence_id = player_data.get('licence_id') + validator = LicenseValidator(licence_id) + if validator and validator.stripped_license: + try: + # Try to find a user with matching license + user_with_same_license = User.objects.get(licence_id__icontains=validator.stripped_license) + + # If found, update the email and phone + if user_with_same_license: + player_data.update({ + 'email': user_with_same_license.email, + 'phone': user_with_same_license.phone + }) + print(f"Found user with license {licence_id}, updated email and phone") + except User.DoesNotExist: + # No user found with this license, continue with None email and phone + pass + def _handle_first_tournament_case(self, data): print("_handle_first_tournament_case", data) if data: diff --git a/tournaments/signals.py b/tournaments/signals.py index 735f1ec..7da0831 100644 --- a/tournaments/signals.py +++ b/tournaments/signals.py @@ -60,29 +60,7 @@ def notify_team(team, tournament, message_type): if tournament.supposedly_in_progress(): return - captain = None - other_player = None - - for player in team.playerregistration_set.all(): - if player.captain: - captain = player - else: - other_player = player - - if captain: - TournamentEmailService.notify(captain, other_player, tournament, message_type) - if not captain.registered_online or not captain.email: - TournamentEmailService.notify(other_player, captain, tournament, message_type) - else: - # Notify both players separately if there is no captain or the captain is unavailable - players = list(team.playerregistration_set.all()) - if len(players) == 2: - first_player, second_player = players - TournamentEmailService.notify(first_player, second_player, tournament, message_type) - TournamentEmailService.notify(second_player, first_player, tournament, message_type) - elif len(players) == 1: - # If there's only one player, just send them the notification - TournamentEmailService.notify(players[0], None, tournament, message_type) + TournamentEmailService.notify_team(team, tournament, message_type) @receiver(pre_delete, sender=TeamRegistration) def unregister_team(sender, instance, **kwargs): diff --git a/tournaments/templates/registration/activation_failed.html b/tournaments/templates/registration/activation_failed.html new file mode 100644 index 0000000..dfa42e6 --- /dev/null +++ b/tournaments/templates/registration/activation_failed.html @@ -0,0 +1,23 @@ +{% extends 'tournaments/base.html' %} +{% block head_title %} Activation {% endblock %} +{% block first_title %} Padel Club {% endblock %} +{% block second_title %} Activation {% endblock %} + +{% block content %} +{% load static %} +{% load tz %} + +
+
+
+ +

Le lien d'activation est invalide ou a expiré.

+ + +
+
+{% endblock %} diff --git a/tournaments/templates/registration/activation_success.html b/tournaments/templates/registration/activation_success.html new file mode 100644 index 0000000..6e17e01 --- /dev/null +++ b/tournaments/templates/registration/activation_success.html @@ -0,0 +1,18 @@ +{% extends 'tournaments/base.html' %} +{% block head_title %} Activation {% endblock %} +{% block first_title %} Padel Club {% endblock %} +{% block second_title %} Activation {% endblock %} + +{% block content %} +{% load static %} +{% load tz %} + +
+
+
+ +

Votre compte a été activé et vous êtes maintenant connecté.

+ Aller à la page d'accueil +
+
+{% endblock %} diff --git a/tournaments/templates/tournaments/tournament_info.html b/tournaments/templates/tournaments/tournament_info.html index f54c34b..0e655fd 100644 --- a/tournaments/templates/tournaments/tournament_info.html +++ b/tournaments/templates/tournaments/tournament_info.html @@ -169,6 +169,8 @@

Vous avez besoin d'un compte Padel Club pour pouvoir vous inscrire en ligne. +
+

diff --git a/tournaments/urls.py b/tournaments/urls.py index 4f315c2..b2255d9 100644 --- a/tournaments/urls.py +++ b/tournaments/urls.py @@ -73,5 +73,7 @@ urlpatterns = [ path('admin/tournament-import/', views.tournament_import_view, name='tournament_import'), path('admin/tournament-import-tr/', views.tournament_import_team_reg, name='tournament_import'), path('admin/users-export/', views.UserListExportView.as_view(), name='users_export'), + path('activation-success/', views.activation_success, name='activation_success'), + path('activation-failed/', views.activation_failed, name='activation_failed'), ] diff --git a/tournaments/views.py b/tournaments/views.py index 1a5df8a..081fa02 100644 --- a/tournaments/views.py +++ b/tournaments/views.py @@ -525,10 +525,16 @@ def activate(request, uidb64, token): from django.contrib.auth import login login(request, user, backend='django.contrib.auth.backends.ModelBackend') - next_url = request.GET.get('next', '/') - return redirect(next_url) + return redirect('activation_success') else: - return HttpResponse('Le lien est invalide.') + # Redirect to a custom error page + return redirect('activation_failed') # Define this URL in your urls.py + +def activation_success(request): + return render(request, 'registration/activation_success.html') + +def activation_failed(request): + return render(request, 'registration/activation_failed.html') def club_broadcast(request, broadcast_code): club = get_object_or_404(Club, broadcast_code=broadcast_code)