diff --git a/instance/team_tryouts.db b/instance/team_tryouts.db
index d0243f9..1de4424 100644
Binary files a/instance/team_tryouts.db and b/instance/team_tryouts.db differ
diff --git a/migrate_usernames.py b/migrate_usernames.py
new file mode 100644
index 0000000..49870ea
--- /dev/null
+++ b/migrate_usernames.py
@@ -0,0 +1,58 @@
+"""Data migration script to fix corrupted username fields.
+
+This script fixes the bug where user.username was incorrectly set to full_name
+instead of preserving the actual username. It migrates existing users by:
+1. Setting username to a slug version of full_name (e.g., 'sarah-johnson')
+2. Clearing full_name to empty string (will be collected via profile edit)
+
+Run this script once to fix existing database records.
+"""
+
+from app import create_app
+from extensions import db
+from models import User
+
+def slugify(name):
+ """Convert a name to a username-friendly slug.
+
+ Args:
+ name (str): Full name to convert.
+
+ Returns:
+ str: Slugified username.
+ """
+ return name.lower().replace(' ', '-').replace("'", '')
+
+def migrate():
+ """Migrate existing users to fix corrupted username/full_name fields."""
+ with app.app_context():
+ users = User.query.all()
+ migrated = 0
+
+ for user in users:
+ # If username looks like a full name (contains spaces), migrate it
+ if ' ' in user.username:
+ # Save the current username (which is actually the full name)
+ actual_full_name = user.username
+ # Generate a username from the full name
+ new_username = slugify(actual_full_name)
+ # Ensure uniqueness
+ base_username = new_username
+ counter = 1
+ while User.query.filter_by(username=new_username).first() and User.query.get(user.id).username != new_username:
+ new_username = f"{base_username}-{counter}"
+ counter += 1
+
+ user.username = new_username
+ user.full_name = actual_full_name
+ migrated += 1
+ print(f"Migrated: '{actual_full_name}' -> username='{new_username}', full_name='{actual_full_name}'")
+
+ db.session.commit()
+ print(f"\n[MIGRATION] Migrated {migrated} users")
+ print("Done! Usernames are now properly stored.")
+ print("Users should edit their profile to set a proper username and full name.")
+
+if __name__ == '__main__':
+ app = create_app()
+ migrate()
\ No newline at end of file
diff --git a/routes/__pycache__/auth.cpython-313.pyc b/routes/__pycache__/auth.cpython-313.pyc
index e98dac6..d2c6525 100644
Binary files a/routes/__pycache__/auth.cpython-313.pyc and b/routes/__pycache__/auth.cpython-313.pyc differ
diff --git a/routes/__pycache__/evaluations.cpython-313.pyc b/routes/__pycache__/evaluations.cpython-313.pyc
index 694d793..fc2b429 100644
Binary files a/routes/__pycache__/evaluations.cpython-313.pyc and b/routes/__pycache__/evaluations.cpython-313.pyc differ
diff --git a/routes/__pycache__/matches.cpython-313.pyc b/routes/__pycache__/matches.cpython-313.pyc
index c44c888..886bd78 100644
Binary files a/routes/__pycache__/matches.cpython-313.pyc and b/routes/__pycache__/matches.cpython-313.pyc differ
diff --git a/routes/__pycache__/teams.cpython-313.pyc b/routes/__pycache__/teams.cpython-313.pyc
index e06a112..30d379a 100644
Binary files a/routes/__pycache__/teams.cpython-313.pyc and b/routes/__pycache__/teams.cpython-313.pyc differ
diff --git a/routes/__pycache__/tryouts.cpython-313.pyc b/routes/__pycache__/tryouts.cpython-313.pyc
index 1515b63..241e54d 100644
Binary files a/routes/__pycache__/tryouts.cpython-313.pyc and b/routes/__pycache__/tryouts.cpython-313.pyc differ
diff --git a/routes/__pycache__/users.cpython-313.pyc b/routes/__pycache__/users.cpython-313.pyc
index bcaa8db..9249980 100644
Binary files a/routes/__pycache__/users.cpython-313.pyc and b/routes/__pycache__/users.cpython-313.pyc differ
diff --git a/routes/auth.py b/routes/auth.py
index b2f2efa..fd34390 100644
--- a/routes/auth.py
+++ b/routes/auth.py
@@ -61,7 +61,7 @@ def login():
next_page = request.args.get('next')
if next_page and not is_safe_url(next_page):
next_page = None
- flash(f'Welcome back, {user.full_name}!', 'success')
+ flash(f'Welcome back, {user.username}!', 'success')
return redirect(next_page) if next_page else redirect(url_for('main.dashboard'))
else:
flash('Login unsuccessful. Please check username and password.', 'danger')
diff --git a/routes/evaluations.py b/routes/evaluations.py
index 367fb64..ceb619b 100644
--- a/routes/evaluations.py
+++ b/routes/evaluations.py
@@ -67,8 +67,8 @@ def list_evaluations():
sort_map = {
'tryout': Tryout.title,
- 'player': player_alias.full_name,
- 'evaluator': evaluator_alias.full_name,
+ 'player': player_alias.username,
+ 'evaluator': evaluator_alias.username,
'mecanics_score': Evaluation.mecanics_score,
'cohesion_score': Evaluation.cohesion_score,
'communication_score': Evaluation.communication_score,
diff --git a/routes/matches.py b/routes/matches.py
index 291f01b..04131aa 100644
--- a/routes/matches.py
+++ b/routes/matches.py
@@ -82,7 +82,7 @@ def api_events():
# Player scrim - show all participants
player_names = []
for p in match.participants.all():
- player_name = p.player.full_name if p.player else 'Unknown Player'
+ player_name = p.player.username if p.player else 'Unknown Player'
player_names.append(player_name)
participants_str = ', '.join(player_names) if player_names else 'No players'
match_desc = participants_str + (f"
{match.description}" if match.description else '')
@@ -186,11 +186,11 @@ def api_events_for_tryout(tryout_id):
team1_players = []
for p in match.participants.filter_by(team_side=1).all():
if p.player:
- team1_players.append(p.player.full_name)
+ team1_players.append(p.player.username)
team2_players = []
for p in match.participants.filter_by(team_side=2).all():
if p.player:
- team2_players.append(p.player.full_name)
+ team2_players.append(p.player.username)
if team1_players and team2_players:
participants_str = f"{', '.join(team1_players)} vs {', '.join(team2_players)}"
else:
@@ -198,7 +198,7 @@ def api_events_for_tryout(tryout_id):
else:
player_names = []
for p in match.participants.all():
- player_name = p.player.full_name if p.player else 'Unknown Player'
+ player_name = p.player.username if p.player else 'Unknown Player'
player_names.append(player_name)
participants_str = ', '.join(player_names) if player_names else 'No players'
@@ -295,7 +295,7 @@ def create_match(tryout_id):
teams = Team.query.filter_by(tryout_id=tryout_id).all()
registrations = TryoutRegistration.query.filter_by(tryout_id=tryout_id).all()
all_players = [User.query.get(r.player_id) for r in registrations if User.query.get(r.player_id)]
- all_players = sorted([p for p in all_players if p], key=lambda x: x.full_name)
+ all_players = sorted([p for p in all_players if p], key=lambda x: x.username)
if request.method == 'POST':
title = request.form.get('title')
@@ -453,7 +453,7 @@ def edit_match(match_id):
# Only show players registered for this tryout
registrations = TryoutRegistration.query.filter_by(tryout_id=tryout.id).all()
all_players = [User.query.get(r.player_id) for r in registrations if r.player_id]
- all_players = sorted([p for p in all_players if p], key=lambda x: x.full_name)
+ all_players = sorted([p for p in all_players if p], key=lambda x: x.username)
current_player_ids = [p.player_id for p in match.participants.all()]
# Get players grouped by team side for player_vs_player matches
team1_player_ids = [p.player_id for p in match.participants.filter_by(team_side=1).all()]
diff --git a/routes/teams.py b/routes/teams.py
index ebd1ea5..347cabe 100644
--- a/routes/teams.py
+++ b/routes/teams.py
@@ -34,9 +34,9 @@ def list_teams():
flash('You do not have permission to view teams.', 'danger')
return redirect(url_for('main.dashboard'))
- coaches = User.query.filter_by(role='coach').order_by(User.full_name).all()
- managers = User.query.filter_by(role='manager').order_by(User.full_name).all()
- all_players = User.query.filter_by(role='player').order_by(User.full_name).all()
+ coaches = User.query.filter_by(role='coach').order_by(User.username).all()
+ managers = User.query.filter_by(role='manager').order_by(User.username).all()
+ all_players = User.query.filter_by(role='player').order_by(User.username).all()
return render_template('pages/teams.html', teams=teams, coaches=coaches, managers=managers, all_players=all_players, can_manage=can_manage)
@@ -239,7 +239,7 @@ def add_player(team_id):
# Check if player is already on this team
existing = TeamPlayer.query.filter_by(player_id=player.id, org_team_id=team.id).first()
if existing:
- flash(f'{player.full_name} is already on {team.name}.', 'info')
+ flash(f'{player.username} is already on {team.name}.', 'info')
return redirect(url_for('teams.list_teams'))
# Add player to the team using TeamPlayer model (allows multiple teams)
@@ -250,7 +250,7 @@ def add_player(team_id):
)
db.session.add(tp)
db.session.commit()
- flash(f'{player.full_name} added to {team.name}!', 'success')
+ flash(f'{player.username} added to {team.name}!', 'success')
return redirect(url_for('teams.list_teams'))
@@ -275,12 +275,12 @@ def remove_player(team_id, player_id):
tp = TeamPlayer.query.filter_by(player_id=player_id, org_team_id=team_id).first()
if not tp:
- flash(f'{player.full_name} is not on {team.name}.', 'danger')
+ flash(f'{player.username} is not on {team.name}.', 'danger')
return redirect(url_for('teams.list_teams'))
db.session.delete(tp)
db.session.commit()
- flash(f'{player.full_name} removed from {team.name}.', 'success')
+ flash(f'{player.username} removed from {team.name}.', 'success')
return redirect(url_for('teams.list_teams'))
@@ -312,7 +312,7 @@ def toggle_player_status(team_id, player_id):
'success': True,
'player_id': player_id,
'new_status': tp.status,
- 'player_name': tp.player.full_name
+ 'player_name': tp.player.username
})
@@ -379,7 +379,7 @@ def add_player_note(team_id, player_id):
# Verify player belongs to this team
tp = TeamPlayer.query.filter_by(player_id=player_id, org_team_id=team_id).first()
if not tp:
- flash(f'{player.full_name} is not on {team.name}.', 'danger')
+ flash(f'{player.username} is not on {team.name}.', 'danger')
return redirect(url_for('teams.list_teams'))
content = request.form.get('content', '').strip()
@@ -392,6 +392,6 @@ def add_player_note(team_id, player_id):
)
db.session.add(note)
db.session.commit()
- flash(f'Note added for {player.full_name}!', 'success')
+ flash(f'Note added for {player.username}!', 'success')
return redirect(url_for('teams.list_teams'))
\ No newline at end of file
diff --git a/routes/tryouts.py b/routes/tryouts.py
index c9fa812..e5e0723 100644
--- a/routes/tryouts.py
+++ b/routes/tryouts.py
@@ -73,8 +73,8 @@ def create_tryout():
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()
+ 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')
@@ -265,7 +265,7 @@ def view_tryout(tryout_id):
# Only expose all_players to users who can manage players in this tryout
all_players = None
if can_edit:
- all_players = User.query.filter_by(role='player').order_by(User.full_name).all()
+ all_players = User.query.filter_by(role='player').order_by(User.username).all()
# Get matches for this tryout with participant info
matches = Match.query.filter_by(tryout_id=tryout_id).order_by(Match.date, Match.start_time).all()
@@ -275,12 +275,12 @@ def view_tryout(tryout_id):
participants = {
'team1': match.team1.name if match.team1 else 'TBD',
'team2': match.team2.name if match.team2 else 'TBD',
- 'team1_players': [{'name': m.player.full_name, 'position': m.position} for m in match.team1.members.all()] if match.team1 else [],
- 'team2_players': [{'name': m.player.full_name, 'position': m.position} for m in match.team2.members.all()] if match.team2 else []
+ '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.full_name, 'position': p.position} for p in match.participants.filter_by(team_side=1).all() if p.player]
- team2_players = [{'name': p.player.full_name, 'position': p.position} for p in match.participants.filter_by(team_side=2).all() if p.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',
@@ -288,7 +288,7 @@ def view_tryout(tryout_id):
'team2_players': team2_players
}
else:
- participants = [p.player.full_name for p in match.participants.all()]
+ participants = [p.player.username for p in match.participants.all()]
match_data.append({
'match': match,
'participants': participants
@@ -433,7 +433,7 @@ def register_player(tryout_id):
existing = TryoutRegistration.query.filter_by(tryout_id=tryout_id, player_id=player.id).first()
if existing:
- flash(f'{player.full_name} is already registered for this tryout.', 'info')
+ 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:
@@ -445,7 +445,7 @@ def register_player(tryout_id):
registration = TryoutRegistration(tryout_id=tryout_id, player_id=player.id)
db.session.add(registration)
db.session.commit()
- flash(f'{player.full_name} registered for tryout!', 'success')
+ flash(f'{player.username} registered for tryout!', 'success')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
diff --git a/routes/users.py b/routes/users.py
index 87cf69e..18861b9 100644
--- a/routes/users.py
+++ b/routes/users.py
@@ -66,7 +66,7 @@ def list_users():
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()
+ users = User.query.order_by(User.role, User.username).all()
return render_template('pages/users.html', users=users, roles=ROLES)
@@ -124,7 +124,7 @@ def edit_user(user_id):
user.password_hash = hash_password(password)
db.session.commit()
- flash(f'User {user.full_name} updated successfully!', 'success')
+ flash(f'User {user.username} 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}
@@ -207,7 +207,7 @@ def delete_user(user_id):
db.session.delete(user)
db.session.commit()
- flash(f'User {user.full_name} has been removed.', 'success')
+ flash(f'User {user.username} has been removed.', 'success')
return redirect(url_for('users.list_users'))
@@ -292,6 +292,7 @@ def edit_profile():
Response: Edit form or redirect to profile.
"""
if request.method == 'POST':
+ username = request.form.get('username')
full_name = request.form.get('full_name')
email = request.form.get('email')
phone = request.form.get('phone')
@@ -301,10 +302,15 @@ def edit_profile():
discord_user_id = request.form.get('discord_user_id', '').strip()
league_os_profile = request.form.get('league_os_profile', '').strip()
+ if username != current_user.username and User.query.filter_by(username=username).first():
+ flash('Username already taken.', 'danger')
+ return render_template('pages/edit_profile.html', user=current_user, esport_games=ESPORT_GAMES, game_platforms=GAME_PLATFORMS, user_gamertags={gt.game: {'gamertag': gt.gamertag, 'platform': gt.platform} for gt in current_user.gamertags})
+
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)
+ return render_template('pages/edit_profile.html', user=current_user, esport_games=ESPORT_GAMES, game_platforms=GAME_PLATFORMS, user_gamertags={gt.game: {'gamertag': gt.gamertag, 'platform': gt.platform} for gt in current_user.gamertags})
+ current_user.username = username
current_user.full_name = full_name
current_user.email = email
current_user.phone = phone
@@ -358,13 +364,13 @@ def get_disponibilities():
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()
+ players = User.query.filter_by(role='player', is_active_account=True).order_by(User.username).all()
result = {}
for player in players:
disponibilities = list(player.disponibilities)
result[player.id] = {
- 'full_name': player.full_name,
+ 'username': player.username,
'disponibilities': [
{
'id': d.id,
@@ -671,7 +677,7 @@ def upload_contract():
# 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 = f"{secure_filename(player.username)}_{timestamp}_{original_filename}"
stored_filename = stored_filename.replace(' ', '_')
# Save file
@@ -691,7 +697,7 @@ def upload_contract():
db.session.add(contract)
db.session.commit()
- flash(f'Contract uploaded successfully for {player.full_name}!', 'success')
+ flash(f'Contract uploaded successfully for {player.username}!', 'success')
return redirect(url_for('users.list_contracts'))
return render_template('pages/upload_contract.html', players=players)
@@ -955,13 +961,13 @@ def one_on_one():
# Send Discord notification
send_discord_notification(
- player_name=current_user.full_name,
+ player_name=current_user.username,
points=points,
date_str=date_str,
start_time_str=start_time_str,
end_time_str=end_time_str,
team_name=org_team.name if org_team else None,
- coach_name=coach.full_name,
+ coach_name=coach.username,
coach_discord=coach.discord_username,
coach_discord_id=coach.discord_user_id,
request_id=request_obj.id
@@ -1207,7 +1213,7 @@ def manage_personal_notes():
players = []
if org_team:
player_ids = [tp.player_id for tp in TeamPlayer.query.filter_by(org_team_id=org_team.id).all()]
- players = User.query.filter(User.id.in_(player_ids)).order_by(User.full_name).all() if player_ids else []
+ players = User.query.filter(User.id.in_(player_ids)).order_by(User.username).all() if player_ids else []
if request.method == 'POST':
player_id = request.form.get('player_id', type=int)
@@ -1264,7 +1270,7 @@ def notes_dashboard():
players = []
if org_team:
player_ids = [tp.player_id for tp in TeamPlayer.query.filter_by(org_team_id=org_team.id).all()]
- players = User.query.filter(User.id.in_(player_ids)).order_by(User.full_name).all() if player_ids else []
+ players = User.query.filter(User.id.in_(player_ids)).order_by(User.username).all() if player_ids else []
# Get existing team notes
team_notes = []
@@ -1361,9 +1367,9 @@ def add_personal_note():
org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first()
if org_team:
player_ids = [tp.player_id for tp in TeamPlayer.query.filter_by(org_team_id=org_team.id).all()]
- players = User.query.filter(User.id.in_(player_ids)).order_by(User.full_name).all() if player_ids else []
+ players = User.query.filter(User.id.in_(player_ids)).order_by(User.username).all() if player_ids else []
elif current_user.role in ['president', 'manager']:
- players = User.query.filter_by(role='player').order_by(User.full_name).all()
+ players = User.query.filter_by(role='player').order_by(User.username).all()
# Get available matches and tryouts for context
matches = []
@@ -1483,7 +1489,7 @@ def add_note_from_match(match_id):
participants = MatchParticipant.query.filter_by(match_id=match_id).all()
participant_ids = [p.player_id for p in participants]
- players = User.query.filter(User.id.in_(participant_ids)).order_by(User.full_name).all() if participant_ids else []
+ players = User.query.filter(User.id.in_(participant_ids)).order_by(User.username).all() if participant_ids else []
# Get team notes for context
team_notes = []
@@ -1553,9 +1559,9 @@ def add_note_from_tryout(tryout_id):
# If coach, filter to only their team players; otherwise include all
if org_team:
team_player_ids = [tp.player_id for tp in TeamPlayer.query.filter_by(org_team_id=org_team.id).all()]
- players = User.query.filter(User.id.in_(player_ids), User.id.in_(team_player_ids)).order_by(User.full_name).all()
+ players = User.query.filter(User.id.in_(player_ids), User.id.in_(team_player_ids)).order_by(User.username).all()
else:
- players = User.query.filter(User.id.in_(player_ids)).order_by(User.full_name).all()
+ players = User.query.filter(User.id.in_(player_ids)).order_by(User.username).all()
# Get team notes for context
team_notes = []
diff --git a/templates/layouts/base.html b/templates/layouts/base.html
index cfb94bb..b7380b1 100644
--- a/templates/layouts/base.html
+++ b/templates/layouts/base.html
@@ -18,10 +18,10 @@
{{ note.content[:200] }}{% if note.content|length > 200 %}...{% endif %}