Laurent 2 months ago
commit 731c3fc1fb
  1. 4
      tournaments/models/tournament.py
  2. 4
      tournaments/templates/tournaments/broadcast/broadcast.html
  3. 2
      tournaments/templates/tournaments/broadcast/broadcast_base.html
  4. 17
      tournaments/templates/tournaments/broadcast/broadcast_club.html
  5. 145
      tournaments/templates/tournaments/broadcast/broadcasted_auto.html
  6. 20
      tournaments/templates/tournaments/broadcast/broadcasted_bracket.html
  7. 67
      tournaments/templates/tournaments/broadcast/broadcasted_club_auto.html
  8. 67
      tournaments/templates/tournaments/broadcast/broadcasted_event_auto.html
  9. 2
      tournaments/templates/tournaments/broadcast/broadcasted_group_stage.html
  10. 2
      tournaments/templates/tournaments/broadcast/broadcasted_group_stages.html
  11. 2
      tournaments/templates/tournaments/broadcast/broadcasted_matches.html
  12. 4
      tournaments/templates/tournaments/broadcast/broadcasted_planning.html
  13. 2
      tournaments/templates/tournaments/broadcast/broadcasted_prog.html
  14. 2
      tournaments/templates/tournaments/broadcast/broadcasted_summon.html
  15. 143
      tournaments/views.py

@ -167,6 +167,10 @@ class Tournament(BaseModel):
return str
def short_full_name(self):
# For animation tournaments with custom names, just return the name
if self.federal_level_category == 0 and self.name: # FederalLevelCategory.UNLISTED (Animation) with custom name
return self.name
age = self.age()
str = f"{self.level()}{self.category()[0]}"
if age is not None and self.federal_age_category != 200:

@ -24,13 +24,15 @@
<div class="grid-x">
<div class="cell medium-6 large-6 topblock padding10">
<div class="bubble">
<div><a href="{% url 'automatic-broadcast' tournament.id %}">Automatique</a></div>
<div><a href="{% url 'automatic-broadcast' tournament.id %}">Auto</a></div>
<div><a href="{% url 'automatic-broadcast-event' tournament.event.id %}">Event Auto</a></div>
<div><a href="{% url 'broadcasted-matches' tournament.id %}">Matchs</a></div>
<div><a href="{% url 'broadcasted-group-stages' tournament.id %}">Poules</a></div>
<div><a href="{% url 'broadcasted-summons' tournament.id %}">Convocations</a></div>
<div><a href="{% url 'broadcasted-rankings' tournament.id %}">Classement</a></div>
<div><a href="{% url 'broadcasted-planning' tournament.id %}">(beta) Planning</a></div>
<div><a href="{% url 'broadcasted-bracket' tournament.id %}">(beta) Tableau</a></div>
<div><a href="{% url 'club-broadcast-auto' tournament.event.club.broadcast_code %}">Club Auto</a></div>
</div>
</div>

@ -6,7 +6,7 @@
<head>
{% include 'tournaments/broadcast/base_head.html' %}
<script src="{% static 'tournaments/js/alpine.min.js' %}"></script>
<script src="{% static 'tournaments/js/alpine.min.js' %}" defer></script>
<title>{% block head_title %}Page Title{% endblock %}</title>

@ -2,12 +2,22 @@
{% load static %}
{% block head_title %}Broadcast{% endblock %}
{% block first_title %}{{ club.name }}{% endblock %}
{% block head_title %}{{ club.broadcast_code }} - {{ club.name }} Broadcast{% endblock %}
{% block first_title %}{{ club.name }} ({{ club.broadcast_code }}){% endblock %}
{% block second_title %}Broadcast{% endblock %}
{% block content %}
<div class="grid-x">
<!-- Club Auto Broadcast Link at the top -->
<div class="cell topblock padding10">
<div class="bubble" style="padding: 8px 15px; margin-bottom: 10px; background-color: #f8f9fa;">
<span style="font-size: 0.9em; color: #555;">
<a href="{% url 'club-broadcast-auto' club.broadcast_code %}" style="color: #007acc; text-decoration: none; font-weight: 500;">Diffusion automatique du club</a>
- Cycle automatiquement entre tous les tournois actifs +- 3 jours
</span>
</div>
</div>
<div class="cell topblock padding10">
<div class="bubble">
@ -30,7 +40,8 @@
<span>{{ tournament.private_label }}</span>
</div>
<div class="table-cell">
<span><a href="{% url 'automatic-broadcast' tournament.id %}">Automatic</a></span> |
<span><a href="{% url 'automatic-broadcast' tournament.id %}">Auto</a></span> |
<span><a href="{% url 'automatic-broadcast-event' tournament.event.id %}">Event Auto</a></span> |
<span><a href="{% url 'broadcasted-bracket' tournament.id %}">(beta) Tableau</a></span> |
<span><a href="{% url 'broadcasted-planning' tournament.id %}">(beta) Planning</a></span> |
<span><a href="{% url 'broadcasted-matches' tournament.id %}">Matchs</a></span> |

@ -6,7 +6,7 @@
<head>
{% include 'tournaments/broadcast/base_head.html' %}
<script src="{% static 'tournaments/js/alpine.min.js' %}"></script>
<script src="{% static 'tournaments/js/alpine.min.js' %}" defer></script>
<title>Broadcast</title>
@ -43,7 +43,12 @@
prefixTitle: '',
retrieveData() {
fetch('/tournament/{{ tournament.id }}/broadcast/json/')
.then(res => res.json())
.then(res => {
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
return res.json();
})
.then((data) => {
this.paginatedMatches = this.paginate(data.matches, 8)
this.paginatedGroupStages = this.paginate(data.group_stages, 4)
@ -56,6 +61,25 @@
this.active = 1; // Reset to the first page
}
})
.catch((error) => {
console.error('Error fetching tournament data:', error);
// If this tournament is in an iframe (part of event/club auto), notify parent
if (window.parent !== window) {
console.log('Notifying parent of tournament error');
window.parent.postMessage({
type: 'tournamentError',
tournamentId: '{{ tournament.id }}',
error: error.message
}, '*');
}
// Set empty data to prevent further errors
this.paginatedMatches = [];
this.paginatedGroupStages = [];
this.paginatedSummons = [];
this.paginatedRankings = [];
})
},
paginateSummons(array) {
let pageSize = 16
@ -127,14 +151,18 @@
}, 15000)
},
pageCount() {
return this.paginatedMatches.length + this.paginatedGroupStages.length + this.paginatedSummons.length + this.paginatedRankings.length
return (this.paginatedMatches?.length || 0) + (this.paginatedGroupStages?.length || 0) + (this.paginatedSummons?.length || 0) + (this.paginatedRankings?.length || 0)
},
setPrefixTitle() {
if (this.active < 1 + this.paginatedSummons.length) {
const summonsLength = this.paginatedSummons?.length || 0;
const matchesLength = this.paginatedMatches?.length || 0;
const groupStagesLength = this.paginatedGroupStages?.length || 0;
if (this.active < 1 + summonsLength) {
this.prefixTitle = 'Convocations'
} else if (this.active < 1 + this.paginatedSummons.length + this.paginatedMatches.length) {
} else if (this.active < 1 + summonsLength + matchesLength) {
this.prefixTitle = 'Matchs'
} else if (this.active < 1 + this.paginatedSummons.length + this.paginatedMatches.length + this.paginatedGroupStages.length) {
} else if (this.active < 1 + summonsLength + matchesLength + groupStagesLength) {
this.prefixTitle = 'Poules'
} else {
this.prefixTitle = 'Classement'
@ -162,7 +190,7 @@
<main>
<div class="grid-x">
<template x-for="i in paginatedSummons.length">
<template x-for="i in (paginatedSummons?.length || 0)">
<template x-for="column in paginatedSummons[i-1]">
<div class="cell medium-6 large-6 topblock padding10" x-show="active === i">
{% include 'tournaments/broadcast/broadcasted_summon.html' %}
@ -170,25 +198,25 @@
</template>
</template>
<template x-for="i in paginatedMatches.length" >
<template x-for="i in (paginatedMatches?.length || 0)" >
<template x-for="match in paginatedMatches[i-1]" >
<div class="cell medium-6 large-3 padding10" x-show="active === i + paginatedSummons.length">
<div class="cell medium-6 large-3 padding10" x-show="active === i + (paginatedSummons?.length || 0)">
{% include 'tournaments/broadcast/broadcasted_match.html' %}
</div>
</template>
</template>
<template x-for="i in paginatedGroupStages.length">
<template x-for="i in (paginatedGroupStages?.length || 0)">
<template x-for="group_stage in paginatedGroupStages[i-1]">
<div class="cell medium-6 large-3 padding10" x-show="active === i + paginatedSummons.length + paginatedMatches.length">
<div class="cell medium-6 large-3 padding10" x-show="active === i + (paginatedSummons?.length || 0) + (paginatedMatches?.length || 0)">
{% include 'tournaments/broadcast/broadcasted_group_stage.html' %}
</div>
</template>
</template>
<template x-for="i in paginatedRankings.length">
<template x-for="i in (paginatedRankings?.length || 0)">
<template x-for="column in paginatedRankings[i-1]">
<div class="cell medium-6 large-6 topblock padding10" x-show="active === i + paginatedSummons.length + paginatedMatches.length + paginatedGroupStages.length">
<div class="cell medium-6 large-6 topblock padding10" x-show="active === i + (paginatedSummons?.length || 0) + (paginatedMatches?.length || 0) + (paginatedGroupStages?.length || 0)">
{% include 'tournaments/broadcast/broadcasted_ranking.html' %}
</div>
</template>
@ -200,6 +228,7 @@
</div>
</body>
</body>
<footer class="footer-broadcast">
{% if tournament.event.images.exists %}
@ -213,4 +242,94 @@
</div>
{% endif %}
</footer>
<script>
console.log('Tournament auto page loaded, tournament ID: {{ tournament.id }}');
let startTime = Date.now();
let lastActive = null;
let cycleCount = 0;
let maxPagesSeen = 0;
let hasNotifiedParent = false;
function checkForCompletion() {
try {
// Find the Alpine.js element
const alpineEl = document.querySelector('[x-data]');
if (!alpineEl || !alpineEl._x_dataStack) {
console.log('Alpine not ready yet');
return;
}
const data = alpineEl._x_dataStack[0];
const currentActive = data.active;
const pageCount = data.pageCount();
const elapsed = Date.now() - startTime;
if (pageCount === 0) {
console.log('No pages to display yet');
return;
}
// Track the maximum page number we've seen
maxPagesSeen = Math.max(maxPagesSeen, currentActive);
console.log(`Current page: ${currentActive}/${pageCount}, Max seen: ${maxPagesSeen}, Elapsed: ${Math.round(elapsed/1000)}s`);
let shouldComplete = false;
let reason = '';
// Calculate completion time: 15 seconds per page
const completionTime = pageCount * 15000; // 15 seconds per page
if (pageCount === 1) {
// Single page tournament: complete after 15 seconds
if (elapsed >= 15000) {
shouldComplete = true;
reason = 'Single page - 15 seconds elapsed';
}
} else if (pageCount > 1) {
// Multi-page tournament: complete when we cycle back to page 1 after seeing all pages
if (currentActive === 1 && lastActive > 1 && maxPagesSeen >= pageCount) {
shouldComplete = true;
reason = 'Multi-page - full cycle completed';
} else if (elapsed >= completionTime) {
// Fallback: complete after (pageCount * 15 seconds)
shouldComplete = true;
reason = `Multi-page - ${pageCount * 15} seconds elapsed`;
}
}
if (shouldComplete && !hasNotifiedParent) {
hasNotifiedParent = true;
cycleCount++;
console.log(`🎯 TOURNAMENT COMPLETED! Reason: ${reason}`);
// Send message to parent
if (window.parent !== window) {
window.parent.postMessage({
type: 'tournamentCycleComplete',
cycleCount: cycleCount,
tournamentId: '{{ tournament.id }}',
pageCount: pageCount,
reason: reason,
elapsedSeconds: Math.round(elapsed/1000)
}, '*');
console.log('Message sent to parent window');
} else {
console.log('No parent window found');
}
}
lastActive = currentActive;
} catch (error) {
console.error('Error checking completion:', error);
}
}
// Check every 2 seconds
setInterval(checkForCompletion, 2000);
console.log('Tournament completion detection initialized');
</script>
</html>

@ -15,7 +15,7 @@
<title>Tableau</title>
<script src="{% static 'tournaments/js/alpine.min.js' %}"></script>
<script src="{% static 'tournaments/js/alpine.min.js' %}" defer></script>
<!-- Matomo -->
<script>
var _paq = window._paq = window._paq || [];
@ -43,6 +43,24 @@
padding: 20px;
max-width: 45%;
}
/* Reduce font size for player names */
.broadcast-mode .player .semibold {
font-size: 0.85em !important;
line-height: 1.2 !important;
}
/* Reduce font size for team weight */
.broadcast-mode .score.numbers {
font-size: 0.85em !important;
}
/* Optional: Also reduce single/two player text if needed */
.broadcast-mode .single-player .semibold,
.broadcast-mode .two-players .semibold {
font-size: 0.85em !important;
line-height: 1.2 !important;
}
</style>
</head>

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html>
<head>
<title>Club Auto Broadcast</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
overflow: hidden;
background-color: #000;
}
iframe {
width: 100vw;
height: 100vh;
border: none;
display: block;
background-color: #000;
}
</style>
</head>
<body>
<iframe id="tournamentFrame" src="about:blank"></iframe>
<script>
const tournamentIds = {{ tournament_ids|safe }};
let currentIndex = 0;
const frame = document.getElementById('tournamentFrame');
function loadNextTournament() {
if (tournamentIds.length === 0) {
document.body.innerHTML = '<div style="display: flex; align-items: center; justify-content: center; height: 100vh; font-size: 24px; color: white;">Aucun tournoi disponible pour {{ club_name }}</div>';
return;
}
const tournamentId = tournamentIds[currentIndex];
const url = `/tournament/${tournamentId}/broadcast/auto/`;
console.log(`Loading tournament ${currentIndex + 1}/${tournamentIds.length}: ${tournamentId.substring(0, 8)}`);
frame.src = url;
currentIndex = (currentIndex + 1) % tournamentIds.length;
}
// Listen for cycle completion messages from tournament iframe
window.addEventListener('message', function(event) {
if (event.data.type === 'tournamentCycleComplete') {
console.log(`Tournament completed (${event.data.reason})`);
setTimeout(loadNextTournament, 1000); // Switch after 1 second
} else if (event.data.type === 'tournamentError') {
console.error(`Tournament ${event.data.tournamentId.substring(0, 8)} failed: ${event.data.error}`);
console.log('Switching to next tournament due to error...');
setTimeout(loadNextTournament, 2000); // Switch after 2 seconds on error
}
});
// Load first tournament immediately
loadNextTournament();
</script>
</body>
</html>

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html>
<head>
<title>Event Auto Broadcast</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
overflow: hidden;
background-color: #000;
}
iframe {
width: 100vw;
height: 100vh;
border: none;
display: block;
background-color: #000;
}
</style>
</head>
<body>
<iframe id="tournamentFrame" src="about:blank"></iframe>
<script>
const tournamentIds = {{ tournament_ids|safe }};
let currentIndex = 0;
const frame = document.getElementById('tournamentFrame');
function loadNextTournament() {
if (tournamentIds.length === 0) {
document.body.innerHTML = '<div style="display: flex; align-items: center; justify-content: center; height: 100vh; font-size: 24px; color: white;">Aucun tournoi disponible</div>';
return;
}
const tournamentId = tournamentIds[currentIndex];
const url = `/tournament/${tournamentId}/broadcast/auto/`;
console.log(`Loading tournament ${currentIndex + 1}/${tournamentIds.length}: ${tournamentId.substring(0, 8)}`);
frame.src = url;
currentIndex = (currentIndex + 1) % tournamentIds.length;
}
// Listen for cycle completion messages from tournament iframe
window.addEventListener('message', function(event) {
if (event.data.type === 'tournamentCycleComplete') {
console.log(`Tournament completed (${event.data.reason})`);
setTimeout(loadNextTournament, 1000); // Switch after 1 second
} else if (event.data.type === 'tournamentError') {
console.error(`Tournament ${event.data.tournamentId.substring(0, 8)} failed: ${event.data.error}`);
console.log('Switching to next tournament due to error...');
setTimeout(loadNextTournament, 2000); // Switch after 2 seconds on error
}
});
// Load first tournament immediately
loadNextTournament();
</script>
</body>
</html>

@ -14,7 +14,7 @@
}">
<template x-for="name in group_stage.teams[i-1].names">
<div class="bold" x-text="name"></div>
<div class="semibold" x-text="name"></div>
<!-- <div class="semibold" x-data="{

@ -14,7 +14,7 @@
<title>Poules</title>
<script src="{% static 'tournaments/js/alpine.min.js' %}"></script>
<script src="{% static 'tournaments/js/alpine.min.js' %}" defer></script>
<!-- Matomo -->
<script>
var _paq = window._paq = window._paq || [];

@ -13,7 +13,7 @@
<title>Matchs</title>
<script src="{% static 'tournaments/js/alpine.min.js' %}"></script>
<scriptt src="{% static 'tournaments/js/alpine.min.js' %}" defer></scriptt>
<!-- Matomo -->
<script>
var _paq = window._paq = window._paq || [];

@ -126,7 +126,7 @@
<title>Programmation</title>
<script src="{% static 'tournaments/js/alpine.min.js' %}"></script>
<script src="{% static 'tournaments/js/alpine.min.js' %}" defer></script>
<script>
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
@ -267,7 +267,7 @@
this.currentDayIndex = 0;
this.currentPageIndex = 0;
}
}, 15000);
}, 20000);
},
calculateFractionWidth() {

@ -13,7 +13,7 @@
<title>Matchs</title>
<script src="{% static 'tournaments/js/alpine.min.js' %}"></script>
<script src="{% static 'tournaments/js/alpine.min.js' %}" defer></script>
<!-- Matomo -->
<script>
var _paq = window._paq = window._paq || [];

@ -10,7 +10,7 @@
</div>
</div>
<div class="table-cell left">
<div class="table-cell large"><span x-text="summon.date"></span></div>
<div class="table-cell"><span x-text="summon.date"></span></div>
<div class="table-cell"><span x-text="summon.court"></span></div>
</div>
<div class="table-cell right"><div class="mybox center"><span x-text="summon.stage"></span></div></div>

@ -521,18 +521,47 @@ def tournament_broadcast_home(request, tournament_id):
def automatic_broadcast(request, tournament_id):
tournament = get_object_or_404(Tournament, pk=tournament_id)
return render(request, 'tournaments/broadcast/broadcasted_auto.html', {
response = render(request, 'tournaments/broadcast/broadcasted_auto.html', {
'tournament': tournament,
'qr_code_url': qr_code_url(request, tournament_id),
'qr_code_options': qr_code_options(),
})
# Allow iframe embedding from same origin
response['X-Frame-Options'] = 'SAMEORIGIN'
return response
def automatic_broadcast_event(request, event_id):
event = get_object_or_404(Event, pk=event_id)
tournaments = Tournament.objects.filter(event=event)
tournament_ids = [str(tournament.id) for tournament in tournaments]
return render(request, 'tournaments/broadcast/broadcasted_auto_event.html', {
tournaments = Tournament.objects.filter(event=event).order_by('start_date')
# Filter tournaments that have broadcast content
tournaments_with_content = []
for tournament in tournaments:
try:
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_with_content.append(tournament)
except:
# Skip tournaments that error when getting content
continue
tournament_ids = [str(tournament.id) for tournament in tournaments_with_content]
# Create QR code URL for the event broadcast
qr_code_url_val = reverse('automatic-broadcast-event', args=[event_id])
qr_code_url_full = request.build_absolute_uri(qr_code_url_val)
return render(request, 'tournaments/broadcast/broadcasted_event_auto.html', {
'tournament_ids': tournament_ids,
'event': event,
'qr_code_url': qr_code_url_full,
'qr_code_options': qr_code_options(),
})
def qr_code_url(request, tournament_id):
@ -756,33 +785,115 @@ def club_broadcast(request, broadcast_code):
def club_broadcast_auto(request, broadcast_code):
club = get_object_or_404(Club, broadcast_code=broadcast_code)
q_not_deleted = Q(is_deleted=False, event__club=club)
q_not_deleted = Q(is_deleted=False, event__club=club, is_private=False)
now = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
# Primary selection: Active tournaments (current logic)
recent_time_window = now - timedelta(hours=4)
tournaments = Tournament.objects.filter(
active_tournaments = Tournament.objects.filter(
q_not_deleted,
Q(is_private=False), # Exclude private tournaments
(
(
Q(end_date__isnull=True) & # Include ongoing tournaments
(Q(start_date__gte=now - timedelta(days=3)) & Q(start_date__lt=now + timedelta(days=3))) # Include tournaments that are ongoing and start within 3 days
(Q(start_date__gte=now - timedelta(days=3)) & Q(start_date__lt=now + timedelta(days=3)))
) |
(Q(end_date__isnull=False) & Q(end_date__gte=recent_time_window)) # Include tournaments finished within the last 4 hours
(Q(end_date__isnull=False) & Q(end_date__gte=recent_time_window)) # Finished within 4 hours
)
).select_related('event').distinct()
# Smart fallback: If we have few active tournaments, add recent/upcoming ones
tournaments_set = set()
tournaments_list = []
# Add active tournaments first
for tournament in active_tournaments:
if tournament.id not in tournaments_set:
tournaments_set.add(tournament.id)
tournaments_list.append(tournament)
# If we have less than 3 tournaments, add recent finished tournaments
if len(tournaments_list) < 3:
recent_finished = Tournament.objects.filter(
q_not_deleted,
Q(end_date__isnull=False),
Q(end_date__gte=now - timedelta(days=7)) # Finished within last 7 days
).select_related('event').order_by('-end_date')[:3]
for tournament in recent_finished:
if tournament.id not in tournaments_set and len(tournaments_list) < 5:
tournaments_set.add(tournament.id)
tournaments_list.append(tournament)
# If we still have less than 3 tournaments, add upcoming tournaments
if len(tournaments_list) < 3:
upcoming = Tournament.objects.filter(
q_not_deleted,
Q(start_date__gt=now + timedelta(days=3)), # Starting more than 3 days from now
Q(start_date__lte=now + timedelta(days=30)) # But within next 30 days
).select_related('event').order_by('start_date')[:3]
for tournament in upcoming:
if tournament.id not in tournaments_set and len(tournaments_list) < 5:
tournaments_set.add(tournament.id)
tournaments_list.append(tournament)
# Filter tournaments that have broadcast content (or at least some basic info to show)
tournaments_with_content = []
for tournament in tournaments_list:
try:
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
)
tournament_ids = [str(tournament.id) for tournament in tournaments]
#print(tournament_ids)
return render(request, 'tournaments/broadcast/broadcasted_auto_event.html', {
# For upcoming tournaments, show them even without content (they'll show basic info)
is_upcoming = tournament.start_date > now + timedelta(days=3)
is_recent_finished = tournament.end_date and tournament.end_date >= now - timedelta(days=7)
if has_content or is_upcoming or is_recent_finished:
tournaments_with_content.append(tournament)
except:
# For tournaments that error, still include if they're upcoming/recent
is_upcoming = tournament.start_date > now + timedelta(days=3)
is_recent_finished = tournament.end_date and tournament.end_date >= now - timedelta(days=7)
if is_upcoming or is_recent_finished:
tournaments_with_content.append(tournament)
# Sort tournaments: Active first, then by start date
def tournament_priority(t):
now_time = timezone.now()
if t.end_date is None and t.start_date <= now_time + timedelta(days=3):
return (0, t.start_date) # Active tournaments first
elif t.end_date and t.end_date >= now_time - timedelta(hours=4):
return (1, -t.end_date.timestamp()) # Recently finished, newest first
elif t.start_date > now_time:
return (2, t.start_date) # Upcoming tournaments by start date
else:
return (3, -t.end_date.timestamp() if t.end_date else 0) # Other finished tournaments
tournaments_with_content.sort(key=tournament_priority)
tournament_ids = [str(tournament.id) for tournament in tournaments_with_content]
print(f"Club '{club.name}' auto broadcast:")
print(f"Found {len(active_tournaments)} active, total {len(tournaments_with_content)} tournaments to display")
for i, tournament in enumerate(tournaments_with_content):
status = "ACTIVE" if (tournament.end_date is None and tournament.start_date <= timezone.now() + timedelta(days=3)) else \
"RECENT" if (tournament.end_date and tournament.end_date >= timezone.now() - timedelta(days=7)) else \
"UPCOMING" if tournament.start_date > timezone.now() else "FINISHED"
# Fix: Convert UUID to string before slicing
tournament_id_short = str(tournament.id)[:8]
print(f" {i+1}. {tournament_id_short} ({tournament.event.display_name()}) - {tournament.name or 'Unnamed'} [{status}]")
return render(request, 'tournaments/broadcast/broadcasted_club_auto.html', {
'tournament_ids': tournament_ids,
'club_name': club.name,
'qr_code_url': qr_code_url_with_query(request, club.id),
'qr_code_options': qr_code_options(),
})
def download(request):
return render(request, 'tournaments/download.html')

Loading…
Cancel
Save