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

@@ -226,6 +226,17 @@ class SponsorForm(forms.ModelForm):
return cleaned
def _location_label(location) -> str:
""""Name — City" for the location picker, plus the country when it isn't
Belgium (the club's home country and, in practice, nearly every location
a club will ever add) -- lets an admin tell two same-named venues apart,
or spot an away trip abroad, straight from the dropdown."""
label = f"{location.name}{location.city}"
if location.country.code != "BE":
label += f", {location.country.name}"
return label
class EventAudienceFormMixin:
"""Shared club/user-scoped audience fields for EventForm and EventSeriesForm:
teams restricted to the ones the requester manages (all of them for an
@@ -238,6 +249,7 @@ class EventAudienceFormMixin:
self.user = user
self.fields["teams"].queryset = Team.objects.filter(club=club) if is_club_admin(user, club) else teams_managed_by(user, club)
self.fields["location"].queryset = Location.objects.filter(club=club)
self.fields["location"].label_from_instance = _location_label
self.fields["opponent"].queryset = Opponent.objects.filter(club=club)
members = Member.objects.filter(member_of__club=club).distinct()
self.fields["invited_members"].queryset = members
@@ -253,6 +265,7 @@ _AUDIENCE_WIDGETS = {
"teams": forms.SelectMultiple(attrs={"data-searchable": "true", "data-search-placeholder": _("Type a team to search...")}),
"invited_members": forms.SelectMultiple(attrs={"data-searchable": "true", "data-search-placeholder": _("Type a name to search...")}),
"excluded_members": forms.SelectMultiple(attrs={"data-searchable": "true", "data-search-placeholder": _("Type a name to search...")}),
"location": forms.Select(attrs={"data-searchable": "true", "data-search-placeholder": _("Type a name or city to search...")}),
}