A member excluded from an event's Attendance sync by an open onboarding
requirement (events.services.attendance.effective_members) previously just
never saw that event anywhere -- no row, no explanation. Two new read-side
functions mirror that exclusion instead of hiding it: club.services.
onboarding.open_requirements_blocking (per-member, "why") and events.
services.attendance.blocked_upcoming_events_for_member (which of their
upcoming events are affected).
The Calendar now shows those events as a distinct muted "Blocked" row
naming the outstanding requirement, and the event detail page shows a
"Can't sign up yet" card for the same reason -- no RSVP buttons, no lineup/
referee actions, just the explanation. Write-side blocking (who actually
gets an Attendance row, who a coach can select) is untouched.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Joining a team or group already re-synced Attendance rows for its upcoming
events (events/signals.py), but did so silently. A fresh membership (not a
field edit, not a removal) now sends one summary notification -- "N new
events on your calendar" -- rather than one per event, so joining mid-season
with a whole recurring series already scheduled doesn't flood the member
with pushes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Member detail: a 12-bar season attendance sparkline (present/absent/upcoming)
beside Present/Absent/No-reply totals, shown for any rostered non-guardian
member with events this season (events.services.attendance.member_attendance_
sparkline/_counts). News list: rows are one dense line (status pill inline
with the headline, everything else folded into a single meta line) instead
of the previous multi-line stacked layout that read as a small card per item.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
Large uncommitted body of work accumulated across sessions on this branch --
committing as a checkpoint so it's tracked and future worktree-isolated agents
see the real codebase instead of a stale ancestor commit. Covers the
management app's dedicated Tailwind theme and templates, the club onboarding
requirement/signup workflow (club/services/onboarding.py, requirement/status
models, sign-up dashboard), fee/status auto-activation decoupling, referee
management, and the new events calendar grid service layer.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECGMEwrc2k4D8VQuwjstj9
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>
Team pages now show, for the selected season: overall attendance rate,
best/worst attenders, players who missed the last 2 practices, and
no-shows (an affirmative RSVP checked in as absent). No-shows need a
real distinction the RSVP status alone can't make, so Attendance gains
a separate showed_up tri-state field, usable today via Django admin --
a full check-in screen is future work for the coaches app.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R1gj3J1QPfP38XWpnpbFpy
Rework an event's audience: replace the single team FK with a teams M2M
plus invited_members and excluded_members, and add a season FK (derived
from the start date when blank) so team rosters resolve correctly. A data
migration copies existing team -> teams.
Add an attendance sync service: the effective audience is the union of the
teams' rosters for the event's season plus invited, minus excluded;
sync_event_attendances reconciles Attendance rows for future events only,
adding NO_RESPONSE rows for new members and hard-deleting rows for members
no longer invited. Signals drive it: editing an event or its audience
re-syncs that event, and adding/removing a team-roster member re-syncs
that team's future events.
Register all events models in the admin (with an attendance inline) and
add events to the admin registration smoke test. Add Season.covering()
and pillow (Opponent.logo ImageField). Full suite at 100% coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>