4 Commits
15 changed files with 1662 additions and 67 deletions
+1
View File
@@ -20,6 +20,7 @@ from app.models._constants import (
GAME_PLATFORMS, GAME_PLATFORMS,
PLATFORM_CODES, PLATFORM_CODES,
TRN_URLS, TRN_URLS,
EVALUATION_CRITERIA,
) )
# ========================================================================= # =========================================================================
+10
View File
@@ -37,3 +37,13 @@ tryout_coaches = db.Table(
'coach_id', db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), primary_key=True 'coach_id', db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), primary_key=True
), ),
) )
tryout_managers = db.Table(
'tryout_managers',
db.Column(
'tryout_id', db.Integer, db.ForeignKey('tryouts.id', ondelete='CASCADE'), primary_key=True
),
db.Column(
'manager_id', db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), primary_key=True
),
)
+15
View File
@@ -9,6 +9,21 @@ Contains game lists, position mappings, platform codes, and TRN URL templates.
USER_TYPES = ['admin', 'manager', 'coach', 'player', 'scout'] USER_TYPES = ['admin', 'manager', 'coach', 'player', 'scout']
# Ordered list of (field_name, human_label) pairs for the player evaluation
# score criteria. Kept in a single place so the evaluation forms, batch
# evaluation page, and any future reporting all stay in sync.
EVALUATION_CRITERIA = [
('mecanics_score', 'Mecanics'),
('cohesion_score', 'Cohesion'),
('communication_score', 'Communication'),
('gamesense_score', 'Gamesense'),
('versatility_score', 'Versatility'),
('discipline_score', 'Discipline'),
('analysis_score', 'Analysis'),
('sport_ethics_score', 'Sport Ethics'),
('mental_score', 'Mental'),
]
ESPORT_GAMES = [ ESPORT_GAMES = [
'Valorant', 'Valorant',
'League of Legends', 'League of Legends',
+18 -3
View File
@@ -1,7 +1,7 @@
"""Tryout event for player evaluations and team formation.""" """Tryout event for player evaluations and team formation."""
from app.extensions import db from app.extensions import db
from app.models._associations import tryout_coaches from app.models._associations import tryout_coaches, tryout_managers
from app.time_utils import utc_now_naive from app.time_utils import utc_now_naive
@@ -20,16 +20,17 @@ class Tryout(db.Model):
max_players = db.Column(db.Integer, nullable=True) max_players = db.Column(db.Integer, nullable=True)
created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) 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) target_org_team_id = db.Column(db.Integer, db.ForeignKey('org_teams.id'), nullable=True)
manager_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) manager_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) # deprecated, kept for migration
coach_id = db.Column( coach_id = db.Column(
db.Integer, db.ForeignKey('users.id'), nullable=True db.Integer, db.ForeignKey('users.id'), nullable=True
) # deprecated, kept for migration ) # deprecated, kept for migration
created_at = db.Column(db.DateTime, default=utc_now_naive) created_at = db.Column(db.DateTime, default=utc_now_naive)
creator = db.relationship('User', foreign_keys=[created_by], backref='created_tryouts') creator = db.relationship('User', foreign_keys=[created_by], backref='created_tryouts')
manager = db.relationship('User', foreign_keys=[manager_id], backref='managed_tryouts') manager = db.relationship('User', foreign_keys=[manager_id], backref='managed_tryouts') # deprecated
coach = db.relationship('User', foreign_keys=[coach_id], backref='_deprecated_coached_tryouts') coach = db.relationship('User', foreign_keys=[coach_id], backref='_deprecated_coached_tryouts')
coaches = db.relationship('User', secondary=tryout_coaches, backref='coached_tryouts') coaches = db.relationship('User', secondary=tryout_coaches, backref='coached_tryouts')
managers = db.relationship('User', secondary=tryout_managers, backref='managed_tryouts_m2m')
registrations = db.relationship('TryoutRegistration', backref='tryout', lazy='dynamic') registrations = db.relationship('TryoutRegistration', backref='tryout', lazy='dynamic')
evaluations = db.relationship('Evaluation', backref='tryout', lazy='dynamic') evaluations = db.relationship('Evaluation', backref='tryout', lazy='dynamic')
teams = db.relationship('Team', backref='tryout', lazy='dynamic') teams = db.relationship('Team', backref='tryout', lazy='dynamic')
@@ -47,3 +48,17 @@ class Tryout(db.Model):
if self.end_date is not None: if self.end_date is not None:
return self.end_date < today return self.end_date < today
return self.date < today return self.date < today
def get_managers(self):
"""Managers attached to this tryout, both legacy and many-to-many."""
manager_list = list(self.managers)
if not manager_list and self.manager:
return [self.manager]
return manager_list
def get_coaches(self):
"""Coaches attached to this tryout, both legacy and many-to-many."""
coach_list = list(self.coaches)
if not coach_list and self.coach:
return [self.coach]
return coach_list
+12 -2
View File
@@ -21,7 +21,11 @@ class Manager(User):
return True return True
def can_manage_this_tryout(self, tryout): def can_manage_this_tryout(self, tryout):
return tryout.created_by == self.id or tryout.manager_id == self.id return (
tryout.created_by == self.id
or tryout.manager_id == self.id
or any(m.id == self.id for m in tryout.managers)
)
def can_manage_this_org_team(self, org_team): def can_manage_this_org_team(self, org_team):
return True return True
@@ -32,7 +36,13 @@ class Manager(User):
from app.models.tryout.tryout import Tryout from app.models.tryout.tryout import Tryout
return ( return (
Tryout.query.filter(or_(Tryout.created_by == self.id, Tryout.manager_id == self.id)) Tryout.query.filter(
or_(
Tryout.created_by == self.id,
Tryout.manager_id == self.id,
Tryout.managers.any(id=self.id),
)
)
.order_by(Tryout.date) .order_by(Tryout.date)
.all() .all()
) )
+99
View File
@@ -7,6 +7,14 @@ from flask import Blueprint, flash, redirect, render_template, request, url_for
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_login import current_user, login_required from flask_login import current_user, login_required
from marshmallow import ValidationError from marshmallow import ValidationError
from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_required, current_user
from app.extensions import db
from app.models import (
Admin, Coach, Manager, Player,
User, Tryout, Evaluation, TryoutRegistration,
OrgTeam, GAME_POSITIONS, EVALUATION_CRITERIA,
)
from sqlalchemy import func from sqlalchemy import func
from sqlalchemy.orm import aliased from sqlalchemy.orm import aliased
@@ -245,3 +253,94 @@ def players_to_evaluate(tryout_id):
] ]
return render_template('pages/players_to_evaluate.html', tryout=tryout, players=players) return render_template('pages/players_to_evaluate.html', tryout=tryout, players=players)
@evaluations_bp.route('/<int:tryout_id>/batch', methods=['GET', 'POST'])
@login_required
def batch_evaluate(tryout_id):
"""Evaluate multiple players at once in a tryout.
GET renders a single form listing every selected player with their
evaluation criteria. POST saves (creates or updates) all of them.
"""
if not current_user.can_evaluate():
flash('You do not have permission to evaluate players.', 'danger')
return redirect(url_for('main.dashboard'))
tryout = Tryout.query.get_or_404(tryout_id)
if not current_user.can_manage_this_tryout(tryout):
flash('You do not have permission to evaluate players in this tryout.', 'danger')
return redirect(url_for('tryouts.list_tryouts'))
# Resolve selected player ids (query string on GET, hidden fields on POST).
player_ids = []
for raw in request.values.getlist('player_ids'):
try:
pid = int(raw)
except (ValueError, TypeError):
continue
if pid not in player_ids:
player_ids.append(pid)
if not player_ids:
flash('Please select at least one player to evaluate.', 'warning')
return redirect(url_for('evaluations.players_to_evaluate', tryout_id=tryout_id))
players = []
for pid in player_ids:
player = User.query.get(pid)
if not player or not isinstance(player, Player):
continue
is_registered = TryoutRegistration.query.filter_by(
tryout_id=tryout_id, player_id=pid,
).first() is not None
if not is_registered:
continue
existing = Evaluation.query.filter_by(
tryout_id=tryout_id, player_id=pid, evaluator_id=current_user.id,
).first()
existing_scores = {
field_name: getattr(existing, field_name) if existing else None
for field_name, _ in EVALUATION_CRITERIA
}
players.append({
'player': player,
'existing': existing,
'existing_scores': existing_scores,
})
if not players:
flash('No valid players selected for evaluation.', 'danger')
return redirect(url_for('evaluations.players_to_evaluate', tryout_id=tryout_id))
if request.method == 'POST':
saved = 0
for entry in players:
pid = entry['player'].id
scores = {
field_name: validate_score(request.form.get(f'{field_name}_{pid}'))
for field_name, _ in EVALUATION_CRITERIA
}
comments = request.form.get(f'comments_{pid}')
position = request.form.get(f'position_recommendation_{pid}')
existing = entry['existing']
if existing:
_apply_evaluation(existing, scores, comments, position)
else:
evaluation = Evaluation(
tryout_id=tryout_id, player_id=pid,
evaluator_id=current_user.id,
)
_apply_evaluation(evaluation, scores, comments, position)
db.session.add(evaluation)
saved += 1
db.session.commit()
flash(f'Saved evaluations for {saved} player(s).', 'success')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
return render_template('pages/batch_evaluate.html',
tryout=tryout, players=players,
evaluation_criteria=EVALUATION_CRITERIA,
game_positions=GAME_POSITIONS)
+18 -5
View File
@@ -51,7 +51,7 @@ def can_manage():
def tryout_form_payload(): def tryout_form_payload():
"""The tryout form, shaped for marshmallow (ARCH-005).""" """The tryout form, shaped for marshmallow (ARCH-005)."""
return form_payload(list_fields=('coach_ids',), optional_blank=()) return form_payload(list_fields=('coach_ids', 'manager_ids'), optional_blank=())
def coaches_from_ids(coach_ids): def coaches_from_ids(coach_ids):
@@ -67,6 +67,13 @@ def coaches_from_ids(coach_ids):
return User.query.filter(User.id.in_(coach_ids), User.role == 'coach').all() return User.query.filter(User.id.in_(coach_ids), User.role == 'coach').all()
def managers_from_ids(manager_ids):
"""The manager accounts behind these ids, filtered by role."""
if not manager_ids:
return []
return User.query.filter(User.id.in_(manager_ids), User.role == 'manager').all()
def _users_by_id(user_ids): def _users_by_id(user_ids):
"""Load these users in one query, keyed by id. """Load these users in one query, keyed by id.
@@ -160,12 +167,12 @@ def create_tryout():
created_by=current_user.id, created_by=current_user.id,
status='upcoming', status='upcoming',
target_org_team_id=data['target_org_team_id'], target_org_team_id=data['target_org_team_id'],
manager_id=data['manager_id'],
) )
db.session.add(tryout) db.session.add(tryout)
db.session.flush() db.session.flush()
tryout.coaches = coaches_from_ids(data['coach_ids']) tryout.coaches = coaches_from_ids(data['coach_ids'])
tryout.managers = managers_from_ids(data['manager_ids'])
db.session.commit() db.session.commit()
flash(_('Tryout created successfully!'), 'success') flash(_('Tryout created successfully!'), 'success')
@@ -221,8 +228,14 @@ def edit_tryout(tryout_id):
tryout.location = data['location'] tryout.location = data['location']
tryout.max_players = data['max_players'] tryout.max_players = data['max_players']
tryout.target_org_team_id = data['target_org_team_id'] tryout.target_org_team_id = data['target_org_team_id']
tryout.manager_id = data['manager_id']
tryout.coaches = coaches_from_ids(data['coach_ids']) # Only update staff lists when the form explicitly sends them.
# An absent checkbox group (all unchecked or JS failed) means
# \"don't change\", not \"remove everyone\".
if 'coach_ids' in request.form:
tryout.coaches = coaches_from_ids(data['coach_ids'])
if 'manager_ids' in request.form:
tryout.managers = managers_from_ids(data['manager_ids'])
db.session.commit() db.session.commit()
flash(_('Tryout updated successfully!'), 'success') flash(_('Tryout updated successfully!'), 'success')
@@ -241,7 +254,7 @@ def view_tryout(tryout_id):
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):
can_view = tryout.created_by == current_user.id or tryout.manager_id == current_user.id can_view = current_user.can_manage_this_tryout(tryout)
elif isinstance(current_user, Coach): 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):
+1270
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -2037,3 +2037,30 @@ a:hover { color: var(--primary-dark); }
.honeypot { .honeypot {
display: none; display: none;
} }
/* Batch Evaluation - 2 cards wide layout */
.batch-eval-form {
max-width: none;
}
.batch-eval-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
margin-bottom: 20px;
}
.batch-eval-grid .card {
margin-bottom: 0;
}
/* Allow criteria rows to wrap for a 3x3 grid inside each card */
.batch-eval-grid .form-row {
flex-wrap: wrap;
}
@media (max-width: 1100px) {
.batch-eval-grid {
grid-template-columns: 1fr;
}
}
+85
View File
@@ -0,0 +1,85 @@
{% extends "layouts/base.html" %}
{% block title %}Evaluate Players - UdeS team manager{% endblock %}
{% block page_title %}Evaluate Players{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}">{{ tryout.title }}</a> / Evaluate</span>{% endblock %}
{% block content %}
<form method="POST" action="{{ url_for('evaluations.batch_evaluate', tryout_id=tryout.id) }}" class="form batch-eval-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
{% for entry in players %}
<input type="hidden" name="player_ids" value="{{ entry.player.id }}"/>
{% endfor %}
{% set positions = game_positions.get(tryout.game, []) %}
<div class="batch-eval-grid">
{% for entry in players %}
<div class="card">
<div class="card-header">
<h3>
<i class="fas fa-user"></i> {{ entry.player.username }}
{% if entry.existing %}
<span class="badge badge-success">Already Evaluated</span>
{% else %}
<span class="badge badge-warning">Not Evaluated</span>
{% endif %}
</h3>
</div>
<div class="card-body">
<div class="eval-player-info mb-4">
<div class="user-avatar avatar-lg">{{ entry.player.username[:2] | upper }}</div>
<div>
<h3>{{ entry.player.username }}</h3>
<p class="text-muted">{{ entry.player.email }} | {{ entry.player.phone or 'No phone' }}</p>
</div>
</div>
{% set pid = entry.player.id %}
{% set existing = entry.existing %}
{% set existing_scores = entry.existing_scores %}
<div class="form-row">
{% for field_name, label in evaluation_criteria %}
<div class="form-group col-4">
<label for="{{ field_name }}_{{ pid }}">{{ label }} (1-10)</label>
<div class="score-input">
<input type="range" id="{{ field_name }}_{{ pid }}" name="{{ field_name }}_{{ pid }}" min="1" max="10" value="{{ existing_scores[field_name] or 5 }}" data-mirror>
<span class="range-value">{{ existing_scores[field_name] or 5 }}</span>
</div>
</div>
{% endfor %}
</div>
<div class="form-row">
<div class="form-group col-12">
<label for="position_recommendation_{{ pid }}">Recommended Position</label>
{% if positions %}
<select id="position_recommendation_{{ pid }}" name="position_recommendation_{{ pid }}" class="form-select">
<option value="">-- Select Position --</option>
{% for pos in positions %}
<option value="{{ pos }}" {% if existing and existing.position_recommendation == pos %}selected{% endif %}>{{ pos }}</option>
{% endfor %}
</select>
{% else %}
<input type="text" id="position_recommendation_{{ pid }}" name="position_recommendation_{{ pid }}" value="{{ existing.position_recommendation if existing else '' }}" placeholder="Enter position (optional)">
{% endif %}
</div>
</div>
<div class="form-group">
<label for="comments_{{ pid }}">Comments</label>
<textarea id="comments_{{ pid }}" name="comments_{{ pid }}" rows="3" placeholder="Enter your evaluation notes...">{{ existing.comments if existing else '' }}</textarea>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="form-actions">
<a href="{{ url_for('evaluations.players_to_evaluate', tryout_id=tryout.id) }}" class="btn btn-secondary">Back</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Save All Evaluations
</button>
</div>
</form>
{% endblock %}
+74 -44
View File
@@ -6,54 +6,84 @@
{% block content %} {% block content %}
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-users"></i> Players in {{ tryout.title }}</h3> <h3><i class="fas fa-users"></i> Select players to evaluate in {{ tryout.title }}</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="table-container"> <p class="text-muted mb-3">Choose the players you want to evaluate, then load all of them on a single page.</p>
<table class="table"> <form method="GET" action="{{ url_for('evaluations.batch_evaluate', tryout_id=tryout.id) }}">
<thead> <div class="table-container">
<tr> <table class="table">
<th>{{ _('Player') }}</th> <thead>
<th>{{ _('Contact') }}</th> <tr>
<th>{{ _('Attendance') }}</th> <th><input type="checkbox" id="select-all" data-action="toggle-all"></th>
<th>{{ _('Status') }}</th> <th>Player</th>
<th>{{ _('Actions') }}</th> <th>Contact</th>
</tr> <th>Attendance</th>
</thead> <th>Status</th>
<tbody> <th>Actions</th>
{% for entry in players %} </tr>
<tr> </thead>
<td> <tbody>
<div class="user-mini"> {% for entry in players %}
<tr>
<td>
<input type="checkbox" name="player_ids" value="{{ entry.player.id }}" class="player-check">
</td>
<td>
<div class="user-mini">
<div class="avatar-sm">{{ entry.player.username[:2] | upper }}</div> <div class="avatar-sm">{{ entry.player.username[:2] | upper }}</div>
<span>{{ entry.player.username }}</span> <span>{{ entry.player.username }}</span>
</div> </div>
</td> </td>
<td>{{ entry.player.email }}</td> <td>{{ entry.player.email }}</td>
<td> <td>
<span class="badge badge-{{ entry.registration.status }}">{{ entry.registration.status }}</span> <span class="badge badge-{{ entry.registration.status }}">{{ entry.registration.status }}</span>
</td> </td>
<td> <td>
{% if entry.evaluated %} {% if entry.evaluated %}
<span class="badge badge-success">Evaluated</span> <span class="badge badge-success">Evaluated</span>
{% else %} {% else %}
<span class="badge badge-warning">Not Evaluated</span> <span class="badge badge-warning">Not Evaluated</span>
{% endif %} {% endif %}
</td> </td>
<td> <td>
<a href="{{ url_for('evaluations.evaluate_player', tryout_id=tryout.id, player_id=entry.player.id) }}" class="btn btn-sm btn-primary"> <a href="{{ url_for('evaluations.evaluate_player', tryout_id=tryout.id, player_id=entry.player.id) }}" class="btn btn-sm btn-outline">
<i class="fas fa-clipboard"></i> {% if entry.evaluated %}View/Edit{% else %}Evaluate{% endif %} <i class="fas fa-clipboard"></i> {% if entry.evaluated %}View/Edit{% else %}Single{% endif %}
</a> </a>
</td> </td>
</tr> </tr>
{% else %} {% else %}
<tr> <tr>
<td colspan="5" class="text-center">{{ _('No players registered for this tryout.') }}</td> <td colspan="6" class="text-center">No players registered for this tryout.</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="form-actions">
<a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}" class="btn btn-secondary">Cancel</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-clipboard-check"></i> Evaluate Selected
</button>
</div>
</form>
</div> </div>
</div> </div>
{% endblock %}
{% block scripts %}
<script nonce="{{ csp_nonce }}">
function toggleAll() {
var boxes = document.querySelectorAll('.player-check');
var master = document.getElementById('select-all');
for (var i = 0; i < boxes.length; i++) {
boxes[i].checked = master.checked;
}
}
registerActions({
'toggle-all': toggleAll,
});
</script>
{% endblock %} {% endblock %}
+3
View File
@@ -300,6 +300,9 @@
</div> </div>
</div> </div>
{% endblock %}
{% block scripts %}
<script nonce="{{ csp_nonce }}"> <script nonce="{{ csp_nonce }}">
function showCreateForm() { function showCreateForm() {
document.getElementById('createTeamForm').classList.remove('hidden'); document.getElementById('createTeamForm').classList.remove('hidden');
+14 -7
View File
@@ -67,15 +67,22 @@
</select> </select>
</div> </div>
<div class="form-group col-6"> <div class="form-group col-6">
<label for="manager_id">{{ _('Assigned Manager') }}</label> <label>{{ _('Assigned Managers') }}</label>
<select id="manager_id" name="manager_id" class="form-select"> <div class="checkbox-grid">
<option value="">{{ _('-- No manager assigned --') }}</option>
{% for manager in managers %} {% for manager in managers %}
<option value="{{ manager.id }}" {% if tryout and tryout.manager_id == manager.id %}selected{% endif %}> {% set is_checked = false %}
{{ manager.username }} {% if tryout %}
</option> {% for m in tryout.get_managers() %}
{% if m.id == manager.id %}{% set is_checked = true %}{% endif %}
{% endfor %}
{% endif %}
<label class="checkbox-label">
<input type="checkbox" name="manager_ids" value="{{ manager.id }}" {% if is_checked %}checked{% endif %}>
<span>{{ manager.username }}</span>
</label>
{% endfor %} {% endfor %}
</select> </div>
<small class="text-muted">{{ _('Select one or more managers for this tryout.') }}</small>
</div> </div>
</div> </div>
<div class="form-row"> <div class="form-row">
+15 -6
View File
@@ -70,16 +70,22 @@
<span class="detail-value">{{ tryout.target_org_team.name if tryout.target_org_team else 'Not specified' }}</span> <span class="detail-value">{{ tryout.target_org_team.name if tryout.target_org_team else 'Not specified' }}</span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="detail-label">Manager</span> <span class="detail-label">Manager(s)</span>
<span class="detail-value">{{ tryout.manager.username if tryout.manager else 'Not assigned' }}</span> <span class="detail-value">
{% set mgrs = tryout.get_managers() %}
{% if mgrs %}
{{ mgrs | map(attribute='username') | join(', ') }}
{% else %}
Not assigned
{% endif %}
</span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="detail-label">Coaches</span> <span class="detail-label">Coaches</span>
<span class="detail-value"> <span class="detail-value">
{% if tryout.coaches %} {% set cos = tryout.get_coaches() %}
{{ tryout.coaches | map(attribute='username') | join(', ') }} {% if cos %}
{% elif tryout.coach %} {{ cos | map(attribute='username') | join(', ') }}
{{ tryout.coach.username }}
{% else %} {% else %}
Not assigned Not assigned
{% endif %} {% endif %}
@@ -534,6 +540,9 @@
{% endif %} {% endif %}
</div> </div>
{% endblock %}
{% block scripts %}
<style> <style>
.presence-toggle-btn { .presence-toggle-btn {
padding: 2px 7px; padding: 2px 7px;
+1
View File
@@ -949,6 +949,7 @@ class TryoutSchema(StripMixin):
target_org_team_id = fields.Integer(allow_none=True, load_default=None) target_org_team_id = fields.Integer(allow_none=True, load_default=None)
manager_id = fields.Integer(allow_none=True, load_default=None) manager_id = fields.Integer(allow_none=True, load_default=None)
coach_ids = fields.List(fields.Integer(), load_default=list) coach_ids = fields.List(fields.Integer(), load_default=list)
manager_ids = fields.List(fields.Integer(), load_default=list)
@validates_schema @validates_schema
def validate_span(self, data, **kwargs): def validate_span(self, data, **kwargs):