"""Main dashboard routes for the Team Tryouts application. Uses polymorphic isinstance checks instead of role-string comparisons. """ from flask import Blueprint, render_template, redirect, url_for, flash, request from flask_login import login_required, current_user from flask_babel import gettext as _ from app.extensions import db from app.models import ( Admin, Manager, Coach, Player, Scout, User, Tryout, Evaluation, TryoutRegistration, TeamMember, Match, MatchParticipant, ) from app.permissions import coach_tryout_ids from sqlalchemy import func from datetime import date main_bp = Blueprint('main', __name__) @main_bp.route('/') def index(): """Redirect root URL to login page.""" return redirect(url_for('auth.login')) @main_bp.route('/lang/') def set_language(locale): """Switch the interface language and return where the user came from. Available to anonymous visitors too: the login page has to be readable before anyone can sign in. A GET link rather than a form: the only thing a forged request could achieve is changing the visitor's own display language, which carries no consequence worth a token. The redirect target is still validated — an unchecked `Referer` would make this an open redirect. """ from app.i18n import set_locale from app.routes.auth import is_safe_url if not set_locale(locale): flash(_('That language is not available.'), 'warning') target = request.referrer if target and is_safe_url(target): return redirect(target) return redirect(url_for('main.dashboard') if current_user.is_authenticated else url_for('auth.login')) @main_bp.route('/dashboard') @login_required def dashboard(): """Render the main dashboard with role-specific statistics. Each User subclass provides its own stats view. """ user = current_user stats = {} if isinstance(user, Admin): stats['total_users'] = User.query.count() stats['total_players'] = User.query.filter_by(role='player').count() stats['total_tryouts'] = Tryout.query.count() stats['total_evaluations'] = Evaluation.query.count() stats['active_tryouts'] = Tryout.query.filter_by(status='in_progress').count() stats['completed_tryouts'] = Tryout.query.filter_by(status='completed').count() stats['recent_users'] = User.query.order_by(User.created_at.desc()).limit(10).all() stats['recent_tryouts'] = Tryout.query.order_by(Tryout.created_at.desc()).limit(10).all() today = date.today() stats['upcoming_matches'] = Match.query.filter( Match.status == 'scheduled', Match.date >= today, ).order_by(Match.date, Match.start_time).limit(5).all() elif isinstance(user, Manager): stats['total_tryouts'] = Tryout.query.filter_by(created_by=user.id).count() stats['active_tryouts'] = Tryout.query.filter_by( created_by=user.id, status='in_progress').count() stats['total_evaluations'] = Evaluation.query.filter_by(evaluator_id=user.id).count() stats['my_tryouts'] = Tryout.query.filter_by( created_by=user.id).order_by(Tryout.date.desc()).limit(5).all() today = date.today() manager_tryout_ids = [t.id for t in Tryout.query.filter_by(created_by=user.id).all()] stats['upcoming_matches'] = Match.query.filter( Match.tryout_id.in_(manager_tryout_ids), Match.status == 'scheduled', Match.date >= today, ).order_by(Match.date, Match.start_time).limit(5).all() if manager_tryout_ids else [] elif isinstance(user, Coach): stats['my_evaluations'] = Evaluation.query.filter_by(evaluator_id=user.id).count() registrations = TryoutRegistration.query.filter( TryoutRegistration.status.in_(['registered', 'attended'])).all() registered_player_ids = [r.player_id for r in registrations] evaluated_player_ids = [e.player_id for e in Evaluation.query.filter_by(evaluator_id=user.id).all()] stats['pending_evaluations'] = len(set(registered_player_ids) - set(evaluated_player_ids)) stats['my_recent_evaluations'] = Evaluation.query.filter_by( evaluator_id=user.id).order_by(Evaluation.created_at.desc()).limit(10).all() today = date.today() # Was: the first team matching the legacy coach_id column, and only # the tryouts targeting it. A coach attached by the many-to-many # relationship, or coaching a second team, saw no upcoming match. tryout_ids = coach_tryout_ids(user) stats['upcoming_matches'] = Match.query.filter( Match.tryout_id.in_(tryout_ids), Match.status == 'scheduled', Match.date >= today, ).order_by(Match.date, Match.start_time).limit(5).all() if tryout_ids else [] elif isinstance(user, Player): stats['my_tryouts'] = TryoutRegistration.query.filter_by(player_id=user.id).count() stats['my_registrations'] = TryoutRegistration.query.filter_by( player_id=user.id).order_by(TryoutRegistration.registered_at.desc()).limit(5).all() today = date.today() next_matches = [] all_registrations = TryoutRegistration.query.filter_by(player_id=user.id).all() registered_tryout_ids = [r.tryout_id for r in all_registrations] player_participant_matches = MatchParticipant.query.filter_by(player_id=user.id).all() player_match_ids = [p.match_id for p in player_participant_matches] player_team_memberships = TeamMember.query.filter_by(player_id=user.id).all() player_team_ids = [tm.team_id for tm in player_team_memberships] upcoming_matches = Match.query.filter( Match.tryout_id.in_(registered_tryout_ids), Match.status == 'scheduled', Match.date >= today, ).order_by(Match.date, Match.start_time).all() for match in upcoming_matches: is_participant = False team = None if match.match_type == 'team_vs_team': if match.team1_id in player_team_ids: is_participant = True team = next((tm for tm in player_team_memberships if tm.team_id == match.team1_id), None) elif match.team2_id in player_team_ids: is_participant = True team = next((tm for tm in player_team_memberships if tm.team_id == match.team2_id), None) else: if match.id in player_match_ids: is_participant = True if is_participant: next_matches.append({ 'tryout': match.tryout, 'match': match, 'team': team.team if team else None, }) stats['next_matches'] = next_matches elif isinstance(user, Scout): stats['total_players'] = User.query.filter_by(role='player').count() stats['total_evaluations'] = Evaluation.query.count() stats['avg_scores'] = db.session.query( Evaluation.player_id, func.avg(Evaluation.overall_score).label('avg_score'), ).group_by(Evaluation.player_id).order_by( func.avg(Evaluation.overall_score).desc()).limit(5).all() stats['top_players'] = [] for row in stats['avg_scores']: p = User.query.get(row.player_id) if p: stats['top_players'].append((p, round(row.avg_score, 1))) return render_template('pages/dashboard.html', user=user, stats=stats)