feat(events): event owner and cross-club validation

Add Event.created_by (the owner, used later by the access service to let an
event's creator edit it).

Validate that an event's season/location/opponent — and an EventSeries'
location/opponent — belong to the event's club. The teams M2M cannot be checked
in clean() (M2M rows are written after save), so an m2m_changed pre_add receiver
rejects teams from another club.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 14:42:17 +02:00
parent c0816a1add
commit 22d971d48c
4 changed files with 91 additions and 3 deletions

View File

@@ -2,7 +2,7 @@ from django.db import models
from django.utils.translation import gettext_lazy as _
from club.models import Season
from clubmanager.base import ClubScopedModel, UUIDModel
from clubmanager.base import ClubScopedModel, UUIDModel, validate_club_scope
from members.models import Member
from teams.models import Team
@@ -64,6 +64,7 @@ class Event(ClubScopedModel):
location = models.ForeignKey(Location, on_delete=models.SET_NULL, related_name="events", null=True, blank=True, verbose_name=_("location"))
opponent = models.ForeignKey(Opponent, on_delete=models.SET_NULL, related_name="events", null=True, blank=True, verbose_name=_("opponent"))
created_by = models.ForeignKey(Member, on_delete=models.SET_NULL, related_name="created_events", null=True, blank=True, verbose_name=_("created by"))
class Meta:
verbose_name = _("event")
@@ -73,6 +74,9 @@ class Event(ClubScopedModel):
def __str__(self):
return self.title
def clean(self):
validate_club_scope(self, self.club_id, same_club_fields=("season", "location", "opponent"))
class EventSeries(ClubScopedModel):
"""A recurring event definition that materialises concrete Event rows."""
@@ -103,6 +107,9 @@ class EventSeries(ClubScopedModel):
def __str__(self):
return self.title
def clean(self):
validate_club_scope(self, self.club_id, same_club_fields=("location", "opponent"))
class Attendance(UUIDModel):
class AttendanceStatus(models.TextChoices):