From c6f5571d43e1572d40a98ace8fcdcf106f5cda45 Mon Sep 17 00:00:00 2001 From: Raz Date: Sat, 10 May 2025 09:37:40 +0200 Subject: [PATCH] add planning event --- tournaments/models/match.py | 18 +- tournaments/models/tournament.py | 75 ++++- tournaments/static/tournaments/css/style.css | 2 +- .../tournaments/broadcast/broadcast.html | 1 + .../broadcast/broadcasted_planning.html | 301 ++++++++++++++++++ tournaments/urls.py | 2 + tournaments/views.py | 41 +++ 7 files changed, 419 insertions(+), 21 deletions(-) create mode 100644 tournaments/templates/tournaments/broadcast/broadcasted_planning.html diff --git a/tournaments/models/match.py b/tournaments/models/match.py index 00650e6..4610e1a 100644 --- a/tournaments/models/match.py +++ b/tournaments/models/match.py @@ -420,7 +420,13 @@ class Match(SideStoreModel): # _minutes = int((_seconds % 3600) / 60) # return f"{_hours:02d}h{_minutes:02d}min" - def live_match(self, hide_teams=False): + def tournament_title(self): + if self.group_stage: + return self.group_stage.tournament.full_name() + else: + return self.round.tournament.full_name() + + def live_match(self, hide_teams=False, event_mode=False): title = self.computed_name() date = self.formatted_start_date() time_indication = self.time_indication() @@ -435,7 +441,11 @@ class Match(SideStoreModel): ended = self.end_date is not None live_format = "Format " + FederalMatchCategory(self.format).format_label_short - 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, bracket_name, self.should_show_lucky_loser_status()) + tournament_title = None + if event_mode is True: + tournament_title = self.tournament_title() + + 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, bracket_name, self.should_show_lucky_loser_status(), tournament_title) for team in self.live_teams(hide_teams): livematch.add_team(team) @@ -502,7 +512,7 @@ class Team: } class LiveMatch: - def __init__(self, index, title, date, time_indication, court, started, ended, group_stage_name, format, start_date, court_index, disabled, bracket_name, should_show_lucky_loser_status): + def __init__(self, index, title, date, time_indication, court, started, ended, group_stage_name, format, start_date, court_index, disabled, bracket_name, should_show_lucky_loser_status, tournament_title): self.index = index self.title = title self.date = date @@ -519,6 +529,7 @@ class LiveMatch: self.court_index = court_index self.bracket_name = bracket_name self.should_show_lucky_loser_status = should_show_lucky_loser_status + self.tournament_title = tournament_title def add_team(self, team): self.teams.append(team) @@ -542,6 +553,7 @@ class LiveMatch: "court_index": self.court_index, "bracket_name": self.bracket_name, "should_show_lucky_loser_status": self.should_show_lucky_loser_status, + "tournament_title": self.tournament_title, } def show_time_indication(self): diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index 642f622..fc3ff50 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -564,9 +564,9 @@ class Tournament(BaseModel): return groups - def create_match_group(self, name, matches, round_id=None, round_index=None, hide_teams=False): + def create_match_group(self, name, matches, round_id=None, round_index=None, hide_teams=False, event_mode=False): matches = list(matches) - live_matches = [match.live_match(hide_teams) for match in matches] + live_matches = [match.live_match(hide_teams, event_mode) for match in matches] # Filter out matches that have a start_date of None valid_matches = [match for match in matches if match.start_date is not None] @@ -1906,7 +1906,7 @@ class Tournament(BaseModel): return teams_processed - def planned_matches_by_day(self, day=None): + def planned_matches_by_day(self, day=None, all=False, event_mode=False): """ Collect all matches from tournaments and group them by their planned_start_date. @@ -1917,20 +1917,29 @@ class Tournament(BaseModel): - days: List of unique days found (datetime.date objects) - match_groups: Dictionary of match groups by date and hour or just for the selected day """ + + if event_mode is True and self.event.tournaments.count() == 1: + event_mode = False + # Get all matches from rounds and group stages - use a set to avoid duplicates all_matches = set() - # Get matches only from top-level rounds to avoid duplicates - for round in self.rounds.filter(parent=None).all(): - round_matches = round.get_matches_recursive(False) - # Add to set using IDs to avoid duplicates - for match in round_matches: - all_matches.add(match) + tournaments = [self] + if event_mode is True: + tournaments = self.event.tournaments.all() - # Get matches from group stages - for group_stage in self.group_stages.all(): - for match in group_stage.matches.all(): - all_matches.add(match) + for tournament in tournaments: + # Get matches only from top-level rounds to avoid duplicates + for round in tournament.rounds.filter(parent=None).all(): + round_matches = round.get_matches_recursive(False) + # Add to set using IDs to avoid duplicates + for match in round_matches: + all_matches.add(match) + + # Get matches from group stages + for group_stage in tournament.group_stages.all(): + for match in group_stage.matches.all(): + all_matches.add(match) # Filter matches with planned_start_date - convert back to list planned_matches = [match for match in all_matches if match.planned_start_date and not match.disabled] @@ -1955,6 +1964,40 @@ class Tournament(BaseModel): # Sort days sorted_days = sorted(list(days)) + # Create match groups for the selected day + match_groups = [] + + if all: + for selected_day in sorted_days: + # Group matches by hour + matches_by_hour = {} + for match in matches_by_day[selected_day]: + local_time = timezone.localtime(match.planned_start_date) + hour_key = local_time.strftime('%H:%M') + + if hour_key not in matches_by_hour: + matches_by_hour[hour_key] = [] + + matches_by_hour[hour_key].append(match) + + hide_teams = self.show_teams_in_prog == False + # Create match groups for each hour + for hour, matches in sorted(matches_by_hour.items()): + # Sort matches by court if available + matches.sort(key=lambda m: (m.court_index if m.court_index is not None else 999)) + + local_date = matches[0].local_start_date() + formatted_name = formats.date_format(local_date, format='l j F à H:i').capitalize() + mg = self.create_match_group( + name=formatted_name, + matches=matches, + round_id=None, + round_index=None, + hide_teams=hide_teams, + event_mode=event_mode + ) + match_groups.append(mg) + return sorted_days, match_groups # If specific day requested, filter to that day selected_day = None @@ -1969,9 +2012,6 @@ class Tournament(BaseModel): else: selected_day = sorted_days[0] if sorted_days else None - # Create match groups for the selected day - match_groups = [] - if selected_day and selected_day in matches_by_day: # Group matches by hour matches_by_hour = {} @@ -1997,7 +2037,8 @@ class Tournament(BaseModel): matches=matches, round_id=None, round_index=None, - hide_teams=hide_teams + hide_teams=hide_teams, + event_mode=event_mode ) match_groups.append(mg) diff --git a/tournaments/static/tournaments/css/style.css b/tournaments/static/tournaments/css/style.css index fed7a51..2d72aab 100644 --- a/tournaments/static/tournaments/css/style.css +++ b/tournaments/static/tournaments/css/style.css @@ -930,7 +930,7 @@ h-margin { display: flex; justify-content: space-between; align-items: center; - padding: 20px 40px; + padding: 30px 30px; } .left-content { diff --git a/tournaments/templates/tournaments/broadcast/broadcast.html b/tournaments/templates/tournaments/broadcast/broadcast.html index 9096c02..a0f6a2b 100644 --- a/tournaments/templates/tournaments/broadcast/broadcast.html +++ b/tournaments/templates/tournaments/broadcast/broadcast.html @@ -30,6 +30,7 @@
Convocations
Classement
(beta) Programmation
+
(beta) Planning
(beta) Tableau
diff --git a/tournaments/templates/tournaments/broadcast/broadcasted_planning.html b/tournaments/templates/tournaments/broadcast/broadcasted_planning.html new file mode 100644 index 0000000..bb48bbb --- /dev/null +++ b/tournaments/templates/tournaments/broadcast/broadcasted_planning.html @@ -0,0 +1,301 @@ + +{% load static %} +{% load qr_code %} + + + + + + + + + + + + Programmation + + + + + + + + + +
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+ + +
+ + +
+
+ + + + diff --git a/tournaments/urls.py b/tournaments/urls.py index a62fa34..417da4b 100644 --- a/tournaments/urls.py +++ b/tournaments/urls.py @@ -32,9 +32,11 @@ urlpatterns = [ path('broadcast/auto/', views.automatic_broadcast, name='automatic-broadcast'), path('matches/json/', views.tournament_matches_json, name='tournament-matches-json'), path('prog/json/', views.tournament_prog_json, name='tournament-prog-json'), + path('planning/json/', views.tournament_planning_json, name='tournament-planning-json'), 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/planning/', views.tournament_broadcasted_planning, name='broadcasted-planning'), 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'), diff --git a/tournaments/views.py b/tournaments/views.py index 4c9830f..c120783 100644 --- a/tournaments/views.py +++ b/tournaments/views.py @@ -31,6 +31,7 @@ import pandas as pd from tournaments.utils.extensions import create_random_filename from django.core.files.storage import default_storage from django.core.files.base import ContentFile +from django.utils import formats from api.tokens import account_activation_token @@ -386,6 +387,37 @@ def tournament_prog_json(request, tournament_id): data = json.dumps(live_matches, default=vars) return HttpResponse(data, content_type='application/json') +def tournament_planning_json(request, tournament_id): + tournament = get_object_or_404(Tournament, id=tournament_id) + day_param = request.GET.get('day', None) + + # Get days and match groups using the planned_matches_by_day method + days, match_groups = tournament.planned_matches_by_day(day=day_param, all=True, event_mode=True) + + # Format data for JSON response + formatted_days = [formats.date_format(day, format='l j F').capitalize() for day in days] + + # Convert match groups to JSON-serializable format + match_groups_data = [] + for match_group in match_groups: + if not hasattr(match_group, 'matches') or not match_group.matches: + continue + live_matches = [] + for match in match_group.matches: + live_matches.append(match.to_dict()) + + match_groups_data.append({ + 'name': match_group.name, + 'matches': live_matches + }) + + response_data = { + 'days': formatted_days, + 'match_groups': match_groups_data, + } + + return JsonResponse(response_data, safe=False) + def tournament_group_stages(request, tournament_id): tournament = get_object_or_404(Tournament, pk=tournament_id) live_group_stages = list(tournament.live_group_stages()) @@ -421,6 +453,15 @@ def tournament_broadcasted_bracket(request, tournament_id): 'qr_code_options': qr_code_options(), }) +def tournament_broadcasted_planning(request, tournament_id): + tournament = get_object_or_404(Tournament, pk=tournament_id) + + return render(request, 'tournaments/broadcast/broadcasted_planning.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): """