Files
team-tryouts/routes/users.py
T

580 lines
22 KiB
Python

import os
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, send_file
from flask_login import login_required, current_user
from extensions import db, hash_password
from models import User, ROLES, ESPORT_GAMES, PlayerDisponibility, UserGamertag, GAME_PLATFORMS, Contract, OrgTeam
from werkzeug.utils import secure_filename
from datetime import datetime, timedelta
users_bp = Blueprint('users', __name__, url_prefix='/users')
@users_bp.route('')
@login_required
def list_users():
if current_user.role != 'president':
flash('Only the president can manage users.', 'danger')
return redirect(url_for('main.dashboard'))
users = User.query.order_by(User.role, User.full_name).all()
return render_template('pages/users.html', users=users, roles=ROLES)
@users_bp.route('/<int:user_id>/edit', methods=['GET', 'POST'])
@login_required
def edit_user(user_id):
if current_user.role != 'president':
flash('Only the president can edit users.', 'danger')
return redirect(url_for('main.dashboard'))
user = User.query.get_or_404(user_id)
if request.method == 'POST':
full_name = request.form.get('full_name')
email = request.form.get('email')
phone = request.form.get('phone')
role = request.form.get('role')
is_active = request.form.get('is_active_account') == 'on'
if role not in ROLES:
flash('Invalid role selected.', 'danger')
return render_template('pages/edit_user.html', user=user, roles=ROLES, esport_games=ESPORT_GAMES, game_platforms=GAME_PLATFORMS)
selected_games = request.form.getlist('games')
discord_username = request.form.get('discord_username', '').strip()
league_os_profile = request.form.get('league_os_profile', '').strip()
user.full_name = full_name
user.email = email
user.phone = phone
user.role = role
user.is_active_account = is_active
user.games = ','.join(selected_games) if selected_games else None
user.discord_username = discord_username or None
user.league_os_profile = league_os_profile or None
# Handle gamertags - save or delete based on form input
# First, get all existing gamertags for this user
existing_gamertags = {gt.game: gt for gt in user.gamertags}
for game in selected_games:
gamertag = request.form.get(f'gamertag_{game}', '').strip()
platform = request.form.get(f'platform_{game}', '').strip() if GAME_PLATFORMS.get(game) else None
existing = existing_gamertags.get(game)
if gamertag:
if existing:
existing.gamertag = gamertag
existing.platform = platform
else:
gt = UserGamertag(user_id=user.id, game=game, gamertag=gamertag, platform=platform)
db.session.add(gt)
elif existing:
db.session.delete(existing)
# Remove gamertags for games that are no longer selected
for game in existing_gamertags:
if game not in selected_games:
db.session.delete(existing_gamertags[game])
password = request.form.get('password')
if password:
user.password_hash = hash_password(password)
db.session.commit()
flash(f'User {user.full_name} updated successfully!', 'success')
return redirect(url_for('users.list_users'))
user_gamertags = {gt.game: {'gamertag': gt.gamertag, 'platform': gt.platform} for gt in user.gamertags}
return render_template('pages/edit_user.html', user=user, roles=ROLES, esport_games=ESPORT_GAMES, game_platforms=GAME_PLATFORMS, user_gamertags=user_gamertags)
@users_bp.route('/<int:user_id>/delete', methods=['POST'])
@login_required
def delete_user(user_id):
if current_user.role != 'president':
flash('Only the president can delete users.', 'danger')
return redirect(url_for('main.dashboard'))
if current_user.id == user_id:
flash('You cannot delete your own account.', 'danger')
return redirect(url_for('users.list_users'))
user = User.query.get_or_404(user_id)
db.session.delete(user)
db.session.commit()
flash(f'User {user.full_name} has been removed.', 'success')
return redirect(url_for('users.list_users'))
@users_bp.route('/create', methods=['GET', 'POST'])
@login_required
def create_user():
if current_user.role != 'president':
flash('Only the president can create users.', 'danger')
return redirect(url_for('main.dashboard'))
if request.method == 'POST':
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')
full_name = request.form.get('full_name')
phone = request.form.get('phone')
role = request.form.get('role')
if role not in ROLES:
flash('Invalid role selected.', 'danger')
return render_template('pages/create_user.html', roles=ROLES)
if User.query.filter_by(username=username).first():
flash('Username already exists.', 'danger')
return render_template('pages/create_user.html', roles=ROLES)
if User.query.filter_by(email=email).first():
flash('Email already registered.', 'danger')
return render_template('pages/create_user.html', roles=ROLES)
hashed_password = hash_password(password)
user = User(
username=username,
password_hash=hashed_password,
role=role,
full_name=full_name,
email=email,
phone=phone
)
db.session.add(user)
db.session.commit()
flash(f'User {full_name} created as {role}!', 'success')
return redirect(url_for('users.list_users'))
return render_template('pages/create_user.html', roles=ROLES)
@users_bp.route('/profile')
@login_required
def profile():
# Get contracts ordered by uploaded_at desc for the current user
contracts = None
if current_user.role == 'player':
contracts = Contract.query.filter_by(player_id=current_user.id).order_by(Contract.uploaded_at.desc()).all()
return render_template('pages/profile.html', user=current_user, contracts=contracts)
@users_bp.route('/profile/edit', methods=['GET', 'POST'])
@login_required
def edit_profile():
if request.method == 'POST':
full_name = request.form.get('full_name')
email = request.form.get('email')
phone = request.form.get('phone')
selected_games = request.form.getlist('games')
discord_username = request.form.get('discord_username', '').strip()
league_os_profile = request.form.get('league_os_profile', '').strip()
if email != current_user.email and User.query.filter_by(email=email).first():
flash('Email already in use.', 'danger')
return render_template('pages/edit_profile.html', user=current_user, esport_games=ESPORT_GAMES, game_platforms=GAME_PLATFORMS)
current_user.full_name = full_name
current_user.email = email
current_user.phone = phone
current_user.games = ','.join(selected_games) if selected_games else None
current_user.discord_username = discord_username or None
current_user.league_os_profile = league_os_profile or None
# Handle gamertags - save or delete based on form input
# First, get all existing gamertags for this user
existing_gamertags = {gt.game: gt for gt in current_user.gamertags}
for game in selected_games:
gamertag = request.form.get(f'gamertag_{game}', '').strip()
platform = request.form.get(f'platform_{game}', '').strip() if GAME_PLATFORMS.get(game) else None
existing = existing_gamertags.get(game)
if gamertag:
if existing:
existing.gamertag = gamertag
existing.platform = platform
else:
gt = UserGamertag(user_id=current_user.id, game=game, gamertag=gamertag, platform=platform)
db.session.add(gt)
elif existing:
db.session.delete(existing)
# Remove gamertags for games that are no longer selected
for game in existing_gamertags:
if game not in selected_games:
db.session.delete(existing_gamertags[game])
password = request.form.get('password')
if password:
current_user.password_hash = hash_password(password)
db.session.commit()
flash('Profile updated successfully!', 'success')
return redirect(url_for('users.profile'))
user_gamertags = {gt.game: {'gamertag': gt.gamertag, 'platform': gt.platform} for gt in current_user.gamertags}
return render_template('pages/edit_profile.html', user=current_user, esport_games=ESPORT_GAMES, game_platforms=GAME_PLATFORMS, user_gamertags=user_gamertags)
# Day names for disponibility
DAY_NAMES = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
def add_30_minutes(t):
"""Add 30 minutes to a time object."""
return (datetime.combine(datetime.today(), t) + timedelta(minutes=30)).time()
@users_bp.route('/disponibilities')
@login_required
def get_disponibilities():
"""API endpoint to get all player disponibilities for scheduling."""
# Only managers and above can view disponibilities for scheduling
if not current_user.can_manage_teams() and not current_user.can_schedule_matches():
return jsonify({'error': 'Unauthorized'}), 403
players = User.query.filter_by(role='player', is_active_account=True).order_by(User.full_name).all()
result = {}
for player in players:
disponibilities = list(player.disponibilities)
result[player.id] = {
'full_name': player.full_name,
'disponibilities': [
{
'id': d.id,
'day_of_week': d.day_of_week,
'day_name': DAY_NAMES[d.day_of_week],
'start_time': d.start_time.strftime('%H:%M'),
'end_time': d.end_time.strftime('%H:%M')
}
for d in disponibilities
]
}
return jsonify(result)
@users_bp.route('/disponibilities/my')
@login_required
def get_my_disponibilities():
"""API endpoint for players to get their own disponibilities."""
disponibilities = PlayerDisponibility.query.filter_by(player_id=current_user.id).all()
# Group by day for easier display
result = {}
for d in disponibilities:
day = d.day_of_week
if day not in result:
result[day] = []
result[day].append({
'id': d.id,
'day_of_week': d.day_of_week,
'day_name': DAY_NAMES[d.day_of_week],
'start_time': d.start_time.strftime('%H:%M'),
'end_time': d.end_time.strftime('%H:%M')
})
return jsonify(result)
@users_bp.route('/disponibilities/add', methods=['POST'])
@login_required
def add_disponibility():
"""Add a disponibility block for the current player."""
day_of_week = request.form.get('day_of_week', type=int)
start_time_str = request.form.get('start_time')
if day_of_week is None or day_of_week < 0 or day_of_week > 6:
return jsonify({'error': 'Invalid day of week'}), 400
try:
start_time = datetime.strptime(start_time_str, '%H:%M').time()
except (ValueError, TypeError):
return jsonify({'error': 'Invalid time format'}), 400
end_time = add_30_minutes(start_time)
disponibility = PlayerDisponibility(
player_id=current_user.id,
day_of_week=day_of_week,
start_time=start_time,
end_time=end_time
)
db.session.add(disponibility)
db.session.commit()
return jsonify({
'id': disponibility.id,
'day_of_week': disponibility.day_of_week,
'day_name': DAY_NAMES[disponibility.day_of_week],
'start_time': disponibility.start_time.strftime('%H:%M'),
'end_time': disponibility.end_time.strftime('%H:%M')
})
@users_bp.route('/disponibilities/add_bulk', methods=['POST'])
@login_required
def add_disponibilities_bulk():
"""Add multiple disponibility blocks at once (for grid selection)."""
data = request.get_json()
slots = data.get('slots', []) # List of {day_of_week, start_time}
created = []
for slot in slots:
day_of_week = slot.get('day_of_week')
start_time_str = slot.get('start_time')
if day_of_week is None or day_of_week < 0 or day_of_week > 6:
continue
try:
start_time = datetime.strptime(start_time_str, '%H:%M').time()
except (ValueError, TypeError):
continue
end_time = add_30_minutes(start_time)
# Check if this slot already exists for this player
existing = PlayerDisponibility.query.filter_by(
player_id=current_user.id,
day_of_week=day_of_week,
start_time=start_time
).first()
if not existing:
disponibility = PlayerDisponibility(
player_id=current_user.id,
day_of_week=day_of_week,
start_time=start_time,
end_time=end_time
)
db.session.add(disponibility)
db.session.flush()
created.append({
'id': disponibility.id,
'day_of_week': disponibility.day_of_week,
'day_name': DAY_NAMES[disponibility.day_of_week],
'start_time': disponibility.start_time.strftime('%H:%M'),
'end_time': disponibility.start_time.strftime('%H:%M')
})
db.session.commit()
return jsonify({'success': True, 'created': created})
@users_bp.route('/disponibilities/clear', methods=['POST'])
@login_required
def clear_disponibilities():
"""Clear all disponibilities for the current player (for resetting)."""
PlayerDisponibility.query.filter_by(player_id=current_user.id).delete()
db.session.commit()
return jsonify({'success': True})
@users_bp.route('/disponibilities/<int:disponibility_id>/delete', methods=['POST'])
@login_required
def delete_disponibility(disponibility_id):
"""Delete a disponibility block."""
disponibility = PlayerDisponibility.query.get_or_404(disponibility_id)
# Only the owner can delete their disponibility
if disponibility.player_id != current_user.id:
return jsonify({'error': 'Unauthorized'}), 403
db.session.delete(disponibility)
db.session.commit()
return jsonify({'success': True})
# Contract Dropbox Functions
def can_manage_player_contract(user, player_id):
"""Check if a user can upload contracts for a specific player."""
# President can manage all contracts
if user.role == 'president':
return True
# Manager can upload contracts for any player
if user.role == 'manager':
return True
# Coach can upload contracts only for players on their team
if user.role == 'coach':
org_team = OrgTeam.query.filter_by(coach_id=user.id).first()
if org_team:
player = User.query.get(player_id)
if player and player.team_id == org_team.id:
return True
return False
@users_bp.route('/contracts')
@login_required
def list_contracts():
"""View contracts for the current user (player) or players they manage."""
contracts = None
if current_user.role == 'player':
# Players see their own contracts
contracts = Contract.query.filter_by(player_id=current_user.id).order_by(Contract.uploaded_at.desc()).all()
elif current_user.role in ['president', 'manager', 'coach']:
# Superiors see contracts for players on their teams
players = []
if current_user.role == 'coach':
org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first()
if org_team:
players = User.query.filter_by(role='player', team_id=org_team.id).all()
else: # president or manager
players = User.query.filter_by(role='player').all()
if players:
player_ids = [p.id for p in players]
contracts = Contract.query.filter(Contract.player_id.in_(player_ids)).order_by(Contract.uploaded_at.desc()).all()
return render_template('pages/contracts.html', contracts=contracts, players=players if current_user.role in ['president', 'manager', 'coach'] else None)
@users_bp.route('/contracts/upload', methods=['GET', 'POST'])
@login_required
def upload_contract():
"""Upload a contract for a player."""
if current_user.role not in ['president', 'manager', 'coach']:
flash('Only presidents, managers, and coaches can upload contracts.', 'danger')
return redirect(url_for('users.list_contracts'))
# Get players this user can manage
if current_user.role == 'coach':
org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first()
players = User.query.filter_by(role='player', team_id=org_team.id).all() if org_team else []
else:
players = User.query.filter_by(role='player').all()
if request.method == 'POST':
player_id = request.form.get('player_id', type=int)
notes = request.form.get('notes', '').strip()
if not can_manage_player_contract(current_user, player_id):
flash('You do not have permission to upload a contract for this player.', 'danger')
return redirect(url_for('users.upload_contract'))
if 'contract_file' not in request.files:
flash('No file selected.', 'danger')
return redirect(url_for('users.upload_contract'))
file = request.files['contract_file']
if file.filename == '':
flash('No file selected.', 'danger')
return redirect(url_for('users.upload_contract'))
# Create upload directory
upload_dir = os.path.join(os.getcwd(), 'documents', 'contrats signés')
os.makedirs(upload_dir, exist_ok=True)
# Get player info for folder structure
player = User.query.get_or_404(player_id)
team = OrgTeam.query.get(player.team_id) if player.team_id else None
# Create team folder if team exists
if team:
team_folder = os.path.join(upload_dir, secure_filename(team.name))
os.makedirs(team_folder, exist_ok=True)
final_dir = team_folder
else:
final_dir = upload_dir
# Generate unique filename
original_filename = secure_filename(file.filename)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
stored_filename = f"{secure_filename(player.full_name)}_{timestamp}_{original_filename}"
stored_filename = stored_filename.replace(' ', '_')
# Save file
file_path = os.path.join(final_dir, stored_filename)
file.save(file_path)
# Create database record
contract = Contract(
player_id=player_id,
team_id=player.team_id,
uploaded_by_id=current_user.id,
original_filename=original_filename,
stored_filename=stored_filename,
file_path=file_path,
notes=notes if notes else None
)
db.session.add(contract)
db.session.commit()
flash(f'Contract uploaded successfully for {player.full_name}!', 'success')
return redirect(url_for('users.list_contracts'))
return render_template('pages/upload_contract.html', players=players)
@users_bp.route('/contracts/<int:contract_id>/upload_signed', methods=['POST'])
@login_required
def upload_signed_contract(contract_id):
"""Upload a signed contract."""
contract = Contract.query.get_or_404(contract_id)
if not contract.can_upload_signed(current_user):
flash('Only the player can upload their signed contract.', 'danger')
return redirect(url_for('users.list_contracts'))
if 'signed_file' not in request.files:
flash('No file selected.', 'danger')
return redirect(url_for('users.list_contracts'))
file = request.files['signed_file']
if file.filename == '':
flash('No file selected.', 'danger')
return redirect(url_for('users.list_contracts'))
# Generate unique filename for signed contract
original_filename = secure_filename(file.filename)
signed_filename = f"signed_{contract.stored_filename}"
# Save file
file.save(contract.file_path.replace(contract.stored_filename, signed_filename))
# Update contract record
contract.signed_filename = signed_filename
contract.signed_file_path = contract.file_path.replace(contract.stored_filename, signed_filename)
contract.status = 'signed'
contract.signed_at = datetime.utcnow()
db.session.commit()
flash('Signed contract uploaded successfully!', 'success')
return redirect(url_for('users.list_contracts'))
@users_bp.route('/contracts/<int:contract_id>/download')
@login_required
def download_contract(contract_id):
"""Download a contract file."""
contract = Contract.query.get_or_404(contract_id)
if not contract.can_view(current_user):
flash('You do not have permission to download this contract.', 'danger')
return redirect(url_for('users.list_contracts'))
return send_file(contract.file_path, as_attachment=True, download_name=contract.original_filename)
@users_bp.route('/contracts/<int:contract_id>/download_signed')
@login_required
def download_signed_contract(contract_id):
"""Download a signed contract file."""
contract = Contract.query.get_or_404(contract_id)
if not contract.can_view(current_user):
flash('You do not have permission to download this contract.', 'danger')
return redirect(url_for('users.list_contracts'))
if not contract.signed_file_path:
flash('No signed contract available.', 'danger')
return redirect(url_for('users.list_contracts'))
return send_file(contract.signed_file_path, as_attachment=True, download_name=contract.signed_filename)