Ajout d'une catégorie jeux dans le tryouts, chacun des jeux offre des rôles différents pour les membres d'équipes. Remodelage du code un peu et ajout de docu

This commit is contained in:
cedrick2711
2026-07-15 21:27:18 -04:00
parent a129f218b6
commit 195f3ce312
29 changed files with 1501 additions and 269 deletions
+202 -62
View File
@@ -1,3 +1,9 @@
"""User management routes for profiles, disponibilities, and contracts.
This module handles user CRUD operations, profile editing, player availability,
and contract management.
"""
import os
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, send_file
from flask_login import login_required, current_user
@@ -6,11 +12,54 @@ from models import User, ROLES, ESPORT_GAMES, PlayerDisponibility, UserGamertag,
from werkzeug.utils import secure_filename
from datetime import datetime, timedelta
users_bp = Blueprint('users', __name__, url_prefix='/users')
def update_user_gamertags(user, selected_games):
"""Update gamertags for a user based on form input.
Handles creating, updating, and deleting gamertag records for the specified games.
Used by both edit_user and edit_profile routes to avoid code duplication.
Args:
user: The User object to update gamertags for.
selected_games: List of game names that were selected in the form.
"""
# 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])
@users_bp.route('')
@login_required
def list_users():
"""List all users for management (president only).
Displays all users ordered by role and name. Only accessible to presidents.
Returns:
Response: Rendered users list template or redirect to dashboard.
"""
if current_user.role != 'president':
flash('Only the president can manage users.', 'danger')
return redirect(url_for('main.dashboard'))
@@ -18,9 +67,21 @@ def list_users():
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):
"""Edit an existing user (president only).
GET: Render the user edit form.
POST: Update user details including gamertags and password.
Args:
user_id: The ID of the user to edit.
Returns:
Response: Edit form or redirect to users list.
"""
if current_user.role != 'president':
flash('Only the president can edit users.', 'danger')
return redirect(url_for('main.dashboard'))
@@ -51,29 +112,8 @@ def edit_user(user_id):
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])
# Update gamertags using shared function
update_user_gamertags(user, selected_games)
password = request.form.get('password')
if password:
@@ -86,9 +126,18 @@ def edit_user(user_id):
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):
"""Delete a user (president only).
Args:
user_id: The ID of the user to delete.
Returns:
Response: Redirect to users list with status message.
"""
if current_user.role != 'president':
flash('Only the president can delete users.', 'danger')
return redirect(url_for('main.dashboard'))
@@ -103,9 +152,18 @@ def delete_user(user_id):
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():
"""Create a new user (president only).
GET: Render the user creation form.
POST: Create a new user with the provided details.
Returns:
Response: Create form or redirect to users list.
"""
if current_user.role != 'president':
flash('Only the president can create users.', 'danger')
return redirect(url_for('main.dashboard'))
@@ -146,18 +204,35 @@ def create_user():
return render_template('pages/create_user.html', roles=ROLES)
@users_bp.route('/profile')
@login_required
def profile():
"""View the current user's profile.
Players see their contracts along with their profile information.
Returns:
Response: Rendered profile template.
"""
# 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():
"""Edit the current user's profile.
GET: Render the profile edit form.
POST: Update profile details including gamertags and password.
Returns:
Response: Edit form or redirect to profile.
"""
if request.method == 'POST':
full_name = request.form.get('full_name')
email = request.form.get('email')
@@ -178,29 +253,8 @@ def edit_profile():
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])
# Update gamertags using shared function
update_user_gamertags(current_user, selected_games)
password = request.form.get('password')
if password:
@@ -214,19 +268,32 @@ def edit_profile():
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 for disponibility display
DAY_NAMES = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
def add_30_minutes(t):
"""Add 30 minutes to a time object."""
"""Add 30 minutes to a time object.
Args:
t (datetime.time): The time object to add 30 minutes to.
Returns:
datetime.time: New time 30 minutes later.
"""
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."""
"""API endpoint to get all player disponibilities for scheduling.
Only accessible to managers, coaches, and scouts. Used for match scheduling.
Returns:
Response: JSON with disponibility data for all players.
"""
# 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
@@ -256,7 +323,11 @@ def get_disponibilities():
@users_bp.route('/disponibilities/my')
@login_required
def get_my_disponibilities():
"""API endpoint for players to get their own disponibilities."""
"""API endpoint for players to get their own disponibilities.
Returns:
Response: JSON with disponibility data grouped by day.
"""
disponibilities = PlayerDisponibility.query.filter_by(player_id=current_user.id).all()
# Group by day for easier display
@@ -279,7 +350,11 @@ def get_my_disponibilities():
@users_bp.route('/disponibilities/add', methods=['POST'])
@login_required
def add_disponibility():
"""Add a disponibility block for the current player."""
"""Add a disponibility block for the current player.
Returns:
Response: JSON with the created disponibility data.
"""
day_of_week = request.form.get('day_of_week', type=int)
start_time_str = request.form.get('start_time')
@@ -315,7 +390,14 @@ def add_disponibility():
@users_bp.route('/disponibilities/add_bulk', methods=['POST'])
@login_required
def add_disponibilities_bulk():
"""Add multiple disponibility blocks at once (for grid selection)."""
"""Add multiple disponibility blocks at once (for grid selection).
Used for the disponibility grid UI where players can select multiple
time slots at once.
Returns:
Response: JSON with success status and created slots.
"""
data = request.get_json()
slots = data.get('slots', []) # List of {day_of_week, start_time}
@@ -365,7 +447,11 @@ def add_disponibilities_bulk():
@users_bp.route('/disponibilities/clear', methods=['POST'])
@login_required
def clear_disponibilities():
"""Clear all disponibilities for the current player (for resetting)."""
"""Clear all disponibilities for the current player (for resetting).
Returns:
Response: JSON with success status.
"""
PlayerDisponibility.query.filter_by(player_id=current_user.id).delete()
db.session.commit()
return jsonify({'success': True})
@@ -374,7 +460,14 @@ def clear_disponibilities():
@users_bp.route('/disponibilities/<int:disponibility_id>/delete', methods=['POST'])
@login_required
def delete_disponibility(disponibility_id):
"""Delete a disponibility block."""
"""Delete a disponibility block.
Args:
disponibility_id: The ID of the disponibility to delete.
Returns:
Response: JSON with success status or error.
"""
disponibility = PlayerDisponibility.query.get_or_404(disponibility_id)
# Only the owner can delete their disponibility
@@ -387,10 +480,22 @@ def delete_disponibility(disponibility_id):
return jsonify({'success': True})
# Contract Dropbox Functions
# Contract Management Functions
def can_manage_player_contract(user, player_id):
"""Check if a user can upload contracts for a specific player."""
"""Check if a user can upload contracts for a specific player.
Presidents and managers can manage all contracts. Coaches can only
manage contracts for players on their team.
Args:
user: The User requesting to manage contracts.
player_id: The ID of the player whose contract is being managed.
Returns:
bool: True if user has permission to manage the contract.
"""
# President can manage all contracts
if user.role == 'president':
return True
@@ -412,7 +517,14 @@ def can_manage_player_contract(user, player_id):
@users_bp.route('/contracts')
@login_required
def list_contracts():
"""View contracts for the current user (player) or players they manage."""
"""View contracts for the current user (player) or players they manage.
Players see their own contracts. Coaches/managers see contracts for
players on their teams. Presidents see all contracts.
Returns:
Response: Rendered contracts list template.
"""
contracts = None
if current_user.role == 'player':
@@ -438,7 +550,14 @@ def list_contracts():
@users_bp.route('/contracts/upload', methods=['GET', 'POST'])
@login_required
def upload_contract():
"""Upload a contract for a player."""
"""Upload a contract for a player.
GET: Render the contract upload form.
POST: Save the uploaded contract file and create database record.
Returns:
Response: Upload form or redirect to contracts list.
"""
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'))
@@ -515,7 +634,14 @@ def upload_contract():
@users_bp.route('/contracts/<int:contract_id>/upload_signed', methods=['POST'])
@login_required
def upload_signed_contract(contract_id):
"""Upload a signed contract."""
"""Upload a signed contract (player only).
Args:
contract_id: The ID of the contract to upload signed version for.
Returns:
Response: Redirect to contracts list with status message.
"""
contract = Contract.query.get_or_404(contract_id)
if not contract.can_upload_signed(current_user):
@@ -552,7 +678,14 @@ def upload_signed_contract(contract_id):
@users_bp.route('/contracts/<int:contract_id>/download')
@login_required
def download_contract(contract_id):
"""Download a contract file."""
"""Download a contract file.
Args:
contract_id: The ID of the contract to download.
Returns:
Response: File download response.
"""
contract = Contract.query.get_or_404(contract_id)
if not contract.can_view(current_user):
@@ -565,7 +698,14 @@ def download_contract(contract_id):
@users_bp.route('/contracts/<int:contract_id>/download_signed')
@login_required
def download_signed_contract(contract_id):
"""Download a signed contract file."""
"""Download a signed contract file.
Args:
contract_id: The ID of the contract to download the signed version for.
Returns:
Response: File download response.
"""
contract = Contract.query.get_or_404(contract_id)
if not contract.can_view(current_user):
@@ -576,4 +716,4 @@ def download_signed_contract(contract_id):
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)
return send_file(contract.signed_file_path, as_attachment=True, download_name=contract.signed_filename)