Ajout de notes personnels et de notes d'équipe avec historique. Ajout de prise de rendez-vous avec un coach selon ses disponibilités
793 lines
33 KiB
Python
793 lines
33 KiB
Python
"""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 for player profiles
|
|
ESPORT_GAMES = [
|
|
'Valorant',
|
|
'League of Legends',
|
|
'Counter-Strike 2',
|
|
'Apex Legends',
|
|
'Overwatch 2',
|
|
'Rainbow Six Siege',
|
|
'Rocket League',
|
|
'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', 'Flex'],
|
|
'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)
|
|
password_hash = db.Column(db.String(128), nullable=False)
|
|
role = db.Column(db.String(20), nullable=False, default='player')
|
|
full_name = db.Column(db.String(100), nullable=False)
|
|
email = db.Column(db.String(120), unique=True, nullable=False)
|
|
phone = db.Column(db.String(20), nullable=True)
|
|
team_id = db.Column(db.Integer, db.ForeignKey('org_teams.id'), nullable=True)
|
|
is_active_account = db.Column(db.Boolean, default=True)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
|
|
# E-Sports specific fields
|
|
games = db.Column(db.Text, nullable=True) # Comma-separated list of games
|
|
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')
|
|
tryout_registrations = db.relationship('TryoutRegistration', backref='player', lazy='dynamic')
|
|
team_assignments = db.relationship('TeamMember', foreign_keys='TeamMember.player_id', backref='player_ref', lazy='dynamic')
|
|
|
|
def get_games_list(self):
|
|
"""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):
|
|
"""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.
|
|
|
|
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:
|
|
return True
|
|
if self.role == 'coach':
|
|
org_team = OrgTeam.query.filter_by(coach_id=self.id).first()
|
|
if org_team and tryout.target_org_team_id == org_team.id:
|
|
return True
|
|
return False
|
|
|
|
def can_manage_this_org_team(self, org_team):
|
|
"""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':
|
|
return True # Managers can manage all org teams (create/edit/delete)
|
|
if self.role == 'coach' and org_team.coach_id == self.id:
|
|
return True
|
|
return False
|
|
|
|
def get_gamertags(self):
|
|
"""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}
|
|
|
|
|
|
# Platform options for games that require platform specification
|
|
GAME_PLATFORMS = {
|
|
'Valorant': [], # No platform needed
|
|
'League of Legends': [],
|
|
'Counter-Strike 2': [],
|
|
'Apex Legends': ['PC', 'PlayStation', 'Xbox', 'Nintendo Switch'],
|
|
'Overwatch 2': ['PC', 'PlayStation', 'Xbox', 'Nintendo Switch'],
|
|
'Rainbow Six Siege': ['Ubisoft', 'PlayStation', 'Xbox'], # Ubisoft = Uplay/Steam
|
|
'Rocket League': ['Epic', 'PlayStation', 'Xbox', 'Nintendo Switch'], # Epic uses EpicID, others use username
|
|
'Super Smash Bros.': ['Nintendo Switch'],
|
|
}
|
|
|
|
|
|
# Platform code mapping for TRN URLs (platform -> TRN code)
|
|
PLATFORM_CODES = {
|
|
'Ubisoft': 'ubi',
|
|
'PlayStation': 'psn',
|
|
'Xbox': 'xbl',
|
|
'Nintendo Switch': 'switch',
|
|
'PC': 'pc',
|
|
'Steam': 'steam',
|
|
'Epic': 'epic',
|
|
}
|
|
|
|
# TRN URL mapping for each game
|
|
TRN_URLS = {
|
|
'Valorant': 'https://tracker.gg/valorant/profile/riot/{username}',
|
|
'League of Legends': 'https://tracker.gg/lol/profile/{username}',
|
|
'Counter-Strike 2': 'https://tracker.gg/cs2/profile/steam/{username}',
|
|
'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}',
|
|
'Rocket League': 'https://rocketleague.tracker.network/rocket-league/profile/{platform_code}/{username}',
|
|
'Super Smash Bros.': 'https://tracker.gg/smash/profile/{username}',
|
|
}
|
|
|
|
|
|
class UserGamertag(db.Model):
|
|
"""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)
|
|
game = db.Column(db.String(50), nullable=False)
|
|
gamertag = db.Column(db.String(120), nullable=False)
|
|
platform = db.Column(db.String(30), nullable=True) # For games that need platform (e.g., PSN, Xbox Live)
|
|
|
|
user = db.relationship('User', backref='gamertags')
|
|
|
|
__table_args__ = (
|
|
db.UniqueConstraint('user_id', 'game', name='unique_user_game'),
|
|
)
|
|
|
|
def get_trn_url(self):
|
|
"""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
|
|
|
|
url = TRN_URLS[self.game]
|
|
# URL-encode the gamertag to handle special characters like #, spaces, etc.
|
|
encoded_gamertag = quote(self.gamertag, safe='')
|
|
|
|
# Check for platform_code placeholder (used for R6 and Rocket League)
|
|
if '{platform_code}' in url and '{username}' in url:
|
|
platform_code = PLATFORM_CODES.get(self.platform, self.platform.lower().replace(' ', '-')) if self.platform else ''
|
|
return url.format(platform_code=platform_code, username=encoded_gamertag)
|
|
elif '{platform}' in url and '{username}' in url:
|
|
return url.format(platform=self.platform.lower().replace(' ', '-'), username=encoded_gamertag)
|
|
elif '{username}' in url:
|
|
return url.format(username=encoded_gamertag)
|
|
return url
|
|
|
|
|
|
class OrgTeam(db.Model):
|
|
"""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)
|
|
coach_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
|
|
created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
|
|
coach = db.relationship('User', foreign_keys=[coach_id], backref='coached_org_team', uselist=False)
|
|
creator = db.relationship('User', foreign_keys=[created_by])
|
|
players = db.relationship('User', foreign_keys='User.team_id', backref='org_team', lazy='dynamic')
|
|
|
|
|
|
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
|
|
max_players = db.Column(db.Integer, nullable=True)
|
|
created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|
target_org_team_id = db.Column(db.Integer, db.ForeignKey('org_teams.id'), nullable=True)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
|
|
creator = db.relationship('User', backref='created_tryouts')
|
|
registrations = db.relationship('TryoutRegistration', backref='tryout', lazy='dynamic')
|
|
evaluations = db.relationship('Evaluation', backref='tryout', lazy='dynamic')
|
|
teams = db.relationship('Team', backref='tryout', lazy='dynamic')
|
|
target_org_team = db.relationship('OrgTeam', backref='tryouts', foreign_keys=[target_org_team_id])
|
|
|
|
|
|
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)
|
|
player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|
registered_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
status = db.Column(db.String(20), default='registered') # registered, attended, no_show
|
|
notes = db.Column(db.Text, nullable=True)
|
|
|
|
|
|
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)
|
|
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)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
__table_args__ = (
|
|
db.UniqueConstraint('tryout_id', 'player_id', 'evaluator_id', name='unique_evaluation'),
|
|
)
|
|
|
|
|
|
class Team(db.Model):
|
|
"""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)
|
|
name = db.Column(db.String(100), nullable=False)
|
|
created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
|
|
creator = db.relationship('User', backref='created_teams')
|
|
members = db.relationship('TeamMember', backref='team', lazy='dynamic')
|
|
|
|
|
|
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)
|
|
player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|
position = db.Column(db.String(50), nullable=True)
|
|
added_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
|
|
player = db.relationship('User', overlaps="player_ref,team_assignments") # Many-to-one, no dynamic loader
|
|
|
|
|
|
class Match(db.Model):
|
|
"""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)
|
|
title = db.Column(db.String(200), nullable=False)
|
|
description = db.Column(db.Text, nullable=True)
|
|
date = db.Column(db.Date, nullable=False)
|
|
start_time = db.Column(db.Time, nullable=True)
|
|
end_time = db.Column(db.Time, nullable=True)
|
|
location = db.Column(db.String(200), nullable=True)
|
|
status = db.Column(db.String(20), default='scheduled') # scheduled, completed, cancelled
|
|
match_type = db.Column(db.String(20), nullable=False) # team_vs_team, player_scrim
|
|
created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
|
|
# For team vs team matches
|
|
team1_id = db.Column(db.Integer, db.ForeignKey('teams.id'), nullable=True)
|
|
team2_id = db.Column(db.Integer, db.ForeignKey('teams.id'), nullable=True)
|
|
|
|
creator = db.relationship('User', backref='created_matches')
|
|
tryout = db.relationship('Tryout', backref='matches')
|
|
team1 = db.relationship('Team', foreign_keys=[team1_id], backref='matches_as_team1')
|
|
team2 = db.relationship('Team', foreign_keys=[team2_id], backref='matches_as_team2')
|
|
participants = db.relationship('MatchParticipant', backref='match', lazy='dynamic')
|
|
|
|
def get_participating_players(self):
|
|
"""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.
|
|
|
|
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)
|
|
day_of_week = db.Column(db.Integer, nullable=False) # 0=Monday, 6=Sunday
|
|
start_time = db.Column(db.Time, nullable=False)
|
|
end_time = db.Column(db.Time, nullable=False) # Always 30 minutes after start_time
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
player = db.relationship('User', backref='disponibilities')
|
|
|
|
|
|
class MatchParticipant(db.Model):
|
|
"""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)
|
|
player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|
team_side = db.Column(db.Integer, nullable=True) # 1 for team 1, 2 for team 2 (for player_vs_player matches)
|
|
position = db.Column(db.String(50), nullable=True) # Position for this match
|
|
added_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
|
|
player = db.relationship('User')
|
|
|
|
|
|
class Contract(db.Model):
|
|
"""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)
|
|
team_id = db.Column(db.Integer, db.ForeignKey('org_teams.id'), nullable=True)
|
|
uploaded_by_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|
|
|
# File information
|
|
original_filename = db.Column(db.String(255), nullable=False)
|
|
stored_filename = db.Column(db.String(255), nullable=False)
|
|
file_path = db.Column(db.String(500), nullable=False)
|
|
|
|
# Signed contract information
|
|
signed_filename = db.Column(db.String(255), nullable=True)
|
|
signed_file_path = db.Column(db.String(500), nullable=True)
|
|
|
|
# Status and metadata
|
|
status = db.Column(db.String(20), default='pending') # pending, signed
|
|
notes = db.Column(db.Text, nullable=True)
|
|
uploaded_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
signed_at = db.Column(db.DateTime, nullable=True)
|
|
|
|
# Relationships
|
|
player = db.relationship('User', foreign_keys=[player_id], backref='contracts')
|
|
team = db.relationship('OrgTeam', foreign_keys=[team_id])
|
|
uploader = db.relationship('User', foreign_keys=[uploaded_by_id])
|
|
|
|
def can_view(self, user):
|
|
"""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
|
|
# President can view all contracts
|
|
if user.role == 'president':
|
|
return True
|
|
# Manager can view contracts for players on their teams
|
|
if user.role == 'manager':
|
|
player = User.query.get(self.player_id)
|
|
if player and player.team_id:
|
|
return True
|
|
# Coach can view contracts for players on their team
|
|
if user.role == 'coach':
|
|
org_team = OrgTeam.query.filter_by(coach_id=user.id).first()
|
|
if org_team and (not self.team_id or self.team_id == org_team.id):
|
|
return True
|
|
return False
|
|
|
|
def can_upload_signed(self, user):
|
|
"""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
|
|
|
|
|
|
class CoachAvailability(db.Model):
|
|
"""Coach availability in 30-minute time blocks for One on One sessions.
|
|
|
|
Allows coaches to specify when they're available for individual coaching sessions.
|
|
|
|
Attributes:
|
|
id: Unique identifier.
|
|
coach_id: Foreign key to the coach.
|
|
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__ = 'coach_availabilities'
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
coach_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|
day_of_week = db.Column(db.Integer, nullable=False) # 0=Monday, 6=Sunday
|
|
start_time = db.Column(db.Time, nullable=False)
|
|
end_time = db.Column(db.Time, nullable=False) # Always 30 minutes after start_time
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
coach = db.relationship('User', backref='coach_availabilities')
|
|
|
|
|
|
class TeamNote(db.Model):
|
|
"""Team improvement notes from coach.
|
|
|
|
Contains coaching notes and suggestions for team improvement.
|
|
|
|
Attributes:
|
|
id: Unique identifier.
|
|
org_team_id: Foreign key to the organization team.
|
|
coach_id: Foreign key to the coach who wrote the notes.
|
|
content: The note content.
|
|
created_at: Timestamp of creation.
|
|
updated_at: Timestamp of last update.
|
|
"""
|
|
__tablename__ = 'team_notes'
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
org_team_id = db.Column(db.Integer, db.ForeignKey('org_teams.id'), nullable=False)
|
|
coach_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|
content = db.Column(db.Text, nullable=False)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
team = db.relationship('OrgTeam', backref='team_notes')
|
|
coach = db.relationship('User', foreign_keys=[coach_id])
|
|
|
|
|
|
class PersonalNote(db.Model):
|
|
"""Personal notes from coach to individual player.
|
|
|
|
Contains individual feedback and coaching tips for players.
|
|
Can be linked to specific contexts: match, team, or tryout.
|
|
|
|
Attributes:
|
|
id: Unique identifier.
|
|
player_id: Foreign key to the player.
|
|
coach_id: Foreign key to the coach who wrote the notes.
|
|
content: The note content.
|
|
created_at: Timestamp of creation.
|
|
updated_at: Timestamp of last update.
|
|
match_id: Optional foreign key to the match context.
|
|
team_id: Optional foreign key to the team context.
|
|
tryout_id: Optional foreign key to the tryout context.
|
|
"""
|
|
__tablename__ = 'personal_notes'
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|
coach_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|
content = db.Column(db.Text, nullable=False)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
# Optional context linking
|
|
match_id = db.Column(db.Integer, db.ForeignKey('matches.id'), nullable=True)
|
|
team_id = db.Column(db.Integer, db.ForeignKey('teams.id'), nullable=True)
|
|
tryout_id = db.Column(db.Integer, db.ForeignKey('tryouts.id'), nullable=True)
|
|
|
|
player = db.relationship('User', foreign_keys=[player_id], backref='personal_notes')
|
|
coach = db.relationship('User', foreign_keys=[coach_id])
|
|
match = db.relationship('Match', foreign_keys=[match_id])
|
|
team = db.relationship('Team', foreign_keys=[team_id])
|
|
tryout = db.relationship('Tryout', foreign_keys=[tryout_id])
|
|
|
|
|
|
class OneOnOneRequest(db.Model):
|
|
"""Request from player to coach for a One on One session.
|
|
|
|
Tracks requests for individual coaching sessions with time slot selection.
|
|
|
|
Attributes:
|
|
id: Unique identifier.
|
|
player_id: Foreign key to the player requesting.
|
|
coach_id: Foreign key to the coach.
|
|
org_team_id: Foreign key to the player's team.
|
|
date: Requested date for the session.
|
|
start_time: Requested start time.
|
|
end_time: Requested end time.
|
|
points: What the player wants to discuss.
|
|
status: Request status (pending, approved, rejected, scheduled).
|
|
created_at: Timestamp of creation.
|
|
responded_at: Timestamp when coach responded.
|
|
"""
|
|
__tablename__ = 'one_on_one_requests'
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|
coach_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|
org_team_id = db.Column(db.Integer, db.ForeignKey('org_teams.id'), nullable=True)
|
|
date = db.Column(db.Date, nullable=False)
|
|
start_time = db.Column(db.Time, nullable=False)
|
|
end_time = db.Column(db.Time, nullable=False)
|
|
points = db.Column(db.Text, nullable=True)
|
|
status = db.Column(db.String(20), default='pending') # pending, approved, rejected, scheduled
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
responded_at = db.Column(db.DateTime, nullable=True)
|
|
|
|
player = db.relationship('User', foreign_keys=[player_id], backref='one_on_one_requests')
|
|
coach = db.relationship('User', foreign_keys=[coach_id])
|
|
team = db.relationship('OrgTeam', foreign_keys=[org_team_id])
|