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

@@ -94,6 +94,32 @@ class EventModelTests(EventsTestBase):
self.assertFalse(game_with_no_location.is_home_game)
self.assertFalse(training_at_home_ground.is_home_game)
def test_a_game_with_no_end_gets_a_two_hour_default_on_save(self):
game = self.make_event(kind=Event.EventKind.GAME, start=self.future)
self.assertEqual(game.end, self.future + timedelta(hours=2))
def test_an_explicit_end_is_not_overridden(self):
explicit_end = self.future + timedelta(hours=5)
game = self.make_event(kind=Event.EventKind.GAME, start=self.future, end=explicit_end)
self.assertEqual(game.end, explicit_end)
def test_a_non_game_event_is_left_with_no_end(self):
training = self.make_event(kind=Event.EventKind.TRAINING, start=self.future)
self.assertIsNone(training.end)
def test_re_saving_a_game_does_not_move_its_end(self):
game = self.make_event(kind=Event.EventKind.GAME, start=self.future)
original_end = game.end
game.title = "Renamed"
game.save()
self.assertEqual(game.end, original_end)
class CompetitionModelTests(EventsTestBase):
def test_sport_type_defaults_to_other(self):