Feat: Implementiere Team-Statistiken für Unterteams und Saisonauswahl

Erweitert die Team-Statistikansicht, sodass Head Coaches von übergeordneten Teams
auch die Statistiken ihrer untergeordneten Teams einsehen können.
Jedes Team wird mit seinen eigenen Statistiken separat dargestellt.
Zusätzlich wurde eine Filterfunktion implementiert, die es ermöglicht,
die Statistiken nach Saison zu filtern.
This commit is contained in:
Matthias Nagel 2025-11-19 08:43:44 +01:00
parent 9cfe5e50c4
commit c63ad532b5
2 changed files with 139 additions and 85 deletions

View File

@ -4,67 +4,109 @@
<div class="container mt-4"> <div class="container mt-4">
<h1 class="mb-4">Statistics for {{ team.name }}</h1> <h1 class="mb-4">Statistics for {{ team.name }}</h1>
<!-- W-L Record, PCT, and Streak --> <!-- Season Filter Form -->
<div class="row"> <div class="mb-4">
<div class="col-md-4"> <form method="get" action="">
<div class="card"> <div class="row align-items-end">
<div class="card-header">Record</div> <div class="col-md-3">
<div class="card-body"> <label for="season" class="form-label">Select Season:</label>
<h5 class="card-title">{{ wins }} - {{ losses }}</h5> <select name="season" id="season" class="form-select">
<p class="card-text">PCT: {{ pct|floatformat:3 }}</p> <option value="">All Time</option>
<p class="card-text">Streak: {{ streak }}</p> {% for season in available_seasons %}
<option value="{{ season }}" {% if season|stringformat:"s" == selected_season %}selected{% endif %}>
{{ season }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary">Filter</button>
</div> </div>
</div> </div>
</div> </form>
<div class="col-md-8"> </div>
<div class="card">
<div class="card-header">Offense vs Defense</div> {% for stats in teams_stats %}
<div class="card-body"> {% if not forloop.first %}
<p>Runs Scored: {{ runs_scored }}</p> <hr class="my-4">
<p>Runs Allowed: {{ runs_allowed }}</p> {% endif %}
<div class="progress" style="height: 30px;">
<div class="progress-bar bg-success" role="progressbar" style="width: {% widthratio runs_scored runs_scored|add:runs_allowed 100 %}%;" aria-valuenow="{{ runs_scored }}" aria-valuemin="0" aria-valuemax="{{ runs_scored|add:runs_allowed }}">{{ runs_scored }}</div> <h2 class="mb-3">{{ stats.team.name }}</h2>
<div class="progress-bar bg-danger" role="progressbar" style="width: {% widthratio runs_allowed runs_scored|add:runs_allowed 100 %}%;" aria-valuenow="{{ runs_allowed }}" aria-valuemin="0" aria-valuemax="{{ runs_scored|add:runs_allowed }}">{{ runs_allowed }}</div>
{% if stats.total_games > 0 %}
<!-- W-L Record, PCT, and Streak -->
<div class="row">
<div class="col-md-4">
<div class="card">
<div class="card-header">Record</div>
<div class="card-body">
<h5 class="card-title">{{ stats.wins }} - {{ stats.losses }}</h5>
<p class="card-text">PCT: {{ stats.pct|floatformat:3 }}</p>
<p class="card-text">Streak: {{ stats.streak }}</p>
</div>
</div>
</div>
<div class="col-md-8">
<div class="card">
<div class="card-header">Offense vs Defense</div>
<div class="card-body">
<p>Runs Scored: {{ stats.runs_scored }}</p>
<p>Runs Allowed: {{ stats.runs_allowed }}</p>
{% with total_runs=stats.runs_scored|add:stats.runs_allowed %}
<div class="progress" style="height: 30px;">
{% if total_runs > 0 %}
<div class="progress-bar bg-success" role="progressbar" style="width: {% widthratio stats.runs_scored total_runs 100 %}%;" aria-valuenow="{{ stats.runs_scored }}" aria-valuemin="0" aria-valuemax="{{ total_runs }}">{{ stats.runs_scored }}</div>
<div class="progress-bar bg-danger" role="progressbar" style="width: {% widthratio stats.runs_allowed total_runs 100 %}%;" aria-valuenow="{{ stats.runs_allowed }}" aria-valuemin="0" aria-valuemax="{{ total_runs }}">{{ stats.runs_allowed }}</div>
{% else %}
<div class="progress-bar" role="progressbar" style="width: 100%;">0</div>
{% endif %}
</div>
{% endwith %}
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<!-- Luck-O-Meter and Inning Heatmap --> <!-- Luck-O-Meter and Inning Heatmap -->
<div class="row mt-4"> <div class="row mt-4">
<div class="col-md-4"> <div class="col-md-4">
<div class="card"> <div class="card">
<div class="card-header">Luck-O-Meter</div> <div class="card-header">Luck-O-Meter</div>
<div class="card-body"> <div class="card-body">
<p>Real Winning %: {{ pct|floatformat:3 }}</p> <p>Real Winning %: {{ stats.pct|floatformat:3 }}</p>
<p>Pythagorean Winning %: {{ pythagorean_pct|floatformat:3 }}</p> <p>Pythagorean Winning %: {{ stats.pythagorean_pct|floatformat:3 }}</p>
</div>
</div>
</div>
<div class="col-md-8">
<div class="card">
<div class="card-header">Inning Heatmap (Runs Scored)</div>
<div class="card-body">
<table class="table table-bordered text-center">
<thead>
<tr>
{% for inning, runs in stats.inning_runs.items %}
<th>{{ inning }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
<tr>
{% for inning, runs in stats.inning_runs.items %}
<td>{{ runs }}</td>
{% endfor %}
</tr>
</tbody>
</table>
</div>
</div>
</div> </div>
</div> </div>
</div> {% else %}
<div class="col-md-8"> <div class="alert alert-info" role="alert">
<div class="card"> No games played for this team{% if selected_season %} in the {{ selected_season }} season{% endif %}.
<div class="card-header">Inning Heatmap (Runs Scored)</div>
<div class="card-body">
<table class="table table-bordered text-center">
<thead>
<tr>
{% for inning, runs in inning_runs.items %}
<th>{{ inning }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
<tr>
{% for inning, runs in inning_runs.items %}
<td>{{ runs }}</td>
{% endfor %}
</tr>
</tbody>
</table>
</div>
</div> </div>
</div> {% endif %}
</div> {% endfor %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -2,17 +2,17 @@ from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import HttpResponseForbidden from django.http import HttpResponseForbidden
from clubs.models import Team from clubs.models import Team
from calendars.models import Game, GameResult from calendars.models import Game
@login_required def _calculate_statistics(team, season=None):
def team_statistics(request, team_id): """
team = get_object_or_404(Team, pk=team_id) Calculates statistics for a given team and season.
"""
# Check if the user is the head coach of the team games_query = Game.objects.filter(team=team, result__isnull=False)
if request.user != team.head_coach: if season:
return HttpResponseForbidden("You are not authorized to view this page.") games_query = games_query.filter(season=season)
games = Game.objects.filter(team=team, result__isnull=False).order_by('start_time') games = games_query.order_by('start_time')
wins = 0 wins = 0
losses = 0 losses = 0
@ -25,16 +25,13 @@ def team_statistics(request, team_id):
for game in games: for game in games:
result = game.result result = game.result
print("DEBUGGER:"+str(result))
print("INNING_RESULTS:"+str(result.inning_results))
sorted_items = sorted(result.inning_results.items(),key=lambda x: int(x[0].split('_')[1])) sorted_items = sorted(result.inning_results.items(), key=lambda x: int(x[0].split('_')[1]))
home_innings = [ home_innings = [
item[1].get('home') item[1].get('home')
for item in sorted_items for item in sorted_items
if isinstance(item[1].get('home'), int) if isinstance(item[1].get('home'), int)
] ]
sorted_items = sorted(result.inning_results.items(),key=lambda x: int(x[0].split('_')[1]))
away_innings = [ away_innings = [
item[1].get('guest') item[1].get('guest')
for item in sorted_items for item in sorted_items
@ -42,25 +39,17 @@ def team_statistics(request, team_id):
] ]
home_score = sum(home_innings) home_score = sum(home_innings)
away_score = sum(away_innings) away_score = sum(away_innings)
print("HOMEINNING:"+str(home_innings))
if game.is_home_game: if game.is_home_game:
print("HEIMSPIEL")
team_score = home_score team_score = home_score
opponent_score = away_score opponent_score = away_score
print("Teamscore"+str(team_score))
print("Gegnerscore"+str(opponent_score))
else: else:
print("Auswerts")
team_score = away_score team_score = away_score
opponent_score = home_score opponent_score = home_score
print("Teamscore"+str(team_score))
print("Gegnerscore"+str(opponent_score))
runs_scored += team_score runs_scored += team_score
runs_allowed += opponent_score runs_allowed += opponent_score
# W-L Record and Streak
if team_score > opponent_score: if team_score > opponent_score:
wins += 1 wins += 1
if last_game_result == 'win': if last_game_result == 'win':
@ -75,19 +64,15 @@ def team_statistics(request, team_id):
else: else:
streak_counter = 1 streak_counter = 1
last_game_result = 'loss' last_game_result = 'loss'
print("Win:"+str(wins))
print("Looses:"+str(losses))
# Inning Heatmap
team_innings = home_innings if game.is_home_game else away_innings team_innings = home_innings if game.is_home_game else away_innings
for i, runs in enumerate(team_innings): for i, runs in enumerate(team_innings):
if i + 1 in inning_runs: if i + 1 in inning_runs:
inning_runs[i + 1] += runs inning_runs[i + 1] += runs
# Winning Percentage (PCT)
total_games = wins + losses total_games = wins + losses
pct = (wins / total_games) * 100 if total_games > 0 else 0 pct = (wins / total_games) * 100 if total_games > 0 else 0
# Streak
if last_game_result == 'win': if last_game_result == 'win':
streak_str = f"Won {streak_counter}" streak_str = f"Won {streak_counter}"
elif last_game_result == 'loss': elif last_game_result == 'loss':
@ -95,14 +80,12 @@ def team_statistics(request, team_id):
else: else:
streak_str = "N/A" streak_str = "N/A"
# Pythagorean Winning Percentage
if runs_scored > 0 or runs_allowed > 0: if runs_scored > 0 or runs_allowed > 0:
pythagorean_pct = (runs_scored**2 / (runs_scored**2 + runs_allowed**2)) * 100 pythagorean_pct = (runs_scored**2 / (runs_scored**2 + runs_allowed**2)) * 100
else: else:
pythagorean_pct = 0 pythagorean_pct = 0
return {
context = {
'team': team, 'team': team,
'wins': wins, 'wins': wins,
'losses': losses, 'losses': losses,
@ -112,7 +95,36 @@ def team_statistics(request, team_id):
'runs_allowed': runs_allowed, 'runs_allowed': runs_allowed,
'pythagorean_pct': pythagorean_pct, 'pythagorean_pct': pythagorean_pct,
'inning_runs': inning_runs, 'inning_runs': inning_runs,
'total_games': total_games,
}
@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,
} }
print("DebuggeR:"+str(context))
return render(request, 'team_stats/team_statistics.html', context) return render(request, 'team_stats/team_statistics.html', context)