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
+330 -45
View File
@@ -1,11 +1,19 @@
"""Database models for the Team Tryouts application.
This module defines all SQLAlchemy models including User, Tryout, Evaluation,
Team, Match, and Contract entities with their relationships and helper methods.
"""
from extensions import db, login_manager
from flask_login import UserMixin
from datetime import datetime
from urllib.parse import quote
# Available user roles in the system
ROLES = ['president', 'manager', 'coach', 'player', 'scout']
# Popular E-Sports games list
# Popular E-Sports games list for player profiles
ESPORT_GAMES = [
'Valorant',
'League of Legends',
@@ -13,15 +21,56 @@ ESPORT_GAMES = [
'Apex Legends',
'Overwatch 2',
'Rainbow Six Siege',
'Fortnite',
'Rocket League',
'Call of Duty',
'Dota 2',
'Super Smash Bros.',
'Street Fighter 6',
'Super Smash Bros.'
]
# Game-specific positions for tryouts
GAME_POSITIONS = {
'League of Legends': ['Top Lane', 'Jungle', 'Mid Lane', 'ADC', 'Support'],
'Valorant': ['Controller', 'Initiator', 'Duelist', 'Sentinel'],
'Counter-Strike 2': ['AWPer', 'Entry Fragger', 'Lurker', 'In-Game Leader', 'Support'],
'Rainbow Six Siege': ['Entry', 'Support', 'Breacher', 'Anchor', 'Flex'],
'Overwatch 2': ['Tank', 'Damage', 'Support'],
'Apex Legends': [],
'Rocket League': [],
'Super Smash Bros.': []
}
@login_manager.user_loader
def load_user(user_id):
"""Load a user by ID for Flask-Login session management.
Args:
user_id (int): The user's unique identifier.
Returns:
User: The User object if found, None otherwise.
"""
return User.query.get(int(user_id))
class User(UserMixin, db.Model):
"""User model representing all users in the system.
Users can have different roles: president, manager, coach, player, or scout.
Each user has E-Sports specific fields for competitive gaming profiles.
Attributes:
id: Unique identifier for the user.
username: Unique username for login.
password_hash: Hashed password for authentication.
role: User role determining permissions.
full_name: User's display name.
email: User's email address.
phone: Optional phone number.
team_id: Foreign key to the user's organization team.
is_active_account: Whether the account is active.
games: Comma-separated list of games the user plays.
discord_username: User's Discord handle.
league_os_profile: Link to League OS profile.
"""
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
@@ -36,8 +85,8 @@ class User(UserMixin, db.Model):
# E-Sports specific fields
games = db.Column(db.Text, nullable=True) # Comma-separated list of games
discord_username = db.Column(db.String(100), nullable=True) # Discord handle
league_os_profile = db.Column(db.String(255), nullable=True) # League OS profile URL or ID
discord_username = db.Column(db.String(128), nullable=True) # Discord handle
league_os_profile = db.Column(db.String(256), nullable=True) # League OS profile URL or ID
evaluations_given = db.relationship('Evaluation', foreign_keys='Evaluation.evaluator_id', backref='evaluator', lazy='dynamic')
evaluations_received = db.relationship('Evaluation', foreign_keys='Evaluation.player_id', backref='player', lazy='dynamic')
@@ -45,31 +94,78 @@ class User(UserMixin, db.Model):
team_assignments = db.relationship('TeamMember', foreign_keys='TeamMember.player_id', backref='player_ref', lazy='dynamic')
def get_games_list(self):
"""Return games as a list."""
"""Return the user's games as a list.
Returns:
list: List of game names the user plays, or empty list if none.
"""
if self.games:
return [g.strip() for g in self.games.split(',') if g.strip()]
return []
def has_role(self, *roles):
"""Check if the user has one of the specified roles.
Args:
*roles: Variable number of role names to check.
Returns:
bool: True if user has any of the specified roles.
"""
return self.role in roles
def can_evaluate(self):
"""Check if user can evaluate other players.
Returns:
bool: True if user is president, manager, or coach.
"""
return self.role in ['president', 'manager', 'coach']
def can_manage_users(self):
"""Check if user can manage (create/delete) other users.
Returns:
bool: True if user is president.
"""
return self.role == 'president'
def can_manage_tryouts(self):
return self.role in ['president', 'manager', 'coach', 'scout']
"""Check if user can manage tryouts.
Returns:
bool: True if user is president, manager or coach.
"""
return self.role in ['president', 'manager', 'coach']
def can_manage_teams(self):
"""Check if user can manage organization teams.
Returns:
bool: True if user is president or manager.
"""
return self.role in ['president', 'manager']
def can_schedule_matches(self):
"""Check if user can schedule matches.
Returns:
bool: True if user is president, manager, or coach.
"""
return self.role in ['president', 'manager', 'coach']
def can_manage_this_tryout(self, tryout):
"""Check if user can manage a specific tryout (president, or manager/coach in charge of it)."""
"""Check if user can manage a specific tryout.
Presidents can manage all tryouts. Managers can manage their own tryouts.
Coaches can manage tryouts targeting their coached team.
Args:
tryout: The Tryout object to check permissions for.
Returns:
bool: True if user has permission to manage the tryout.
"""
if self.role == 'president':
return True
if self.role == 'manager' and tryout.created_by == self.id:
@@ -81,7 +177,17 @@ class User(UserMixin, db.Model):
return False
def can_manage_this_org_team(self, org_team):
"""Check if user can manage a specific org team (president, or manager/coach in charge of it)."""
"""Check if user can manage a specific org team.
Presidents can manage all org teams. Managers can manage all org teams.
Coaches can only manage their own coached team.
Args:
org_team: The OrgTeam object to check permissions for.
Returns:
bool: True if user has permission to manage the org team.
"""
if self.role == 'president':
return True
if self.role == 'manager':
@@ -91,15 +197,14 @@ class User(UserMixin, db.Model):
return False
def get_gamertags(self):
"""Return gamertags as a dict keyed by game."""
"""Return gamertags as a dictionary keyed by game.
Returns:
dict: Dictionary with game names as keys and gamertag info as values.
"""
return {gt.game: {'gamertag': gt.gamertag, 'platform': gt.platform} for gt in self.gamertags}
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
# Platform options for games that require platform specification
GAME_PLATFORMS = {
'Valorant': [], # No platform needed
@@ -108,12 +213,8 @@ GAME_PLATFORMS = {
'Apex Legends': ['PC', 'PlayStation', 'Xbox', 'Nintendo Switch'],
'Overwatch 2': ['PC', 'PlayStation', 'Xbox', 'Nintendo Switch'],
'Rainbow Six Siege': ['Ubisoft', 'PlayStation', 'Xbox'], # Ubisoft = Uplay/Steam
'Fortnite': ['PC', 'PlayStation', 'Xbox', 'Nintendo Switch', 'Mobile'],
'Rocket League': ['Epic', 'PlayStation', 'Xbox', 'Nintendo Switch'], # Epic uses EpicID, others use username
'Call of Duty': ['PC', 'PlayStation', 'Xbox'],
'Dota 2': [],
'Rocket League': ['Epic', 'PlayStation', 'Xbox', 'Nintendo Switch'], # Epic uses EpicID, others use username
'Super Smash Bros.': ['Nintendo Switch'],
'Street Fighter 6': ['PC', 'PlayStation', 'Xbox'],
}
@@ -124,7 +225,6 @@ PLATFORM_CODES = {
'Xbox': 'xbl',
'Nintendo Switch': 'switch',
'PC': 'pc',
'Mobile': 'mobile',
'Steam': 'steam',
'Epic': 'epic',
}
@@ -137,17 +237,24 @@ TRN_URLS = {
'Apex Legends': 'https://tracker.gg/apex/profile/{platform}/{username}',
'Overwatch 2': 'https://tracker.gg/overwatch/profile/{platform}/{username}',
'Rainbow Six Siege': 'https://r6.tracker.network/r6siege/profile/{platform_code}/{username}',
'Fortnite': 'https://tracker.gg/fortnite/profile/{platform}/{username}',
'Rocket League': 'https://rocketleague.tracker.network/rocket-league/profile/{platform_code}/{username}',
'Call of Duty': 'https://tracker.gg/call-of-duty/profile/{platform}/{username}',
'Dota 2': 'https://tracker.gg/dota2/profile/steam/{username}',
'Super Smash Bros.': 'https://tracker.gg/smash/profile/{username}',
'Street Fighter 6': 'https://tracker.gg/streetfighter/profile/{username}',
}
class UserGamertag(db.Model):
"""Store gamertag per game for each user."""
"""Store gamertag per game for each user.
Allows users to link their gaming profiles for different games with optional
platform specification for cross-platform games.
Attributes:
id: Unique identifier.
user_id: Foreign key to the user.
game: Name of the game.
gamertag: Player's gamertag/username for the game.
platform: Platform for cross-platform games (optional).
"""
__tablename__ = 'user_gamertags'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
@@ -162,7 +269,14 @@ class UserGamertag(db.Model):
)
def get_trn_url(self):
"""Generate the TRN URL for this gamertag."""
"""Generate the TRN (Tracker Network) URL for this gamertag.
Builds the appropriate URL based on the game, handling platform codes
and URL encoding for special characters.
Returns:
str: The Tracker Network URL for the gamertag, or None if game not supported.
"""
if self.game not in TRN_URLS:
return None
@@ -182,7 +296,18 @@ class UserGamertag(db.Model):
class OrgTeam(db.Model):
"""Persistent organization teams (e.g., Varsity, JV) that exist across tryouts."""
"""Persistent organization teams (e.g., Varsity, JV) that exist across tryouts.
These teams are long-term organizational structures that persist beyond
individual tryouts, unlike tryout-specific Team entities.
Attributes:
id: Unique identifier.
name: Team name (e.g., "Varsity", "Junior Varsity").
coach_id: Foreign key to the assigned coach.
created_by: Foreign key to the user who created the team.
created_at: Timestamp of team creation.
"""
__tablename__ = 'org_teams'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False, unique=True)
@@ -196,10 +321,29 @@ class OrgTeam(db.Model):
class Tryout(db.Model):
"""Tryout event for player evaluations and team formation.
Represents a scheduled tryout session where players can register
and be evaluated by coaches/managers.
Attributes:
id: Unique identifier.
title: Tryout title/name.
description: Optional description of the tryout.
game: The game this tryout is for.
date: Date of the tryout.
location: Physical or virtual location.
status: Current status (upcoming, in_progress, completed).
max_players: Maximum number of players allowed.
created_by: Foreign key to the creating manager/president.
target_org_team_id: Foreign key to target organization team.
created_at: Timestamp of creation.
"""
__tablename__ = 'tryouts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
description = db.Column(db.Text, nullable=True)
game = db.Column(db.String(50), nullable=False)
date = db.Column(db.Date, nullable=False)
location = db.Column(db.String(200), nullable=True)
status = db.Column(db.String(20), default='upcoming') # upcoming, in_progress, completed
@@ -216,6 +360,19 @@ class Tryout(db.Model):
class TryoutRegistration(db.Model):
"""Registration linking a player to a tryout.
Tracks which players have registered for which tryouts and their
attendance status.
Attributes:
id: Unique identifier.
tryout_id: Foreign key to the tryout.
player_id: Foreign key to the registered player.
registered_at: Timestamp of registration.
status: Registration status (registered, attended, no_show).
notes: Optional notes about the registration.
"""
__tablename__ = 'tryout_registrations'
id = db.Column(db.Integer, primary_key=True)
tryout_id = db.Column(db.Integer, db.ForeignKey('tryouts.id'), nullable=False)
@@ -226,16 +383,41 @@ class TryoutRegistration(db.Model):
class Evaluation(db.Model):
"""Player evaluation record.
Contains scored evaluations from coaches/managers for players
during tryouts.
Attributes:
id: Unique identifier.
tryout_id: Foreign key to the tryout.
player_id: Foreign key to the evaluated player.
evaluator_id: Foreign key to the evaluating coach/manager.
speed_score: Speed rating (1-10).
agility_score: Agility rating (1-10).
technique_score: Technique rating (1-10).
teamwork_score: Teamwork rating (1-10).
attitude_score: Attitude rating (1-10).
overall_score: Average of all scores.
comments: Optional evaluator comments.
position_recommendation: Recommended position.
created_at: Timestamp of creation.
updated_at: Timestamp of last update.
"""
__tablename__ = 'evaluations'
id = db.Column(db.Integer, primary_key=True)
tryout_id = db.Column(db.Integer, db.ForeignKey('tryouts.id'), nullable=False)
player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
evaluator_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
speed_score = db.Column(db.Integer, nullable=True)
agility_score = db.Column(db.Integer, nullable=True)
technique_score = db.Column(db.Integer, nullable=True)
teamwork_score = db.Column(db.Integer, nullable=True)
attitude_score = db.Column(db.Integer, nullable=True)
mecanics_score = db.Column(db.Integer, nullable=True)
cohesion_score = db.Column(db.Integer, nullable=True)
communication_score = db.Column(db.Integer, nullable=True)
gamesense_score = db.Column(db.Integer, nullable=True)
versatility_score = db.Column(db.Integer, nullable=True)
discipline_score = db.Column(db.Integer, nullable=True)
analysis_score = db.Column(db.Integer, nullable=True)
sport_ethics_score = db.Column(db.Integer, nullable=True)
mental_score = db.Column(db.Integer, nullable=True)
overall_score = db.Column(db.Float, nullable=True)
comments = db.Column(db.Text, nullable=True)
position_recommendation = db.Column(db.String(50), nullable=True)
@@ -248,7 +430,18 @@ class Evaluation(db.Model):
class Team(db.Model):
"""Tryout-specific teams (e.g., Alpha, Bravo within a single tryout)."""
"""Tryout-specific team (e.g., Alpha, Bravo within a single tryout).
Teams created during a tryout for match scheduling purposes.
Different from OrgTeam which is a long-term organizational entity.
Attributes:
id: Unique identifier.
tryout_id: Foreign key to the parent tryout.
name: Team name.
created_by: Foreign key to the creator.
created_at: Timestamp of creation.
"""
__tablename__ = 'teams'
id = db.Column(db.Integer, primary_key=True)
tryout_id = db.Column(db.Integer, db.ForeignKey('tryouts.id'), nullable=False)
@@ -261,6 +454,15 @@ class Team(db.Model):
class TeamMember(db.Model):
"""Link between a player and a tryout-specific team.
Attributes:
id: Unique identifier.
team_id: Foreign key to the team.
player_id: Foreign key to the player.
position: Player's position on the team.
added_at: Timestamp when player was added.
"""
__tablename__ = 'team_members'
id = db.Column(db.Integer, primary_key=True)
team_id = db.Column(db.Integer, db.ForeignKey('teams.id'), nullable=False)
@@ -272,7 +474,26 @@ class TeamMember(db.Model):
class Match(db.Model):
"""Matches/scrimmages scheduled within tryouts."""
"""Match/scrimmage scheduled within a tryout.
Can be team vs team or player scrim type.
Attributes:
id: Unique identifier.
tryout_id: Foreign key to the parent tryout.
title: Match title/name.
description: Optional description.
date: Match date.
start_time: Match start time.
end_time: Match end time.
location: Match location.
status: Match status (scheduled, completed, cancelled).
match_type: Type of match (team_vs_team, player_vs_player, player_scrim).
created_by: Foreign key to the creator.
created_at: Timestamp of creation.
team1_id: Foreign key to first team (for team_vs_team).
team2_id: Foreign key to second team (for team_vs_team).
"""
__tablename__ = 'matches'
id = db.Column(db.Integer, primary_key=True)
tryout_id = db.Column(db.Integer, db.ForeignKey('tryouts.id'), nullable=False)
@@ -298,12 +519,28 @@ class Match(db.Model):
participants = db.relationship('MatchParticipant', backref='match', lazy='dynamic')
def get_participating_players(self):
"""Return list of players participating in this match."""
"""Return list of player IDs participating in this match.
Returns:
list: List of unique player IDs.
"""
return [p.player_id for p in self.participants.all()]
class PlayerDisponibility(db.Model):
"""Player availability in 30-minute time blocks for scheduling matches."""
"""Player availability in 30-minute time blocks for scheduling matches.
Allows players to specify when they're available for matches.
Attributes:
id: Unique identifier.
player_id: Foreign key to the player.
day_of_week: Day of week (0=Monday, 6=Sunday).
start_time: Start time of availability block.
end_time: End time of availability block (always 30 min after start).
created_at: Timestamp of creation.
updated_at: Timestamp of last update.
"""
__tablename__ = 'player_disponibilities'
id = db.Column(db.Integer, primary_key=True)
player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
@@ -317,7 +554,18 @@ class PlayerDisponibility(db.Model):
class MatchParticipant(db.Model):
"""Players participating in player scrimmage matches."""
"""Players participating in player scrimmage matches.
Links players to matches for player_scrim and player_vs_player match types.
Attributes:
id: Unique identifier.
match_id: Foreign key to the match.
player_id: Foreign key to the player.
team_side: Team side (1 or 2) for player_vs_player matches.
position: Player's position for this match.
added_at: Timestamp when added.
"""
__tablename__ = 'match_participants'
id = db.Column(db.Integer, primary_key=True)
match_id = db.Column(db.Integer, db.ForeignKey('matches.id'), nullable=False)
@@ -330,7 +578,25 @@ class MatchParticipant(db.Model):
class Contract(db.Model):
"""Contract documents for players to sign."""
"""Contract documents for players to sign.
Tracks contract uploads and signed documents for players.
Attributes:
id: Unique identifier.
player_id: Foreign key to the player.
team_id: Foreign key to the player's team.
uploaded_by_id: Foreign key to the uploader.
original_filename: Original uploaded file name.
stored_filename: Stored file name on disk.
file_path: Full path to the contract file.
signed_filename: Signed file name (if signed).
signed_file_path: Full path to signed contract (if signed).
status: Contract status (pending, signed).
notes: Optional notes.
uploaded_at: Timestamp of upload.
signed_at: Timestamp of signing (if signed).
"""
__tablename__ = 'contracts'
id = db.Column(db.Integer, primary_key=True)
player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
@@ -358,7 +624,17 @@ class Contract(db.Model):
uploader = db.relationship('User', foreign_keys=[uploaded_by_id])
def can_view(self, user):
"""Check if a user can view this contract."""
"""Check if a user can view this contract.
Players can always view their own contracts. Presidents and managers
have full access. Coaches can view contracts for players on their teams.
Args:
user: The User object requesting access.
Returns:
bool: True if user has permission to view the contract.
"""
# Player can always view their own contracts
if user.id == self.player_id:
return True
@@ -378,6 +654,15 @@ class Contract(db.Model):
return False
def can_upload_signed(self, user):
"""Check if a user can upload a signed contract."""
"""Check if a user can upload a signed contract.
Only the player themselves can upload their signed contract.
Args:
user: The User object requesting to upload.
Returns:
bool: True if user is the player associated with the contract.
"""
# Only the player can upload their signed contract
return user.id == self.player_id
return user.id == self.player_id