Events can now target members.Group audiences alongside teams, or go
club_wide (every ACTIVE ClubMembership member for the event's season)
instead of specific teams/groups -- the two are mutually exclusive,
enforced in EventForm/EventSeriesForm.clean() since an M2M can't be
validated via a DB CheckConstraint or Event.clean() (no PK yet). Attendance
sync (events/signals.py) now reacts to GroupMembership and ClubMembership
changes the same way it already did for TeamMembership. Authorization:
club.services.access.groups_manageable_by mirrors teams_managed_by (all
groups for an ADMIN, else only the ones the user belongs to -- Group has no
manager/owner concept); a non-admin needs at least one managed team or
belonged-to group to create/edit an event, club_wide stays admin-only, and
EventManagerRequiredMixin gained a get_groups() hook so a non-admin who
creates a group-only event isn't immediately locked out of managing it.
Also adds rosterchief.mail.ResendEmailBackend, an HTTP-API-based Django
email backend for Resend (resend.com) using the existing `requests`
dependency -- no new SDK. Opt in via DJANGO_EMAIL_BACKEND and RESEND_API_KEY;
every Django-sent email (allauth's password reset included) follows
whichever EMAIL_BACKEND is configured, so this covers all of them for free.
Resend's own SMTP relay remains a valid code-free alternative, documented
alongside it in .env.production.example.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add EventSeries.until: an explicit series end that caps occurrence
generation (whichever comes first — it, the generation horizon, or the
rule's own COUNT/UNTIL). Blank means open-ended. Surface it in the admin.
100% coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EventSeries gains gathering_offset and deadline_offset (durations before
the start). Each generated occurrence derives its gathering/deadline from
them (and clears them when unset); propagate_series pushes offset changes
to non-detached future occurrences. Surface them in the admin. 100%
coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add EventSeries (club-scoped): an RFC-5545 rrule + dtstart + duration and a
template (kind/title/location/opponent + audience M2M). Concrete Event rows
are materialised occurrences carrying a series FK plus detached/cancelled
flags; the series tracks excluded_dates (EXDATEs) and a generated_until
horizon watermark.
Recurrence service:
- occurrence_datetimes expands the rrule (via python-dateutil) up to a
horizon, minus EXDATEs.
- generate_occurrences materialises missing rows, copies the template +
audience (so attendance syncs through the existing signals), and is
idempotent.
- cancel_occurrence adds an EXDATE and deletes (or soft-cancels) one
occurrence so it isn't regenerated; detach_occurrence marks an occurrence
as independently edited; propagate_series re-applies the template to
non-detached future occurrences.
- extend_event_series management command rolls the horizon forward.
Register EventSeries in the admin and surface series/detached/cancelled on
the Event admin. Add python-dateutil. Full suite at 100% coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>