Let referee levels inherit from a lower level instead of relying on ordering alone

RefereeLevel.inherits_from chains levels together so a higher tier is
automatically eligible for everything a linked lower tier covers,
transitively, without hand-duplicating teams onto every level. Kept
the ordering field (still drives display order) but eligibility
everywhere (RefereeProfile.eligible_teams, events.services.referees,
the team detail page's eligible-referees list) now reads through
RefereeLevel.eligible_team_ids, which walks the inherits_from chain.
clean() rejects a loop, including an indirect one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
This commit is contained in:
2026-08-20 22:42:18 +02:00
parent 5889b3740e
commit 66d7de820f
10 changed files with 235 additions and 16 deletions

View File

@@ -30,7 +30,7 @@ from django.utils.translation import gettext_lazy as _
from events.models import ASSUMED_EVENT_DURATION, Event, EventReferee
from events.services.attendance import effective_members
from members.models import Member
from teams.models import Team
from teams.models import RefereeLevel, Team
class RefereeAssignmentError(Exception):
@@ -52,17 +52,24 @@ def needs_referee_management(event) -> bool:
def eligible_referees(event):
"""Members who could referee `event`: their RefereeProfile has a level
qualifying for one of its club-managed teams, and is currently valid,
minus whoever is already assigned. Empty unless needs_referee_management."""
qualifying for one of its club-managed teams (directly, or via whatever
that level inherits from), and is currently valid, minus whoever is
already assigned. Empty unless needs_referee_management.
Levels resolved to ids first (RefereeLevel.eligible_team_ids can't be
expressed as a single ORM lookup once inheritance is transitive) rather
than the old flat `level__teams__id__in` filter -- a club has a handful
of levels, so this stays cheap."""
if not needs_referee_management(event):
return Member.objects.none()
team_ids = list(event.teams.filter(referee_management=Team.RefereeManagement.CLUB).values_list("id", flat=True))
team_ids = set(event.teams.filter(referee_management=Team.RefereeManagement.CLUB).values_list("id", flat=True))
qualifying_level_ids = [level.pk for level in RefereeLevel.objects.filter(club=event.club) if level.eligible_team_ids() & team_ids]
assigned_ids = event.referees.values_list("member_id", flat=True)
today = timezone.localdate()
return (
Member.objects.filter(referee_profile__level__teams__id__in=team_ids, referee_profile__valid_until__gte=today)
Member.objects.filter(referee_profile__level_id__in=qualifying_level_ids, referee_profile__valid_until__gte=today)
.exclude(pk__in=assigned_ids)
.distinct()
)