diff --git a/tournaments/models/match.py b/tournaments/models/match.py index 08a4b42..856f13d 100644 --- a/tournaments/models/match.py +++ b/tournaments/models/match.py @@ -423,6 +423,37 @@ class Match(TournamentSubModel): return timezone.now() > self.start_date return False + def will_start(self): + """ + Returns True if match will start within the next hour + """ + if self.disabled or self.end_date: + return False + + # Check start_date first + if self.start_date: + now = timezone.now() + time_until_start = (self.start_date - now).total_seconds() + return 0 < time_until_start <= 3600 # Within 1 hour (3600 seconds) + + # Check planned_start_date as fallback + if self.planned_start_date: + now = timezone.now() + time_until_start = (self.planned_start_date - now).total_seconds() + return 0 < time_until_start <= 3600 # Within 1 hour (3600 seconds) + + return False + + def is_ongoing(self): + """ + Returns True if match has started but not finished + """ + if self.disabled: + return False + + # Match is ongoing if it has started but hasn't ended + return self.started() and self.end_date is None + def should_appear(self): if self.disabled is True: return False diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index a9a0566..f94885f 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -1031,6 +1031,72 @@ class Tournament(BaseModel): def will_start_soon(self, hour_delta=2): return self.has_started(hour_delta=hour_delta) + def has_ongoing_matches(self): + """ + Returns True if tournament has any matches that are currently ongoing + """ + # Check matches in rounds + for round_obj in self.rounds.all(): + for match in round_obj.matches.all(): + if match.is_ongoing(): + return True + + # Check matches in group stages + for group_stage in self.group_stages.all(): + for match in group_stage.matches.all(): + if match.is_ongoing(): + return True + + return False + + def has_matches_starting_soon(self): + """ + Returns True if tournament has any matches starting within the next hour + """ + # Check matches in rounds + for round_obj in self.rounds.all(): + for match in round_obj.matches.all(): + if match.will_start(): + return True + + # Check matches in group stages + for group_stage in self.group_stages.all(): + for match in group_stage.matches.all(): + if match.will_start(): + return True + + return False + + def get_next_match_start_time(self): + """ + Returns the datetime of the earliest upcoming match, or None if no upcoming matches + """ + next_times = [] + + # Check matches in rounds + for round_obj in self.rounds.all(): + for match in round_obj.matches.all(): + if match.disabled or match.end_date: + continue + + if match.start_date and match.start_date > timezone.now(): + next_times.append(match.start_date) + elif match.planned_start_date and match.planned_start_date > timezone.now(): + next_times.append(match.planned_start_date) + + # Check matches in group stages + for group_stage in self.group_stages.all(): + for match in group_stage.matches.all(): + if match.disabled or match.end_date: + continue + + if match.start_date and match.start_date > timezone.now(): + next_times.append(match.start_date) + elif match.planned_start_date and match.planned_start_date > timezone.now(): + next_times.append(match.planned_start_date) + + return min(next_times) if next_times else None + def are_teams_positioned(self): teams = self.team_registrations.all() filtered_teams = [t for t in teams if t.is_positioned()] diff --git a/tournaments/templates/tournaments/broadcast/broadcasted_auto_event.html b/tournaments/templates/tournaments/broadcast/broadcasted_auto_event.html deleted file mode 100644 index cf8a9f7..0000000 --- a/tournaments/templates/tournaments/broadcast/broadcasted_auto_event.html +++ /dev/null @@ -1,263 +0,0 @@ - - - {% load static %} - {% load qr_code %} - - - {% include 'tournaments/broadcast/base_head.html' %} - - - - Broadcast - - - - - - - - -
-
- -
- - -
-
-
- - - - - - - - -
- -
-
- -
- - diff --git a/tournaments/templates/tournaments/broadcast/broadcasted_event_auto.html b/tournaments/templates/tournaments/broadcast/broadcasted_event_auto.html index b328665..308e6c7 100644 --- a/tournaments/templates/tournaments/broadcast/broadcasted_event_auto.html +++ b/tournaments/templates/tournaments/broadcast/broadcasted_event_auto.html @@ -22,46 +22,112 @@ display: block; background-color: #000; } + + .error-message { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: white; + font-size: 18px; + text-align: center; + z-index: 1000; + display: none; + } +
+ Rechargement du tournoi... +
diff --git a/tournaments/views.py b/tournaments/views.py index d9f7475..c0b58fe 100644 --- a/tournaments/views.py +++ b/tournaments/views.py @@ -532,12 +532,26 @@ def automatic_broadcast(request, tournament_id): def automatic_broadcast_event(request, event_id): event = get_object_or_404(Event, pk=event_id) - tournaments = Tournament.objects.filter(event=event).order_by('start_date') + now = timezone.now() - # Filter tournaments that have broadcast content - tournaments_with_content = [] - for tournament in tournaments: + # Get all tournaments for the event (expanded time range for better coverage) + past_cutoff = now - timedelta(hours=72) # Extended past search + future_cutoff = now + timedelta(hours=72) # Extended future search + + all_tournaments = Tournament.objects.filter( + event=event, + start_date__gte=past_cutoff, + start_date__lte=future_cutoff + ).order_by('start_date') + + # Categorize tournaments by priority + ongoing_tournaments = [] + starting_soon_tournaments = [] + other_tournaments_with_content = [] + + for tournament in all_tournaments: try: + # First check if tournament has broadcast content content = tournament.broadcast_content() has_content = ( len(content.get('matches', [])) > 0 or @@ -545,13 +559,93 @@ def automatic_broadcast_event(request, event_id): len(content.get('summons', [])) > 0 or len(content.get('rankings', [])) > 0 ) + if has_content: - tournaments_with_content.append(tournament) - except: - # Skip tournaments that error when getting content + # Categorize based on match activity (highest priority first) + if tournament.has_ongoing_matches(): + ongoing_tournaments.append(tournament) + elif tournament.has_matches_starting_soon(): + starting_soon_tournaments.append(tournament) + else: + other_tournaments_with_content.append(tournament) + except Exception as e: + print(f"Error processing tournament {tournament.id}: {e}") continue - tournament_ids = [str(tournament.id) for tournament in tournaments_with_content] + # Sort each category by next match start time and tournament start time + def sort_by_next_match_time(tournament): + next_match_time = tournament.get_next_match_start_time() + return ( + next_match_time if next_match_time else tournament.start_date, + tournament.start_date + ) + + # Sort ongoing tournaments by next match time (for consistency) + ongoing_tournaments.sort(key=sort_by_next_match_time) + + # Sort starting soon tournaments by next match time (earliest first) + starting_soon_tournaments.sort(key=sort_by_next_match_time) + + # Sort other tournaments by tournament start time + other_tournaments_with_content.sort(key=lambda t: t.start_date) + + # Build final list prioritizing ongoing matches above all else + tournaments_to_display = [] + + # Priority 1: Tournaments with ongoing matches (take max 2) + tournaments_to_display.extend(ongoing_tournaments[:2]) + + # If we need more tournaments and have space + if len(tournaments_to_display) < 2: + remaining_slots = 2 - len(tournaments_to_display) + + # Priority 2: Tournaments with matches starting soon + tournaments_to_display.extend(starting_soon_tournaments[:remaining_slots]) + + # If still need more tournaments + if len(tournaments_to_display) < 2: + remaining_slots = 2 - len(tournaments_to_display) + + # Priority 3: Other tournaments with content + tournaments_to_display.extend(other_tournaments_with_content[:remaining_slots]) + + # Fallback: If still don't have enough tournaments, expand search + if len(tournaments_to_display) < 2: + # Get tournaments from wider time range + extended_tournaments = Tournament.objects.filter( + event=event + ).exclude( + id__in=[t.id for t in tournaments_to_display] + ).order_by('-start_date') # Most recent first + + remaining_needed = 2 - len(tournaments_to_display) + for tournament in extended_tournaments[:remaining_needed]: + try: + # Check if tournament has any content + content = tournament.broadcast_content() + has_content = ( + len(content.get('matches', [])) > 0 or + len(content.get('group_stages', [])) > 0 or + len(content.get('summons', [])) > 0 or + len(content.get('rankings', [])) > 0 + ) + if has_content: + tournaments_to_display.append(tournament) + except: + # If no content, add anyway as last resort + tournaments_to_display.append(tournament) + + # Ensure we have exactly what we need (limit to 2) + tournaments_to_display = tournaments_to_display[:2] + + # Log the prioritization for debugging + print(f"Event {event_id} broadcast prioritization:") + print(f" - Ongoing tournaments: {len(ongoing_tournaments)}") + print(f" - Starting soon tournaments: {len(starting_soon_tournaments)}") + print(f" - Other tournaments with content: {len(other_tournaments_with_content)}") + print(f" - Final selection: {[str(t.id) for t in tournaments_to_display]}") + + tournament_ids = [str(tournament.id) for tournament in tournaments_to_display] # Create QR code URL for the event broadcast qr_code_url_val = reverse('automatic-broadcast-event', args=[event_id])