Fix the referee PDF's card background, make locations searchable, and add game end times to the API

Referee PDF: the info-card background used CSS color-mix(), which WeasyPrint
doesn't support -- the rule was silently dropped, leaving the card with no
background at all. Computed in Python instead (management/pdf.py) and baked
into the template as a plain hex value; the tint is based on the club's
primary_color, falling back to secondary_color when primary is itself (near)
black or white, where a straight tint would be invisible or too harsh.

Event forms: the location picker now shows "Name — City" (plus the country
when it isn't Belgium) and is searchable by name or city, reusing the
existing single-select searchable-select.js widget.

Games API: GameOut now carries `end` (explicit, or start + 2h when a GAME was
saved without one -- Event.save() sets this, never overwriting an explicit
end; other event kinds are untouched). /games/upcoming/ now includes
anything not yet finished rather than only things that haven't started, so a
game already in progress keeps showing up until its window closes; `status`
was adjusted to match so a game returned there never calls itself
"finished".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 23:44:11 +02:00
parent 5dcffefb28
commit e737de9140
11 changed files with 222 additions and 24 deletions

View File

@@ -1,3 +1,4 @@
import datetime
from decimal import Decimal
from django.conf import settings
@@ -11,6 +12,11 @@ from members.models import Member
from rosterchief.base import ClubScopedModel, UUIDModel, validate_club_scope
from teams.models import Team
#: How long a game is assumed to run when no explicit `end` is given -- set on
#: GAME events at save time (Event.save() below), and reused as a read-time-only
#: fallback for other event kinds by events.services.referees.event_window().
ASSUMED_EVENT_DURATION = datetime.timedelta(hours=2)
class Opponent(ClubScopedModel):
name = models.CharField(_("name"), max_length=255)
@@ -101,6 +107,11 @@ class Event(ClubScopedModel):
def clean(self):
validate_club_scope(self, self.club_id, same_club_fields=("season", "location", "opponent"))
def save(self, *args, **kwargs):
if self.kind == self.EventKind.GAME and self.end is None:
self.end = self.start + ASSUMED_EVENT_DURATION
super().save(*args, **kwargs)
@property
def is_home_game(self) -> bool:
"""Whether this game is being played at the club's own ground