Implementiert einen druckbaren Saison-Report für Head Coaches, der eine Übersicht über die Spielerteilnahme an allen Spielen einer ausgewählten Saison bietet. Wesentliche Änderungen: - **Neue URL und View (`season_report` in `team_stats/views.py`):** Empfängt `team_id` und `season`, holt Spieler, Spiele und deren Teilnahmestatus und bereitet die Daten auf. Enthält eine Berechtigungsprüfung für den Head Coach. - **Neue Vorlage (`team_stats/templates/team_stats/season_report.html`):** Zeigt eine Tabelle mit Spielen als Zeilen und Spielern als Spalten an. Teilnahmestatus wird mit Symbolen (✔, ✖, ?) dargestellt. Enthält druckspezifisches CSS, um die Lesbarkeit und das Seitenlayout für DIN A4 (Querformat) zu optimieren, inklusive vertikal gedrehter Spielernamen in der Kopfzeile zur Platzersparnis. - **Integration (`team_statistics.html`):** Ein "Saison-Report generieren"-Button wurde zur Team-Statistikseite hinzugefügt, der den Report für die aktuell ausgewählte Saison öffnet.
196 lines
6.5 KiB
Python
196 lines
6.5 KiB
Python
from django.shortcuts import render, get_object_or_404
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.http import HttpResponseForbidden
|
|
from clubs.models import Team
|
|
from calendars.models import Game, EventParticipation
|
|
|
|
def _calculate_statistics(team, season=None):
|
|
"""
|
|
Calculates statistics for a given team and season.
|
|
"""
|
|
games_query = Game.objects.filter(team=team, result__isnull=False).prefetch_related('opened_for_teams')
|
|
if season:
|
|
games_query = games_query.filter(season=season)
|
|
|
|
games = games_query.order_by('start_time')
|
|
|
|
wins = 0
|
|
losses = 0
|
|
runs_scored = 0
|
|
runs_allowed = 0
|
|
inning_runs = {i: 0 for i in range(1, 10)}
|
|
streak_counter = 0
|
|
last_game_result = None
|
|
|
|
games_with_supporters = 0
|
|
total_supporter_player_percentage = 0
|
|
|
|
for game in games:
|
|
# Supporter stats
|
|
if game.opened_for_teams.exists():
|
|
games_with_supporters += 1
|
|
|
|
attending_players = EventParticipation.objects.filter(event=game, status='attending').select_related('user__team')
|
|
total_attendees = attending_players.count()
|
|
|
|
if total_attendees > 0:
|
|
supporter_players_count = attending_players.exclude(user__team=game.team).count()
|
|
supporter_percentage = (supporter_players_count / total_attendees) * 100
|
|
total_supporter_player_percentage += supporter_percentage
|
|
|
|
# Standard game stats
|
|
result = game.result
|
|
|
|
sorted_items = sorted(result.inning_results.items(), key=lambda x: int(x[0].split('_')[1]))
|
|
home_innings = [
|
|
item[1].get('home')
|
|
for item in sorted_items
|
|
if isinstance(item[1].get('home'), int)
|
|
]
|
|
away_innings = [
|
|
item[1].get('guest')
|
|
for item in sorted_items
|
|
if isinstance(item[1].get('guest'), int)
|
|
]
|
|
home_score = sum(home_innings)
|
|
away_score = sum(away_innings)
|
|
|
|
if game.is_home_game:
|
|
team_score = home_score
|
|
opponent_score = away_score
|
|
else:
|
|
team_score = away_score
|
|
opponent_score = home_score
|
|
|
|
runs_scored += team_score
|
|
runs_allowed += opponent_score
|
|
|
|
if team_score > opponent_score:
|
|
wins += 1
|
|
if last_game_result == 'win':
|
|
streak_counter += 1
|
|
else:
|
|
streak_counter = 1
|
|
last_game_result = 'win'
|
|
elif team_score < opponent_score:
|
|
losses += 1
|
|
if last_game_result == 'loss':
|
|
streak_counter += 1
|
|
else:
|
|
streak_counter = 1
|
|
last_game_result = 'loss'
|
|
|
|
team_innings = home_innings if game.is_home_game else away_innings
|
|
for i, runs in enumerate(team_innings):
|
|
if i + 1 in inning_runs:
|
|
inning_runs[i + 1] += runs
|
|
|
|
total_games = wins + losses
|
|
pct = (wins / total_games) * 100 if total_games > 0 else 0
|
|
|
|
if last_game_result == 'win':
|
|
streak_str = f"Won {streak_counter}"
|
|
elif last_game_result == 'loss':
|
|
streak_str = f"Lost {streak_counter}"
|
|
else:
|
|
streak_str = "N/A"
|
|
|
|
if runs_scored > 0 or runs_allowed > 0:
|
|
pythagorean_pct = (runs_scored**2 / (runs_scored**2 + runs_allowed**2)) * 100
|
|
else:
|
|
pythagorean_pct = 0
|
|
|
|
avg_supporter_player_percentage = (total_supporter_player_percentage / games_with_supporters) if games_with_supporters > 0 else 0
|
|
|
|
return {
|
|
'team': team,
|
|
'wins': wins,
|
|
'losses': losses,
|
|
'pct': pct,
|
|
'streak': streak_str,
|
|
'runs_scored': runs_scored,
|
|
'runs_allowed': runs_allowed,
|
|
'pythagorean_pct': pythagorean_pct,
|
|
'inning_runs': inning_runs,
|
|
'total_games': total_games,
|
|
'games_with_supporters': games_with_supporters,
|
|
'avg_supporter_player_percentage': avg_supporter_player_percentage,
|
|
}
|
|
|
|
@login_required
|
|
def team_statistics(request, team_id):
|
|
team = get_object_or_404(Team, pk=team_id)
|
|
|
|
# User must be the head coach of the parent team to view the stats
|
|
if request.user != team.head_coach:
|
|
return HttpResponseForbidden("You are not authorized to view this page.")
|
|
|
|
selected_season = request.GET.get('season')
|
|
|
|
# Create a list of the parent team and all its child teams
|
|
teams_to_display = [team] + list(team.child_teams.all())
|
|
|
|
# Calculate statistics for each team
|
|
teams_stats = []
|
|
for t in teams_to_display:
|
|
stats = _calculate_statistics(t, selected_season)
|
|
teams_stats.append(stats)
|
|
|
|
# Get all available seasons for the filter dropdown
|
|
available_seasons = Game.objects.values_list('season', flat=True).distinct().order_by('season')
|
|
|
|
context = {
|
|
'team': team, # Primary team for page header/auth
|
|
'teams_stats': teams_stats,
|
|
'available_seasons': available_seasons,
|
|
'selected_season': selected_season,
|
|
}
|
|
|
|
return render(request, 'team_stats/team_statistics.html', context)
|
|
|
|
@login_required
|
|
def season_report(request, team_id, season):
|
|
team = get_object_or_404(Team, pk=team_id)
|
|
|
|
# Security check: only head coach can view
|
|
if request.user != team.head_coach:
|
|
return HttpResponseForbidden("You are not authorized to view this report.")
|
|
|
|
# 1. Get all players for the team, ordered
|
|
players = team.players.all().order_by('last_name', 'first_name')
|
|
player_ids = [p.id for p in players]
|
|
|
|
# 2. Get all games for the team and season
|
|
games = Game.objects.filter(team=team, season=season).order_by('start_time')
|
|
game_ids = [g.id for g in games]
|
|
|
|
# 3. Get all relevant participation data in one query
|
|
participations = EventParticipation.objects.filter(
|
|
event_id__in=game_ids,
|
|
user_id__in=player_ids
|
|
)
|
|
|
|
# 4. Create a fast lookup map
|
|
participation_map = {
|
|
(p.event_id, p.user_id): p.status for p in participations
|
|
}
|
|
|
|
# 5. Build the final data structure for the template
|
|
report_data = []
|
|
for game in games:
|
|
statuses = []
|
|
for player in players:
|
|
status = participation_map.get((game.id, player.id), 'maybe')
|
|
statuses.append(status)
|
|
report_data.append({
|
|
'game': game,
|
|
'statuses': statuses
|
|
})
|
|
|
|
context = {
|
|
'team': team,
|
|
'season': season,
|
|
'players': players,
|
|
'report_data': report_data,
|
|
}
|
|
return render(request, 'team_stats/season_report.html', context) |