From b0b4526867202f2349d6af97cea9e35770d7ee91 Mon Sep 17 00:00:00 2001 From: ParzivalT1 Date: Tue, 18 Aug 2026 23:08:12 +0700 Subject: [PATCH 1/3] Allow nominators to view their nominees --- apps/nominations/models.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/nominations/models.py b/apps/nominations/models.py index 88aa5fc80..87e8f041a 100644 --- a/apps/nominations/models.py +++ b/apps/nominations/models.py @@ -245,10 +245,13 @@ def visible(self, user=None): if self.accepted and self.approved and not self.election.nominations_open: return True - if user is None: + if user is None or not user.is_authenticated: return False - return bool(user.is_staff or user == self.user) + if user.is_staff or user == self.user: + return True + + return self.nominations.filter(election=self.election, nominator=user).exists() class Nomination(models.Model): From 2d902cb0ca4479d95a04f0b6c0d8915592445fbd Mon Sep 17 00:00:00 2001 From: ParzivalT1 Date: Tue, 18 Aug 2026 23:08:23 +0700 Subject: [PATCH 2/3] Scope open nominee lists to the current election --- apps/nominations/views.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/nominations/views.py b/apps/nominations/views.py index 699da3231..a6ab5b073 100644 --- a/apps/nominations/views.py +++ b/apps/nominations/views.py @@ -2,6 +2,7 @@ from django.contrib import messages from django.contrib.auth.mixins import UserPassesTestMixin +from django.db.models import Q from django.http import Http404, JsonResponse from django.shortcuts import get_object_or_404 from django.urls import reverse @@ -72,7 +73,12 @@ def get_queryset(self, *args, **kwargs): return Nominee.objects.filter(accepted=True, approved=True, election=election).exclude(user=None) if self.request.user.is_authenticated: - return Nominee.objects.filter(user=self.request.user) + return ( + Nominee.objects.filter(election=election) + .filter(Q(user=self.request.user) | Q(nominations__nominator=self.request.user)) + .exclude(user=None) + .distinct() + ) return None From 23ead135c08ab4d3d38deb963a032cba5b9dcba6 Mon Sep 17 00:00:00 2001 From: ParzivalT1 Date: Tue, 18 Aug 2026 23:08:31 +0700 Subject: [PATCH 3/3] Test nominee visibility during open elections --- apps/nominations/tests/test_views.py | 56 ++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/apps/nominations/tests/test_views.py b/apps/nominations/tests/test_views.py index 87b8c04b9..b8012ef77 100644 --- a/apps/nominations/tests/test_views.py +++ b/apps/nominations/tests/test_views.py @@ -112,6 +112,62 @@ def test_nomination_create_404s(self): self.assertEqual(self.client.get(url).status_code, 404) +class OpenNomineeVisibilityTests(TestCase): + def setUp(self): + self.nominator = UserFactory(first_name="Ada", last_name="Lovelace") + self.other_user = UserFactory() + self.election = open_election("2026 Board Election") + self.other_election = open_election("2027 Board Election") + + self.nominated = Nominee.objects.create( + user=UserFactory(first_name="Grace", last_name="Hopper"), + election=self.election, + ) + Nomination.objects.create( + election=self.election, + nominator=self.nominator, + nominee=self.nominated, + name="Grace Hopper", + email="grace@example.com", + nomination_statement="A strong candidate.", + ) + + self.own_candidacy = Nominee.objects.create(user=self.nominator, election=self.election) + Nomination.objects.create( + election=self.election, + nominator=self.other_user, + nominee=self.own_candidacy, + name=self.nominator.get_full_name(), + email=self.nominator.email, + nomination_statement="Another strong candidate.", + ) + + self.unrelated = Nominee.objects.create(user=UserFactory(), election=self.election) + self.other_election_candidacy = Nominee.objects.create(user=self.nominator, election=self.other_election) + self.client.force_login(self.nominator) + + def _list_url(self): + return reverse("nominations:nominees_list", kwargs={"election": self.election.slug}) + + def test_list_shows_only_nominees_relevant_to_user_in_current_election(self): + response = self.client.get(self._list_url()) + + self.assertEqual(response.status_code, 200) + self.assertCountEqual(response.context["object_list"], [self.nominated, self.own_candidacy]) + + def test_nominator_can_view_their_nominee_while_nominations_are_open(self): + response = self.client.get(self.nominated.get_absolute_url()) + + self.assertEqual(response.status_code, 200) + + def test_unrelated_user_cannot_view_nominee_while_nominations_are_open(self): + self.client.force_login(self.other_user) + + response = self.client.get(self.nominated.get_absolute_url()) + + self.assertEqual(response.status_code, 404) + + class NominationCreateFormSelectionTests(TestCase): def setUp(self): self.user = UserFactory()