diff --git a/app/models/_associations.py b/app/models/_associations.py index d41cc8b..a7eb124 100644 --- a/app/models/_associations.py +++ b/app/models/_associations.py @@ -37,3 +37,13 @@ tryout_coaches = db.Table( '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 + ), +) diff --git a/app/models/tryout/tryout.py b/app/models/tryout/tryout.py index 6a6cbf2..1f680d2 100644 --- a/app/models/tryout/tryout.py +++ b/app/models/tryout/tryout.py @@ -1,7 +1,7 @@ """Tryout event for player evaluations and team formation.""" 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 @@ -20,16 +20,17 @@ class Tryout(db.Model): 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) - 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( db.Integer, db.ForeignKey('users.id'), nullable=True ) # deprecated, kept for migration created_at = db.Column(db.DateTime, default=utc_now_naive) 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') 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') evaluations = db.relationship('Evaluation', 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: return self.end_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 diff --git a/app/models/user_model/manager.py b/app/models/user_model/manager.py index aa95fb3..501b111 100644 --- a/app/models/user_model/manager.py +++ b/app/models/user_model/manager.py @@ -21,7 +21,11 @@ class Manager(User): return True 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): return True @@ -32,7 +36,13 @@ class Manager(User): from app.models.tryout.tryout import Tryout 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) .all() ) diff --git a/app/routes/tryouts.py b/app/routes/tryouts.py index db2468c..e537463 100644 --- a/app/routes/tryouts.py +++ b/app/routes/tryouts.py @@ -51,7 +51,7 @@ def can_manage(): def tryout_form_payload(): """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): @@ -67,6 +67,13 @@ def coaches_from_ids(coach_ids): 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): """Load these users in one query, keyed by id. @@ -160,12 +167,12 @@ def create_tryout(): created_by=current_user.id, status='upcoming', target_org_team_id=data['target_org_team_id'], - manager_id=data['manager_id'], ) db.session.add(tryout) db.session.flush() tryout.coaches = coaches_from_ids(data['coach_ids']) + tryout.managers = managers_from_ids(data['manager_ids']) db.session.commit() flash(_('Tryout created successfully!'), 'success') @@ -221,8 +228,14 @@ def edit_tryout(tryout_id): tryout.location = data['location'] tryout.max_players = data['max_players'] 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() flash(_('Tryout updated successfully!'), 'success') @@ -241,7 +254,7 @@ def view_tryout(tryout_id): if isinstance(current_user, Admin): can_view = True 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): can_view = current_user.can_manage_this_tryout(tryout) elif isinstance(current_user, Player): diff --git a/app/templates/pages/batch_evaluate.html b/app/templates/pages/batch_evaluate.html index 815e763..0140f15 100644 --- a/app/templates/pages/batch_evaluate.html +++ b/app/templates/pages/batch_evaluate.html @@ -43,7 +43,7 @@
| + | Player | Contact | Attendance | @@ -70,12 +70,20 @@ - {% endblock %} diff --git a/app/templates/pages/teams.html b/app/templates/pages/teams.html index a4b56b6..c1e0d93 100644 --- a/app/templates/pages/teams.html +++ b/app/templates/pages/teams.html @@ -300,6 +300,9 @@ +{% endblock %} + +{% block scripts %}
|---|