Show blocked sign-ups on mobile instead of silently hiding the event
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
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
from .attendance import (
|
||||
blocked_upcoming_events_for_member,
|
||||
effective_members,
|
||||
notify_newly_invited,
|
||||
player_attendance_rankings,
|
||||
@@ -20,6 +21,7 @@ from .recurrence import (
|
||||
|
||||
__all__ = [
|
||||
"apply_template",
|
||||
"blocked_upcoming_events_for_member",
|
||||
"cancel_occurrence",
|
||||
"detach_occurrence",
|
||||
"effective_members",
|
||||
|
||||
@@ -22,7 +22,8 @@ from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import ngettext
|
||||
|
||||
from club.models import ClubMembership, Season
|
||||
from club.services.onboarding import blocked_member_ids_for_event
|
||||
from club.services.access import current_season
|
||||
from club.services.onboarding import blocked_member_ids_for_event, open_requirements_blocking
|
||||
from events.models import Attendance, Event
|
||||
from members.models import Member
|
||||
from notifications.services import notify_members
|
||||
@@ -88,6 +89,50 @@ def sync_event_attendances(event):
|
||||
event.attendances.filter(member_id__in=to_remove).delete()
|
||||
|
||||
|
||||
def blocked_upcoming_events_for_member(member, club):
|
||||
"""Upcoming events ``member`` would normally see (via a team roster or a
|
||||
group they're in) but has no ``Attendance`` row for, because an open
|
||||
onboarding requirement blocks that event's kind -- the read side of
|
||||
``effective_members()``'s own exclusion, which otherwise makes the event
|
||||
vanish for them with no explanation at all. Built for the member-facing
|
||||
"why can't I sign up" card (mobile/views.py) rather than the event simply
|
||||
never appearing.
|
||||
|
||||
Returns ``[(event, [blocking OnboardingRequirement, ...]), ...]``, soonest
|
||||
first. A member explicitly ``invited_members`` on an event never appears
|
||||
here -- that bypasses blocking entirely (see ``effective_members``'s own
|
||||
docstring), so they already have a normal Attendance row for it."""
|
||||
season = current_season(club)
|
||||
if season is None:
|
||||
return []
|
||||
|
||||
team_ids = list(TeamMembership.objects.filter(member=member, season=season).values_list("team_id", flat=True))
|
||||
group_ids = list(member.group_memberships.values_list("group_id", flat=True))
|
||||
if not team_ids and not group_ids:
|
||||
return []
|
||||
|
||||
candidates = list(
|
||||
Event.objects.filter(club=club, cancelled=False, start__gte=timezone.now())
|
||||
.filter(Q(teams__id__in=team_ids) | Q(groups__id__in=group_ids))
|
||||
.exclude(excluded_members=member)
|
||||
.distinct()
|
||||
.order_by("start")
|
||||
)
|
||||
if not candidates:
|
||||
return []
|
||||
|
||||
existing_ids = set(Attendance.objects.filter(member=member, event__in=candidates).values_list("event_id", flat=True))
|
||||
|
||||
results = []
|
||||
for event in candidates:
|
||||
if event.pk in existing_ids:
|
||||
continue
|
||||
requirements = open_requirements_blocking(member, club, season, event.kind)
|
||||
if requirements:
|
||||
results.append((event, requirements))
|
||||
return results
|
||||
|
||||
|
||||
def notify_newly_invited(member, *, club, events):
|
||||
"""One notification (push+email), not one per event -- used when a
|
||||
roster/group change (events/signals.py's sync_on_roster_change/
|
||||
|
||||
Reference in New Issue
Block a user