"""Organization team management routes. Uses polymorphic isinstance checks instead of role-string comparisons. """ from datetime import datetime from flask import Blueprint, flash, jsonify, redirect, render_template, request, url_for from flask_babel import gettext as _ from flask_login import current_user, login_required from marshmallow import ValidationError from app.api import json_endpoint from app.extensions import db from app.forms import flash_validation_errors, form_payload from app.models import ( Admin, Coach, Contract, Manager, OneOnOneRequest, OrgTeam, PersonalNote, Player, TeamMatch, TeamNote, TeamPlayer, Tryout, User, ) from app.permissions import visible_org_teams from app.validators import OrgTeamSchema teams_bp = Blueprint('teams', __name__, url_prefix='/teams') @teams_bp.route('') @login_required def list_teams(): """List all organization teams visible to the current user.""" can_manage = current_user.can_manage_teams() if isinstance(current_user, Player): flash(_('Use My Team(s) to view your teams.'), 'info') return redirect(url_for('teams.my_teams')) if not isinstance(current_user, (Admin, Coach, Manager)): flash(_('You do not have permission to view teams.'), 'danger') return redirect(url_for('main.dashboard')) teams = visible_org_teams(current_user) coaches = ( User.query.filter_by(role='coach', is_active_account=True).order_by(User.username).all() ) managers = ( User.query.filter_by(role='manager', is_active_account=True).order_by(User.username).all() ) all_players = User.query.filter_by(role='player').order_by(User.username).all() return render_template( 'pages/teams.html', teams=teams, coaches=coaches, managers=managers, all_players=all_players, can_manage=can_manage, ) @teams_bp.route('/my-teams') @login_required def my_teams(): """View the player's own teams with upcoming matches.""" if not isinstance(current_user, Player): flash(_('This page is for players.'), 'info') return redirect(url_for('teams.list_teams')) from app.models import TeamMatch, TeamMatchParticipant player_teams = current_user.get_org_teams() now = datetime.utcnow() team_data = [] for org_team in player_teams: matches = ( TeamMatch.query.filter( TeamMatch.org_team_id == org_team.id, TeamMatch.status == 'scheduled', ) .order_by(TeamMatch.date.asc(), TeamMatch.start_time.asc()) .all() ) matches_data = [] for tm in matches: confirmed, total = tm.get_confirmed_count() participant = TeamMatchParticipant.query.filter_by( team_match_id=tm.id, player_id=current_user.id, ).first() matches_data.append( { 'match': tm, 'participant_id': participant.id if participant else None, 'is_confirmed': participant.is_confirmed if participant else False, 'confirmed_count': confirmed, 'total_count': total, } ) team_data.append( { 'team': org_team, 'matches': matches_data, 'coaches': org_team.get_coaches(), 'managers': org_team.get_managers(), } ) return render_template('pages/my_teams.html', team_data=team_data, now=now) def _staff_member(user_id, expected_class): """The user behind an id, only if they hold the role being assigned. Returns None for a missing id, an unknown id, or an account of the wrong role. That last case is the point (SEC-16): the id comes from a `