434 lines
19 KiB
Python
434 lines
19 KiB
Python
"""Tryout management routes for creating, viewing, and managing tryout events.
|
|
|
|
This module handles CRUD operations for tryouts and player registrations.
|
|
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 app.extensions import db
|
|
from app.models import (
|
|
Admin, Manager, Coach, Player, Scout,
|
|
User, Tryout, TryoutRegistration, Evaluation, Team, TeamMember,
|
|
OrgTeam, Match, MatchParticipant,
|
|
ESPORT_GAMES, GAME_POSITIONS,
|
|
)
|
|
from datetime import datetime
|
|
|
|
tryouts_bp = Blueprint('tryouts', __name__, url_prefix='/tryouts')
|
|
|
|
|
|
def can_manage():
|
|
"""Check if current user can manage tryouts (Admin or Manager)."""
|
|
return isinstance(current_user, (Admin, Manager))
|
|
|
|
|
|
@tryouts_bp.route('')
|
|
@login_required
|
|
def list_tryouts():
|
|
"""List all tryouts visible to the current user.
|
|
|
|
Delegates to the polymorphic User subclass's get_visible_tryouts() method.
|
|
"""
|
|
tryouts = current_user.get_visible_tryouts()
|
|
return render_template('pages/tryouts.html', tryouts=tryouts, now=datetime.utcnow())
|
|
|
|
|
|
@tryouts_bp.route('/create', methods=['GET', 'POST'])
|
|
@login_required
|
|
def create_tryout():
|
|
"""Create a new tryout event. Requires Admin or Manager."""
|
|
if not can_manage():
|
|
flash('You do not have permission to create tryouts.', 'danger')
|
|
return redirect(url_for('tryouts.list_tryouts'))
|
|
|
|
org_teams = OrgTeam.query.order_by(OrgTeam.name).all()
|
|
managers = User.query.filter_by(role='manager', is_active_account=True).order_by(User.username).all()
|
|
coaches = User.query.filter_by(role='coach', is_active_account=True).order_by(User.username).all()
|
|
|
|
if request.method == 'POST':
|
|
title = request.form.get('title')
|
|
description = request.form.get('description')
|
|
game = request.form.get('game')
|
|
date_str = request.form.get('date')
|
|
location = request.form.get('location')
|
|
max_players = request.form.get('max_players')
|
|
target_org_team_id = request.form.get('target_org_team_id')
|
|
manager_id = request.form.get('manager_id')
|
|
coach_id = request.form.get('coach_id')
|
|
|
|
try:
|
|
date_obj = datetime.strptime(date_str, '%Y-%m-%d').date()
|
|
except (ValueError, TypeError):
|
|
flash('Invalid date format.', 'danger')
|
|
return render_template('pages/tryout_form.html', tryout=None, org_teams=org_teams,
|
|
managers=managers, coaches=coaches, esport_games=ESPORT_GAMES)
|
|
|
|
tryout = Tryout(
|
|
title=title, description=description, game=game, date=date_obj,
|
|
location=location,
|
|
max_players=int(max_players) if max_players else None,
|
|
created_by=current_user.id, status='upcoming',
|
|
target_org_team_id=int(target_org_team_id) if target_org_team_id else None,
|
|
manager_id=int(manager_id) if manager_id else None,
|
|
coach_id=int(coach_id) if coach_id else None,
|
|
)
|
|
db.session.add(tryout)
|
|
db.session.commit()
|
|
flash('Tryout created successfully!', 'success')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout.id))
|
|
|
|
return render_template('pages/tryout_form.html', tryout=None, org_teams=org_teams,
|
|
managers=managers, coaches=coaches, esport_games=ESPORT_GAMES)
|
|
|
|
|
|
@tryouts_bp.route('/<int:tryout_id>/edit', methods=['GET', 'POST'])
|
|
@login_required
|
|
def edit_tryout(tryout_id):
|
|
"""Edit an existing tryout event. Permission based on can_manage_this_tryout."""
|
|
tryout = Tryout.query.get_or_404(tryout_id)
|
|
|
|
if not current_user.can_manage_this_tryout(tryout):
|
|
flash('You do not have permission to edit this tryout.', 'danger')
|
|
return redirect(url_for('tryouts.list_tryouts'))
|
|
|
|
org_teams = OrgTeam.query.order_by(OrgTeam.name).all()
|
|
managers = User.query.filter_by(role='manager', is_active_account=True).order_by(User.full_name).all()
|
|
coaches = User.query.filter_by(role='coach', is_active_account=True).order_by(User.full_name).all()
|
|
|
|
if request.method == 'POST':
|
|
title = request.form.get('title')
|
|
description = request.form.get('description')
|
|
game = request.form.get('game')
|
|
date_str = request.form.get('date')
|
|
location = request.form.get('location')
|
|
max_players = request.form.get('max_players')
|
|
target_org_team_id = request.form.get('target_org_team_id')
|
|
manager_id = request.form.get('manager_id')
|
|
coach_id = request.form.get('coach_id')
|
|
|
|
try:
|
|
date_obj = datetime.strptime(date_str, '%Y-%m-%d').date()
|
|
except (ValueError, TypeError):
|
|
flash('Invalid date format.', 'danger')
|
|
return render_template('pages/tryout_form.html', tryout=tryout, org_teams=org_teams,
|
|
managers=managers, coaches=coaches, esport_games=ESPORT_GAMES)
|
|
|
|
tryout.title = title
|
|
tryout.description = description
|
|
tryout.game = game
|
|
tryout.date = date_obj
|
|
tryout.location = location
|
|
tryout.max_players = int(max_players) if max_players else None
|
|
tryout.target_org_team_id = int(target_org_team_id) if target_org_team_id else None
|
|
tryout.manager_id = int(manager_id) if manager_id else None
|
|
tryout.coach_id = int(coach_id) if coach_id else None
|
|
db.session.commit()
|
|
flash('Tryout updated successfully!', 'success')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout.id))
|
|
|
|
return render_template('pages/tryout_form.html', tryout=tryout, org_teams=org_teams,
|
|
managers=managers, coaches=coaches, esport_games=ESPORT_GAMES)
|
|
|
|
|
|
@tryouts_bp.route('/<int:tryout_id>')
|
|
@login_required
|
|
def view_tryout(tryout_id):
|
|
"""View a specific tryout with all details. Permission via polymorphic dispatch."""
|
|
tryout = Tryout.query.get_or_404(tryout_id)
|
|
|
|
can_view = False
|
|
if isinstance(current_user, Admin):
|
|
can_view = True
|
|
elif isinstance(current_user, Manager) and tryout.created_by == current_user.id:
|
|
can_view = True
|
|
elif isinstance(current_user, Coach):
|
|
org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first()
|
|
if org_team and tryout.target_org_team_id == org_team.id:
|
|
can_view = True
|
|
elif isinstance(current_user, Player):
|
|
is_registered = TryoutRegistration.query.filter_by(
|
|
tryout_id=tryout_id, player_id=current_user.id).first() is not None
|
|
player_in_match = MatchParticipant.query.join(Match).filter(
|
|
MatchParticipant.player_id == current_user.id,
|
|
Match.tryout_id == tryout_id,
|
|
).first() is not None
|
|
can_view = is_registered or player_in_match
|
|
elif isinstance(current_user, Scout):
|
|
can_view = True
|
|
|
|
if not can_view:
|
|
flash('You do not have permission to view this tryout.', 'danger')
|
|
return redirect(url_for('tryouts.list_tryouts'))
|
|
|
|
registrations = TryoutRegistration.query.filter_by(tryout_id=tryout_id).all()
|
|
registered_players = [User.query.get(r.player_id) for r in registrations if r.player_id]
|
|
evaluations = Evaluation.query.filter_by(tryout_id=tryout_id).all()
|
|
|
|
player_eval_status = {}
|
|
if current_user.can_evaluate():
|
|
for p in registered_players:
|
|
existing = Evaluation.query.filter_by(
|
|
tryout_id=tryout_id, player_id=p.id, evaluator_id=current_user.id,
|
|
).first()
|
|
player_eval_status[p.id] = existing is not None
|
|
|
|
is_registered = TryoutRegistration.query.filter_by(
|
|
tryout_id=tryout_id, player_id=current_user.id,
|
|
).first() is not None
|
|
|
|
teams = Team.query.filter_by(tryout_id=tryout_id).all()
|
|
team_data = []
|
|
for team in teams:
|
|
members = TeamMember.query.filter_by(team_id=team.id).all()
|
|
team_data.append({
|
|
'team': team,
|
|
'members': [{'player': User.query.get(m.player_id), 'position': m.position}
|
|
for m in members],
|
|
})
|
|
|
|
can_edit = current_user.can_manage_this_tryout(tryout)
|
|
|
|
can_view_calendar = can_edit
|
|
if isinstance(current_user, Player):
|
|
player_in_match = MatchParticipant.query.join(Match).filter(
|
|
MatchParticipant.player_id == current_user.id,
|
|
Match.tryout_id == tryout_id,
|
|
).first() is not None
|
|
can_view_calendar = is_registered or player_in_match
|
|
|
|
all_players = None
|
|
if can_edit:
|
|
all_players = User.query.filter_by(role='player').order_by(User.username).all()
|
|
|
|
matches = Match.query.filter_by(tryout_id=tryout_id).order_by(Match.date, Match.start_time).all()
|
|
match_data = []
|
|
for match in matches:
|
|
all_participants = list(match.participants.all())
|
|
confirmed_count = sum(1 for p in all_participants if p.attendance_confirmed)
|
|
total_count = len(all_participants)
|
|
|
|
player_presence = []
|
|
for p in all_participants:
|
|
if p.player:
|
|
player_presence.append({
|
|
'participant_id': p.id, 'player_id': p.player_id,
|
|
'player_name': p.player.username,
|
|
'attendance_confirmed': p.attendance_confirmed,
|
|
})
|
|
|
|
if match.match_type == 'team_vs_team':
|
|
participants = {
|
|
'team1': match.team1.name if match.team1 else 'TBD',
|
|
'team2': match.team2.name if match.team2 else 'TBD',
|
|
'team1_players': [{'name': m.player.username, 'position': m.position}
|
|
for m in match.team1.members.all()] if match.team1 else [],
|
|
'team2_players': [{'name': m.player.username, 'position': m.position}
|
|
for m in match.team2.members.all()] if match.team2 else [],
|
|
}
|
|
elif match.match_type == 'player_vs_player':
|
|
team1_players = [{'name': p.player.username, 'position': p.position}
|
|
for p in match.participants.filter_by(team_side=1).all() if p.player]
|
|
team2_players = [{'name': p.player.username, 'position': p.position}
|
|
for p in match.participants.filter_by(team_side=2).all() if p.player]
|
|
participants = {
|
|
'team1': 'Team 1', 'team2': 'Team 2',
|
|
'team1_players': team1_players, 'team2_players': team2_players,
|
|
}
|
|
else:
|
|
participants = [p.player.username for p in match.participants.all()]
|
|
|
|
match_data.append({
|
|
'match': match, 'participants': participants,
|
|
'confirmed_count': confirmed_count, 'total_count': total_count,
|
|
'player_presence': player_presence,
|
|
})
|
|
|
|
return render_template('pages/view_tryout.html',
|
|
tryout=tryout, registered_players=registered_players,
|
|
evaluations=evaluations, player_eval_status=player_eval_status,
|
|
is_registered=is_registered, registrations=registrations,
|
|
team_data=team_data, can_edit=can_edit,
|
|
can_view_calendar=can_view_calendar, all_players=all_players,
|
|
matches=matches, match_data=match_data,
|
|
game_positions=GAME_POSITIONS, now=datetime.utcnow())
|
|
|
|
|
|
@tryouts_bp.route('/<int:tryout_id>/register', methods=['POST'])
|
|
@login_required
|
|
def register_for_tryout(tryout_id):
|
|
"""Register a player for a tryout. Only Players can self-register."""
|
|
tryout = Tryout.query.get_or_404(tryout_id)
|
|
if not isinstance(current_user, Player):
|
|
flash('Only players can register for tryouts.', 'danger')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
|
|
|
if tryout.status not in ['upcoming', 'in_progress']:
|
|
flash('This tryout is not accepting registrations.', 'danger')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
|
|
|
existing = TryoutRegistration.query.filter_by(
|
|
tryout_id=tryout_id, player_id=current_user.id).first()
|
|
if existing:
|
|
flash('You are already registered for this tryout.', 'info')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
|
|
|
if tryout.max_players:
|
|
count = TryoutRegistration.query.filter_by(tryout_id=tryout_id).count()
|
|
if count >= tryout.max_players:
|
|
flash('This tryout is full.', 'danger')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
|
|
|
registration = TryoutRegistration(tryout_id=tryout_id, player_id=current_user.id)
|
|
db.session.add(registration)
|
|
db.session.commit()
|
|
flash('Successfully registered for tryout!', 'success')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
|
|
|
|
|
@tryouts_bp.route('/<int:tryout_id>/status', methods=['POST'])
|
|
@login_required
|
|
def update_status(tryout_id):
|
|
"""Update the status of a tryout."""
|
|
tryout = Tryout.query.get_or_404(tryout_id)
|
|
if not current_user.can_manage_this_tryout(tryout):
|
|
flash('Permission denied.', 'danger')
|
|
return redirect(url_for('tryouts.list_tryouts'))
|
|
new_status = request.form.get('status')
|
|
if new_status in ['upcoming', 'in_progress', 'completed']:
|
|
tryout.status = new_status
|
|
db.session.commit()
|
|
flash(f'Tryout status updated to {new_status}.', 'success')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
|
|
|
|
|
@tryouts_bp.route('/<int:tryout_id>/registration/<int:player_id>/status', methods=['POST'])
|
|
@login_required
|
|
def update_registration_status(tryout_id, player_id):
|
|
"""Update a registration's attendance status."""
|
|
tryout = Tryout.query.get_or_404(tryout_id)
|
|
if not current_user.can_manage_this_tryout(tryout):
|
|
flash('Permission denied.', 'danger')
|
|
return redirect(url_for('tryouts.list_tryouts'))
|
|
|
|
registration = TryoutRegistration.query.filter_by(
|
|
tryout_id=tryout_id, player_id=player_id).first_or_404()
|
|
new_status = request.form.get('status')
|
|
if new_status in ['registered', 'attended', 'no_show']:
|
|
registration.status = new_status
|
|
db.session.commit()
|
|
flash('Registration status updated.', 'success')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
|
|
|
|
|
@tryouts_bp.route('/<int:tryout_id>/register_player', methods=['POST'])
|
|
@login_required
|
|
def register_player(tryout_id):
|
|
"""Manually register a player for a tryout (by managers/coaches)."""
|
|
tryout = Tryout.query.get_or_404(tryout_id)
|
|
if not current_user.can_manage_this_tryout(tryout):
|
|
flash('Permission denied.', 'danger')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
|
player_id = request.form.get('player_id')
|
|
if not player_id:
|
|
flash('Please select a player.', 'danger')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
|
|
|
player = User.query.get_or_404(int(player_id))
|
|
if not isinstance(player, Player):
|
|
flash('Can only register players.', 'danger')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
|
|
|
existing = TryoutRegistration.query.filter_by(
|
|
tryout_id=tryout_id, player_id=player.id).first()
|
|
if existing:
|
|
flash(f'{player.username} is already registered for this tryout.', 'info')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
|
|
|
if tryout.max_players:
|
|
count = TryoutRegistration.query.filter_by(tryout_id=tryout_id).count()
|
|
if count >= tryout.max_players:
|
|
flash('This tryout is full.', 'danger')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
|
|
|
registration = TryoutRegistration(tryout_id=tryout_id, player_id=player.id)
|
|
db.session.add(registration)
|
|
db.session.commit()
|
|
flash(f'{player.username} registered for tryout!', 'success')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
|
|
|
|
|
@tryouts_bp.route('/<int:tryout_id>/remove_player/<int:player_id>', methods=['POST'])
|
|
@login_required
|
|
def remove_player(tryout_id, player_id):
|
|
"""Remove a registered player from a tryout (cascades to teams/matches)."""
|
|
tryout = Tryout.query.get_or_404(tryout_id)
|
|
if not current_user.can_manage_this_tryout(tryout):
|
|
flash('Permission denied.', 'danger')
|
|
return redirect(url_for('tryouts.list_tryouts'))
|
|
|
|
player = User.query.get_or_404(player_id)
|
|
|
|
registration = TryoutRegistration.query.filter_by(
|
|
tryout_id=tryout_id, player_id=player_id).first()
|
|
if registration:
|
|
db.session.delete(registration)
|
|
|
|
team_ids = [t.id for t in Team.query.filter_by(tryout_id=tryout_id).all()]
|
|
if team_ids:
|
|
TeamMember.query.filter(
|
|
TeamMember.team_id.in_(team_ids),
|
|
TeamMember.player_id == player_id,
|
|
).delete(synchronize_session=False)
|
|
|
|
match_ids = [m.id for m in Match.query.filter_by(tryout_id=tryout_id).all()]
|
|
if match_ids:
|
|
MatchParticipant.query.filter(
|
|
MatchParticipant.match_id.in_(match_ids),
|
|
MatchParticipant.player_id == player_id,
|
|
).delete(synchronize_session=False)
|
|
|
|
db.session.commit()
|
|
flash(f'{player.username} removed from tryout.', 'success')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
|
|
|
|
|
@tryouts_bp.route('/<int:tryout_id>/team/create', methods=['POST'])
|
|
@login_required
|
|
def create_team(tryout_id):
|
|
"""Create a tryout-specific team."""
|
|
tryout = Tryout.query.get_or_404(tryout_id)
|
|
if not current_user.can_manage_this_tryout(tryout):
|
|
flash('Permission denied.', 'danger')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
|
|
|
team_name = request.form.get('team_name')
|
|
if team_name:
|
|
team = Team(tryout_id=tryout_id, name=team_name, created_by=current_user.id)
|
|
db.session.add(team)
|
|
db.session.commit()
|
|
flash(f'Team "{team_name}" created!', 'success')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
|
|
|
|
|
@tryouts_bp.route('/<int:tryout_id>/team/<int:team_id>/add', methods=['POST'])
|
|
@login_required
|
|
def add_to_team(tryout_id, team_id):
|
|
"""Add a player to a tryout team."""
|
|
team = Team.query.get_or_404(team_id)
|
|
tryout = Tryout.query.get_or_404(tryout_id)
|
|
if not current_user.can_manage_this_tryout(tryout):
|
|
flash('Permission denied.', 'danger')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
|
|
|
player_id = request.form.get('player_id')
|
|
position = request.form.get('position', '')
|
|
existing = TeamMember.query.filter_by(team_id=team_id, player_id=player_id).first()
|
|
if existing:
|
|
flash('Player is already on this team.', 'info')
|
|
else:
|
|
member = TeamMember(team_id=team_id, player_id=int(player_id), position=position)
|
|
db.session.add(member)
|
|
db.session.commit()
|
|
flash('Player added to team!', 'success')
|
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id)) |