Notify a member once when a roster/group change adds them to new events

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
This commit is contained in:
2026-08-22 18:44:45 +02:00
parent 5db885c809
commit 618d15a3e0
4 changed files with 115 additions and 4 deletions

View File

@@ -567,6 +567,66 @@ class RosterChangeSyncTests(EventsTestBase):
self.assertTrue(past.attendances.filter(member=self.alice).exists())
def test_joining_a_team_notifies_the_member_of_new_events(self):
event = self.make_event()
event.teams.set([self.team])
dave = Member.objects.create(first_name="Dave", last_name="Dogwood")
TeamMembership.objects.create(team=self.team, member=dave, season=self.season, position=self.position)
notification = Notification.objects.get(member=dave)
self.assertEqual(notification.title, "New events on your calendar")
self.assertIn("1 new event", notification.body)
def test_joining_a_team_with_several_upcoming_events_sends_one_summary_notification(self):
first = self.make_event(title="First")
first.teams.set([self.team])
second = self.make_event(title="Second", start=self.future + timedelta(days=1))
second.teams.set([self.team])
dave = Member.objects.create(first_name="Dave", last_name="Dogwood")
TeamMembership.objects.create(team=self.team, member=dave, season=self.season, position=self.position)
self.assertEqual(Notification.objects.filter(member=dave).count(), 1)
notification = Notification.objects.get(member=dave)
self.assertIn("2 new events", notification.body)
def test_joining_a_team_with_no_upcoming_events_does_not_notify(self):
dave = Member.objects.create(first_name="Dave", last_name="Dogwood")
TeamMembership.objects.create(team=self.team, member=dave, season=self.season, position=self.position)
self.assertFalse(Notification.objects.filter(member=dave).exists())
def test_leaving_a_team_does_not_notify(self):
event = self.make_event()
event.teams.set([self.team])
TeamMembership.objects.get(team=self.team, member=self.alice).delete()
self.assertFalse(Notification.objects.filter(member=self.alice).exists())
def test_editing_a_membership_field_does_not_notify(self):
event = self.make_event()
event.teams.set([self.team])
membership = TeamMembership.objects.get(team=self.team, member=self.alice)
membership.jersey_number = 42
membership.save()
self.assertFalse(Notification.objects.filter(member=self.alice).exists())
def test_joining_a_group_notifies_the_member_of_new_events(self):
group = Group.objects.create(club=self.club, name="Committee")
event = self.make_event()
event.groups.set([group])
carol = Member.objects.create(first_name="Carol", last_name="Cedar")
GroupMembership.objects.create(group=group, member=carol)
notification = Notification.objects.get(member=carol)
self.assertIn("1 new event", notification.body)
class RecurrenceTestBase(EventsTestBase):
@classmethod