Files
RosterChief/events/admin.py
Bernard Siebens 53b3c56594 Add Coach mode C3: game line-up, the last of the six coach screens
New models (events/models.py): Lineup (one per event), LineupUnit (Line 1,
Defence pair 1, ... -- coach-entered labels, no fixed sport structure since
neither Club nor Team carries one), LineupSlot (one position, optionally
filled by a member). events/services/lineup.py's publish_lineup is the
reason Attendance.AttendanceStatus.SELECTED/NOT_SELECTED existed at all --
publishing flips every slotted member to SELECTED and every other available
(non-out, non-silent) roster member to NOT_SELECTED, then notifies only the
selected players.

Placement is a native <select> per slot, batch-saved with one "Save
line-up" submit, not the design mock's drag-and-drop -- this codebase has
no established htmx interaction pattern yet (htmx.js is loaded but nothing
uses it) to build a live per-tap version on, and a reliable plain form beats
a first, unproven real-time interaction for an already-large screen.
place_member handles "swap" semantics for it: placing a member vacates any
other slot of theirs in the same lineup, bumping whoever was already in the
target slot back to the available pool.

Wires the missing pieces from C1's own docstring: the "needs you" line-up-
not-published row and the tonight card's "Line-up" button, both deferred
in that stage specifically because this model didn't exist yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
2026-08-21 22:47:42 +02:00

148 lines
5.8 KiB
Python

from django import forms
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from .models import Attendance, Competition, Event, EventReferee, EventSeries, Lineup, LineupSlot, LineupUnit, Location, Opponent
@admin.register(Opponent)
class OpponentAdmin(admin.ModelAdmin):
list_display = ["name", "club"]
list_filter = ["club"]
search_fields = ["name"]
@admin.register(Location)
class LocationAdmin(admin.ModelAdmin):
list_display = ["name", "city", "club"]
list_filter = ["club"]
search_fields = ["name", "city"]
class AttendanceInline(admin.TabularInline):
model = Attendance
extra = 0
raw_id_fields = ["member"]
class EventRefereeInline(admin.TabularInline):
model = EventReferee
extra = 0
raw_id_fields = ["member", "assigned_by"]
@admin.register(EventSeries)
class EventSeriesAdmin(admin.ModelAdmin):
list_display = ["title", "kind", "rrule", "dtstart", "until", "generated_until", "club"]
list_filter = ["kind", "club"]
search_fields = ["title"]
autocomplete_fields = ["location", "opponent", "teams", "groups", "invited_members", "excluded_members"]
fieldsets = [
[None, {"fields": ["title", "kind"]}],
[_("Recurrence"), {"fields": ["rrule", "dtstart", "until", "excluded_dates", "generated_until"]}],
[_("Timing"), {"fields": ["duration", "gathering_offset", "deadline_offset"]}],
[_("Audience"), {"fields": ["teams", "groups", "club_wide", "invited_members", "excluded_members"]}],
[_("Where"), {"fields": ["location", "opponent"]}],
]
class EventAdminForm(forms.ModelForm):
"""The `competition` field is a plain CharField on Event (it just stores a
name), but the admin should only ever offer a competition this club is
actually allowed to use -- one whose feature flag is active for it, set
from the control panel's Features page. A competition with no flag never
appears at all."""
class Meta:
model = Event
fields = [
"title", "kind", "season", "series", "detached", "cancelled", "teams", "groups", "club_wide", "invited_members", "excluded_members",
"start", "end", "gathering", "deadline", "location", "opponent",
"competition", "external_game_id", "score_for", "score_against", "is_live", "max_referees",
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
club = self.instance.club if self.instance.club_id else None
competitions = Competition.objects.filter(flag__isnull=False).select_related("flag")
if club is not None:
competitions = [competition for competition in competitions if competition.flag.is_active_for_club(club)]
self.fields["competition"] = forms.ChoiceField(
choices=[("", "---------"), *[(competition.name, competition.name) for competition in competitions]],
required=False,
label=self.fields["competition"].label,
help_text=_("Only competitions whose feature flag is active for this club are offered here."),
)
@admin.register(Event)
class EventAdmin(admin.ModelAdmin):
form = EventAdminForm
list_display = ["title", "kind", "start", "season", "series", "detached", "club"]
list_filter = ["kind", "club", "teams", "detached", "cancelled"]
search_fields = ["title"]
date_hierarchy = "start"
autocomplete_fields = ["season", "series", "location", "opponent", "teams", "groups", "invited_members", "excluded_members"]
inlines = [AttendanceInline, EventRefereeInline]
fieldsets = [
[None, {"fields": ["title", "kind", "season"]}],
[_("Series"), {"fields": ["series", "detached", "cancelled"]}],
[_("Audience"), {"fields": ["teams", "groups", "club_wide", "invited_members", "excluded_members"]}],
[_("When"), {"fields": ["start", "end", "gathering", "deadline"]}],
[_("Where"), {"fields": ["location", "opponent"]}],
[_("Game"), {"fields": ["competition", "external_game_id", "score_for", "score_against", "is_live", "max_referees"]}],
]
@admin.register(Competition)
class CompetitionAdmin(admin.ModelAdmin):
list_display = ["name", "sport_type", "module", "flag"]
list_filter = ["sport_type"]
search_fields = ["name", "module"]
raw_id_fields = ["flag"]
@admin.register(Attendance)
class AttendanceAdmin(admin.ModelAdmin):
list_display = ["event", "member", "status", "showed_up"]
list_filter = ["status", "showed_up", "event__kind"]
search_fields = ["event__title", "member__first_name", "member__last_name"]
raw_id_fields = ["event", "member"]
@admin.register(Lineup)
class LineupAdmin(admin.ModelAdmin):
list_display = ["event", "team", "published_at", "created_by"]
list_filter = ["team"]
search_fields = ["event__title", "team__name"]
raw_id_fields = ["event", "team", "created_by"]
@admin.register(LineupUnit)
class LineupUnitAdmin(admin.ModelAdmin):
list_display = ["lineup", "label", "ordering"]
search_fields = ["lineup__event__title", "label"]
raw_id_fields = ["lineup"]
@admin.register(LineupSlot)
class LineupSlotAdmin(admin.ModelAdmin):
list_display = ["unit", "ordering", "member"]
search_fields = ["unit__label", "member__first_name", "member__last_name"]
raw_id_fields = ["unit", "member"]
@admin.register(EventReferee)
class EventRefereeAdmin(admin.ModelAdmin):
list_display = ["event", "display_name", "fee", "km", "total_payable", "assigned_by"]
search_fields = ["event__title", "member__first_name", "member__last_name", "external_name"]
raw_id_fields = ["event", "member", "assigned_by"]
@admin.display(description=_("referee"))
def display_name(self, obj):
return obj.display_name
@admin.display(description=_("total payable"))
def total_payable(self, obj):
return obj.total_payable