diff --git a/management/tests.py b/management/tests.py
index 2b5d0db..9355dcf 100644
--- a/management/tests.py
+++ b/management/tests.py
@@ -5896,6 +5896,45 @@ class FamilyListViewTests(ManagementTestBase):
self.assertNotContains(response, "The Smiths")
+ def test_search_matches_a_parents_name(self):
+ response = self.client.get(reverse("management:family_list") + "?q=Pat", HTTP_HOST="ajax-united.rosterchief.app")
+
+ self.assertContains(response, "The Smiths")
+
+ def test_search_matches_a_childs_name(self):
+ response = self.client.get(reverse("management:family_list") + "?q=Cody", HTTP_HOST="ajax-united.rosterchief.app")
+
+ self.assertContains(response, "The Smiths")
+
+ def test_search_matches_a_last_name(self):
+ response = self.client.get(reverse("management:family_list") + "?q=Smith", HTTP_HOST="ajax-united.rosterchief.app")
+
+ self.assertContains(response, "The Smiths")
+
+ def test_search_with_no_match_excludes_the_family(self):
+ response = self.client.get(reverse("management:family_list") + "?q=Nobody", HTTP_HOST="ajax-united.rosterchief.app")
+
+ self.assertNotContains(response, "The Smiths")
+
+ def test_search_term_is_kept_in_the_input(self):
+ response = self.client.get(reverse("management:family_list") + "?q=Pat", HTTP_HOST="ajax-united.rosterchief.app")
+
+ self.assertContains(response, 'value="Pat"')
+
+ def test_search_still_respects_visibility(self):
+ # A search term matching someone real must not surface a family the
+ # requester otherwise has no reason to see.
+ coach_user = User.objects.create_user(email="coach-fam2@example.com", password="pw-secret-123")
+ coach_member = Member.objects.create(user=coach_user, first_name="Cara", last_name="Coach")
+ team = Team.objects.create(club=self.club, name="U12", short_name="U12")
+ position = Position.objects.create(club=self.club, name="Coach-fam2", short_name="CF2", staff_position=True, management_position=True)
+ StaffAssignment.objects.create(team=team, member=coach_member, season=self.season, position=position)
+ self.client.force_login(coach_user)
+
+ response = self.client.get(reverse("management:family_list") + "?q=Pat", HTTP_HOST="ajax-united.rosterchief.app")
+
+ self.assertNotContains(response, "The Smiths")
+
class SidebarCounterTests(ManagementTestBase):
"""The nav's two admin-only badges -- pending parent claims, and upcoming
diff --git a/management/views.py b/management/views.py
index f05da2b..ba4018f 100644
--- a/management/views.py
+++ b/management/views.py
@@ -1376,10 +1376,20 @@ class FamilyListView(ClubStaffRequiredMixin, ListView):
def get_queryset(self):
visible = members_visible_to(self.request.user, self.request.club, include_guardians=True)
+
+ # ?q= matches a first or last name of any member on the family --
+ # parent/guardian or child alike, since the person being searched for
+ # could be either. Narrowing the already-visible set first (rather than
+ # filtering families on a second, independent membership join) means a
+ # match still respects the same visibility rule as the unfiltered list.
+ search = self.request.GET.get("q", "").strip()
+ if search:
+ visible = visible.filter(Q(first_name__icontains=search) | Q(last_name__icontains=search))
+
return families_of_club(self.request.club).filter(memberships__member__in=visible).distinct()
def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
+ context = super().get_context_data(search=self.request.GET.get("q", ""), **kwargs)
families = list(context["families"])
visible = members_visible_to(self.request.user, self.request.club, include_guardians=True)