changer gestion des team intra tryout
CI - Security, Lint & Tests / validate (push) Failing after 1m4s

This commit is contained in:
cedrick2711
2026-09-05 14:38:31 -04:00
parent 742995d3cd
commit e4a271cfd8
3 changed files with 355 additions and 41 deletions
+192 -15
View File
@@ -16,6 +16,7 @@ from app.models import (
ESPORT_GAMES, ESPORT_GAMES,
GAME_POSITIONS, GAME_POSITIONS,
Admin, Admin,
AppSettings,
Coach, Coach,
Evaluation, Evaluation,
Manager, Manager,
@@ -30,7 +31,6 @@ from app.models import (
Tryout, Tryout,
TryoutRegistration, TryoutRegistration,
User, User,
AppSettings,
) )
from app.time_utils import utc_now_naive from app.time_utils import utc_now_naive
from app.validators import ( from app.validators import (
@@ -38,6 +38,7 @@ from app.validators import (
TryoutRegistrationStatusSchema, TryoutRegistrationStatusSchema,
TryoutSchema, TryoutSchema,
TryoutStatusSchema, TryoutStatusSchema,
TryoutTeamMemberPositionSchema,
TryoutTeamMemberSchema, TryoutTeamMemberSchema,
TryoutTeamSchema, TryoutTeamSchema,
) )
@@ -140,7 +141,10 @@ def list_tryouts():
def create_tryout(): def create_tryout():
"""Create a new tryout event. Requires Admin or Manager.""" """Create a new tryout event. Requires Admin or Manager."""
if tryouts_locked(): if tryouts_locked():
flash('Tryouts are currently closed. An admin must open tryouts before changes can be made.', 'danger') flash(
'Tryouts are currently closed. An admin must open tryouts before changes can be made.',
'danger',
)
return redirect(url_for('tryouts.list_tryouts')) return redirect(url_for('tryouts.list_tryouts'))
if not can_manage(): if not can_manage():
@@ -204,7 +208,10 @@ def edit_tryout(tryout_id):
tryout = db.get_or_404(Tryout, tryout_id) tryout = db.get_or_404(Tryout, tryout_id)
if tryouts_locked(): if tryouts_locked():
flash('Tryouts are currently closed. An admin must open tryouts before changes can be made.', 'danger') flash(
'Tryouts are currently closed. An admin must open tryouts before changes can be made.',
'danger',
)
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout.id)) return redirect(url_for('tryouts.view_tryout', tryout_id=tryout.id))
if not current_user.can_manage_this_tryout(tryout): if not current_user.can_manage_this_tryout(tryout):
@@ -273,9 +280,7 @@ def view_tryout(tryout_id):
can_view = False can_view = False
if isinstance(current_user, Admin): if isinstance(current_user, Admin):
can_view = True can_view = True
elif isinstance(current_user, Manager): elif isinstance(current_user, (Manager, Coach)):
can_view = current_user.can_manage_this_tryout(tryout)
elif isinstance(current_user, Coach):
can_view = current_user.can_manage_this_tryout(tryout) can_view = current_user.can_manage_this_tryout(tryout)
elif isinstance(current_user, Player): elif isinstance(current_user, Player):
is_registered = ( is_registered = (
@@ -341,7 +346,12 @@ def view_tryout(tryout_id):
member_players = _users_by_id([m.player_id for m in member_rows if m.player_id]) member_players = _users_by_id([m.player_id for m in member_rows if m.player_id])
for row in member_rows: for row in member_rows:
members_by_team.setdefault(row.team_id, []).append( members_by_team.setdefault(row.team_id, []).append(
{'player': member_players.get(row.player_id), 'position': row.position} {
'player': member_players.get(row.player_id),
'position': row.position,
'member_id': row.id,
'player_id': row.player_id,
}
) )
team_data = [{'team': team, 'members': members_by_team.get(team.id, [])} for team in teams] team_data = [{'team': team, 'members': members_by_team.get(team.id, [])} for team in teams]
@@ -500,7 +510,10 @@ def update_status(tryout_id):
"""Update the status of a tryout.""" """Update the status of a tryout."""
tryout = db.get_or_404(Tryout, tryout_id) tryout = db.get_or_404(Tryout, tryout_id)
if tryouts_locked(): if tryouts_locked():
flash('Tryouts are currently closed. An admin must open tryouts before changes can be made.', 'danger') flash(
'Tryouts are currently closed. An admin must open tryouts before changes can be made.',
'danger',
)
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id)) return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if not current_user.can_manage_this_tryout(tryout): if not current_user.can_manage_this_tryout(tryout):
@@ -524,7 +537,10 @@ def update_registration_status(tryout_id, player_id):
"""Update a registration's attendance status.""" """Update a registration's attendance status."""
tryout = db.get_or_404(Tryout, tryout_id) tryout = db.get_or_404(Tryout, tryout_id)
if tryouts_locked(): if tryouts_locked():
flash('Tryouts are currently closed. An admin must open tryouts before changes can be made.', 'danger') flash(
'Tryouts are currently closed. An admin must open tryouts before changes can be made.',
'danger',
)
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id)) return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if not current_user.can_manage_this_tryout(tryout): if not current_user.can_manage_this_tryout(tryout):
@@ -552,7 +568,10 @@ def register_player(tryout_id):
"""Manually register a player for a tryout (by managers/coaches).""" """Manually register a player for a tryout (by managers/coaches)."""
tryout = locked_tryout_or_404(tryout_id) tryout = locked_tryout_or_404(tryout_id)
if tryouts_locked(): if tryouts_locked():
flash('Tryouts are currently closed. An admin must open tryouts before changes can be made.', 'danger') flash(
'Tryouts are currently closed. An admin must open tryouts before changes can be made.',
'danger',
)
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id)) return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if not current_user.can_manage_this_tryout(tryout): if not current_user.can_manage_this_tryout(tryout):
@@ -603,7 +622,10 @@ def remove_player(tryout_id, player_id):
"""Remove a registered player from a tryout (cascades to teams/matches).""" """Remove a registered player from a tryout (cascades to teams/matches)."""
tryout = db.get_or_404(Tryout, tryout_id) tryout = db.get_or_404(Tryout, tryout_id)
if tryouts_locked(): if tryouts_locked():
flash('Tryouts are currently closed. An admin must open tryouts before changes can be made.', 'danger') flash(
'Tryouts are currently closed. An admin must open tryouts before changes can be made.',
'danger',
)
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id)) return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if not current_user.can_manage_this_tryout(tryout): if not current_user.can_manage_this_tryout(tryout):
@@ -643,7 +665,10 @@ def create_team(tryout_id):
"""Create a tryout-specific team.""" """Create a tryout-specific team."""
tryout = db.get_or_404(Tryout, tryout_id) tryout = db.get_or_404(Tryout, tryout_id)
if tryouts_locked(): if tryouts_locked():
flash('Tryouts are currently closed. An admin must open tryouts before changes can be made.', 'danger') flash(
'Tryouts are currently closed. An admin must open tryouts before changes can be made.',
'danger',
)
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id)) return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if not current_user.can_manage_this_tryout(tryout): if not current_user.can_manage_this_tryout(tryout):
@@ -670,7 +695,10 @@ def add_to_team(tryout_id, team_id):
team = db.get_or_404(Team, team_id) team = db.get_or_404(Team, team_id)
tryout = db.get_or_404(Tryout, tryout_id) tryout = db.get_or_404(Tryout, tryout_id)
if tryouts_locked(): if tryouts_locked():
flash('Tryouts are currently closed. An admin must open tryouts before changes can be made.', 'danger') flash(
'Tryouts are currently closed. An admin must open tryouts before changes can be made.',
'danger',
)
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id)) return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if not current_user.can_manage_this_tryout(tryout): if not current_user.can_manage_this_tryout(tryout):
@@ -710,13 +738,162 @@ def add_to_team(tryout_id, team_id):
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id)) return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
@tryouts_bp.route('/<int:tryout_id>/team/<int:team_id>/edit', methods=['POST'])
@login_required
def edit_team(tryout_id, team_id):
"""Rename a tryout-specific team."""
team = db.get_or_404(Team, team_id)
tryout = db.get_or_404(Tryout, tryout_id)
if tryouts_locked():
flash(
'Tryouts are currently closed. An admin must open tryouts before changes can be made.',
'danger',
)
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if not current_user.can_manage_this_tryout(tryout):
flash(_('Permission denied.'), 'danger')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if team.tryout_id != tryout_id:
abort(404)
try:
data = TryoutTeamSchema().load(form_payload(list_fields=()))
except ValidationError as err:
flash_validation_errors(err)
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
team.name = data['team_name']
db.session.commit()
flash(_('Team renamed to "%(team_name)s".', team_name=data['team_name']), 'success')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
@tryouts_bp.route('/<int:tryout_id>/team/<int:team_id>/delete', methods=['POST'])
@login_required
def delete_team(tryout_id, team_id):
"""Delete a tryout-specific team and its memberships."""
team = db.get_or_404(Team, team_id)
tryout = db.get_or_404(Tryout, tryout_id)
if tryouts_locked():
flash(
'Tryouts are currently closed. An admin must open tryouts before changes can be made.',
'danger',
)
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if not current_user.can_manage_this_tryout(tryout):
flash(_('Permission denied.'), 'danger')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if team.tryout_id != tryout_id:
abort(404)
name = team.name
# Personal note references outlive the team.
PersonalNote.query.filter_by(team_id=team_id).update(
{'team_id': None}, synchronize_session=False
)
# Matches referencing this team: null the FK so the match survives.
Match.query.filter_by(team1_id=team_id).update({'team1_id': None}, synchronize_session=False)
Match.query.filter_by(team2_id=team_id).update({'team2_id': None}, synchronize_session=False)
# Clean participants for those matches (they are no longer in that team).
for match in Match.query.filter(
db.or_(Match.team1_id == team_id, Match.team2_id == team_id)
).all():
MatchParticipant.query.filter_by(match_id=match.id).delete(synchronize_session=False)
TeamMember.query.filter_by(team_id=team_id).delete(synchronize_session=False)
db.session.delete(team)
db.session.commit()
flash(_('Team "%(name)s" deleted.', name=name), 'success')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
@tryouts_bp.route('/<int:tryout_id>/team/<int:team_id>/remove/<int:player_id>', methods=['POST'])
@login_required
def remove_from_team(tryout_id, team_id, player_id):
"""Remove a player from a tryout team."""
team = db.get_or_404(Team, team_id)
tryout = db.get_or_404(Tryout, tryout_id)
if tryouts_locked():
flash(
'Tryouts are currently closed. An admin must open tryouts before changes can be made.',
'danger',
)
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if not current_user.can_manage_this_tryout(tryout):
flash(_('Permission denied.'), 'danger')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if team.tryout_id != tryout_id:
abort(404)
TeamMember.query.filter_by(team_id=team_id, player_id=player_id).delete(
synchronize_session=False
)
# Also remove the player from any match participants that were tied to this team.
match_ids = [m.id for m in Match.query.filter_by(tryout_id=tryout_id).all()]
if match_ids:
MatchParticipant.query.filter(
MatchParticipant.match_id.in_(match_ids),
MatchParticipant.player_id == player_id,
).delete(synchronize_session=False)
db.session.commit()
flash(_('Player removed from team.'), 'success')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
@tryouts_bp.route(
'/<int:tryout_id>/team/<int:team_id>/member/<int:member_id>/position', methods=['POST']
)
@login_required
def update_member_position(tryout_id, team_id, member_id):
"""Set or clear a team member's in-game position (role)."""
team = db.get_or_404(Team, team_id)
tryout = db.get_or_404(Tryout, tryout_id)
member = db.get_or_404(TeamMember, member_id)
if tryouts_locked():
flash(
'Tryouts are currently closed. An admin must open tryouts before changes can be made.',
'danger',
)
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if not current_user.can_manage_this_tryout(tryout):
flash(_('Permission denied.'), 'danger')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if team.tryout_id != tryout_id or member.team_id != team_id:
abort(404)
try:
data = TryoutTeamMemberPositionSchema().load(form_payload(list_fields=()))
except ValidationError as err:
flash_validation_errors(err)
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
member.position = data['position'] or None
db.session.commit()
flash(_('Position updated.'), 'success')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
@tryouts_bp.route('/<int:tryout_id>/delete', methods=['POST']) @tryouts_bp.route('/<int:tryout_id>/delete', methods=['POST'])
@login_required @login_required
def delete_tryout(tryout_id): def delete_tryout(tryout_id):
"""Delete a tryout and all associated data (matches, teams, registrations, evaluations).""" """Delete a tryout and all associated data (matches, teams, registrations, evaluations)."""
tryout = db.get_or_404(Tryout, tryout_id) tryout = db.get_or_404(Tryout, tryout_id)
if tryouts_locked(): if tryouts_locked():
flash('Tryouts are currently closed. An admin must open tryouts before changes can be made.', 'danger') flash(
'Tryouts are currently closed. An admin must open tryouts before changes can be made.',
'danger',
)
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id)) return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if not current_user.can_manage_this_tryout(tryout): if not current_user.can_manage_this_tryout(tryout):
@@ -757,4 +934,4 @@ def delete_tryout(tryout_id):
db.session.delete(tryout) db.session.delete(tryout)
db.session.commit() db.session.commit()
flash(_('Tryout deleted successfully.'), 'success') flash(_('Tryout deleted successfully.'), 'success')
return redirect(url_for('tryouts.list_tryouts')) return redirect(url_for('tryouts.list_tryouts'))
+151 -26
View File
@@ -249,7 +249,14 @@
{% for team in team_data %} {% for team in team_data %}
<div class="team-card mb-3"> <div class="team-card mb-3">
<h4 class="team-name">{{ team.team.name }}</h4> <div class="team-card-header">
<h4 class="team-name">{{ team.team.name }}</h4>
{% if can_edit and not tryout.is_ended %}
<button class="btn btn-sm btn-outline" data-action="show-modal" data-modal-id="editTeamModal{{ team.team.id }}">
<i class="fas fa-edit"></i> {{ _('Edit') }}
</button>
{% endif %}
</div>
<ul class="team-members"> <ul class="team-members">
{% for member in team.members %} {% for member in team.members %}
<li> <li>
@@ -263,32 +270,96 @@
<li class="text-muted">{{ _('No players assigned yet.') }}</li> <li class="text-muted">{{ _('No players assigned yet.') }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
{% if can_edit and not tryout.is_ended and registered_players %}
{% set positions = game_positions.get(tryout.game, []) %}
<form method="POST" action="{{ url_for('tryouts.add_to_team', tryout_id=tryout.id, team_id=team.team.id) }}" class="form-inline mt-2">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<select name="player_id" class="form-select" required>
<option value="">{{ _('Select player...') }}</option>
{% for p in registered_players %}
{% if p.id not in team.members | map(attribute='player') | map(attribute='id') | list %}
<option value="{{ p.id }}">{{ p.username }}</option>
{% endif %}
{% endfor %}
</select>
{% if positions %}
<select name="position" class="form-select mx-2">
<option value="">{{ _('-- Select Position --') }}</option>
{% for pos in positions %}
<option value="{{ pos }}">{{ pos }}</option>
{% endfor %}
</select>
{% else %}
<input type="text" name="position" placeholder="{{ _('Position') }}" class="form-input mx-2">
{% endif %}
<button type="submit" class="btn btn-sm btn-primary">{{ _('Add') }}</button>
</form>
{% endif %}
</div> </div>
{% if can_edit and not tryout.is_ended %}
{% set positions = game_positions.get(tryout.game, []) %}
<!-- Edit Team Modal -->
<div id="editTeamModal{{ team.team.id }}" class="modal hidden">
<div class="modal-backdrop" data-action="hide-modal" data-modal-id="editTeamModal{{ team.team.id }}"></div>
<div class="modal-content">
<div class="modal-header">
<h3><i class="fas fa-users-cog"></i> {{ _('Edit Team: %(name)s', name=team.team.name) }}</h3>
<button class="modal-close" data-action="hide-modal" data-modal-id="editTeamModal{{ team.team.id }}">&times;</button>
</div>
<div class="modal-body">
<!-- Rename -->
<form method="POST" action="{{ url_for('tryouts.edit_team', tryout_id=tryout.id, team_id=team.team.id) }}" class="form-inline mb-3">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<label for="renameInput{{ team.team.id }}" class="mr-2" style="white-space:nowrap;">{{ _('Team name') }}</label>
<input type="text" id="renameInput{{ team.team.id }}" name="team_name" value="{{ team.team.name }}" required class="form-input" style="flex:1;">
<button type="submit" class="btn btn-sm btn-primary ml-2">{{ _('Rename') }}</button>
</form>
<hr class="section-divider">
<!-- Roster -->
<h5 class="mb-2"><i class="fas fa-users"></i> {{ _('Roster') }}</h5>
<div class="roster-rows">
{% for member in team.members %}
<div class="roster-row">
<div class="avatar-sm">{{ member.player.username[:2] | upper }}</div>
<span class="roster-player-name">{{ member.player.username }}</span>
<form method="POST" action="{{ url_for('tryouts.update_member_position', tryout_id=tryout.id, team_id=team.team.id, member_id=member.member_id) }}" class="inline-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<select name="position" class="form-select roster-role-select" data-submit-on-change>
<option value="">{{ _('No role') }}</option>
{% for pos in positions %}
<option value="{{ pos }}" {% if member.position == pos %}selected{% endif %}>{{ pos }}</option>
{% endfor %}
</select>
</form>
<form method="POST" action="{{ url_for('tryouts.remove_from_team', tryout_id=tryout.id, team_id=team.team.id, player_id=member.player_id) }}" class="inline-form" data-confirm="{{ _('Remove %(player)s from this team?', player=member.player.username) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-sm btn-danger roster-remove-btn" title="{{ _('Remove from team') }}">
<i class="fas fa-user-minus"></i>
</button>
</form>
</div>
{% else %}
<p class="text-muted text-center">{{ _('No players assigned yet.') }}</p>
{% endfor %}
</div>
{% if registered_players %}
<hr class="section-divider">
<h5 class="mb-2"><i class="fas fa-user-plus"></i> {{ _('Add Player') }}</h5>
<form method="POST" action="{{ url_for('tryouts.add_to_team', tryout_id=tryout.id, team_id=team.team.id) }}" class="form-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<select name="player_id" class="form-select" required style="flex:1;">
<option value="">{{ _('Select player...') }}</option>
{% for p in registered_players %}
{% if p.id not in team.members | map(attribute='player') | map(attribute='id') | list %}
<option value="{{ p.id }}">{{ p.username }}</option>
{% endif %}
{% endfor %}
</select>
{% if positions %}
<select name="position" class="form-select mx-2">
<option value="">{{ _('-- Select Position --') }}</option>
{% for pos in positions %}
<option value="{{ pos }}">{{ pos }}</option>
{% endfor %}
</select>
{% else %}
<input type="text" name="position" placeholder="{{ _('Position') }}" class="form-input mx-2" style="max-width:140px;">
{% endif %}
<button type="submit" class="btn btn-sm btn-primary">{{ _('Add') }}</button>
</form>
{% endif %}
<hr class="section-divider" style="border-color: var(--danger);">
<div class="text-center">
<form method="POST" action="{{ url_for('tryouts.delete_team', tryout_id=tryout.id, team_id=team.team.id) }}" class="inline-form" data-confirm="{{ _('Delete this team? Players already in the team will be removed and matches referencing it will be unlinked.') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-danger">{{ _('Delete Team') }}</button>
</form>
</div>
</div>
</div>
</div>
{% endif %}
{% else %} {% else %}
<p class="text-muted text-center">{{ _('No teams created yet.') }}</p> <p class="text-muted text-center">{{ _('No teams created yet.') }}</p>
{% endfor %} {% endfor %}
@@ -574,8 +645,56 @@
transform: scale(1.08); transform: scale(1.08);
box-shadow: 0 1px 3px rgba(0,0,0,0.12); box-shadow: 0 1px 3px rgba(0,0,0,0.12);
} }
/* Team card header — edit button aligned right */
.team-card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.team-card-header .team-name {
margin-bottom: 0;
}
/* Roster rows inside modal */
.roster-rows {
display: flex;
flex-direction: column;
gap: 6px;
}
.roster-row {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 8px;
background: var(--gray-50);
border-radius: var(--radius-sm);
}
.roster-player-name {
flex: 1;
font-size: 0.9rem;
font-weight: 500;
}
.roster-role-select {
width: auto;
font-size: 0.75rem;
padding: 1px 4px;
}
.roster-remove-btn {
padding: 1px 5px;
font-size: 0.7rem;
}
[data-theme="dark"] .roster-row {
background: var(--bg-tertiary);
}
</style> </style>
<script nonce="{{ csp_nonce }}"> <script nonce="{{ csp_nonce }}">
function showModal(modalId) {
document.getElementById(modalId).classList.remove('hidden');
}
function hideModal(modalId) {
document.getElementById(modalId).classList.add('hidden');
}
function showCreateTeam() { document.getElementById('createTeamForm').classList.remove('hidden'); } function showCreateTeam() { document.getElementById('createTeamForm').classList.remove('hidden'); }
function hideCreateTeam() { document.getElementById('createTeamForm').classList.add('hidden'); } function hideCreateTeam() { document.getElementById('createTeamForm').classList.add('hidden'); }
@@ -633,6 +752,12 @@ document.addEventListener('DOMContentLoaded', function() {
registerActions({ registerActions({
'show-create-team': showCreateTeam, 'show-create-team': showCreateTeam,
'hide-create-team': hideCreateTeam, 'hide-create-team': hideCreateTeam,
'show-modal': function (element) {
showModal(element.getAttribute('data-modal-id'));
},
'hide-modal': function (element) {
hideModal(element.getAttribute('data-modal-id'));
},
'toggle-tryout-presence': function (element) { 'toggle-tryout-presence': function (element) {
toggleTryoutPresence( toggleTryoutPresence(
element.getAttribute('data-match-id'), element.getAttribute('data-match-id'),
+12
View File
@@ -605,6 +605,18 @@ class TryoutTeamMemberSchema(StripMixin):
) )
class TryoutTeamMemberPositionSchema(StripMixin):
"""Update a team member's in-game position (role)."""
position = fields.String(
load_default='',
validate=validate.Length(
max=50,
error=_l('Position must be 50 characters or less.'),
),
)
class NoteContentSchema(StripMixin): class NoteContentSchema(StripMixin):
"""Bounded text stored as a team or personal coaching note.""" """Bounded text stored as a team or personal coaching note."""