Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions apps/nominations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,15 @@ 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

# A nominator can see the person they nominated, so that the links
# in the nominee list preview work while nominations are open.
return self.nominations.filter(nominator=user).exists()


class Nomination(models.Model):
Expand Down
70 changes: 70 additions & 0 deletions apps/nominations/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,73 @@ def test_accept_404s_under_wrong_election(self):

def test_edit_still_works_under_own_election(self):
self.assertEqual(self.client.get(self._url("nominations:nomination_edit", self.election)).status_code, 200)


class NomineeListPreviewTests(TestCase):
"""While nominations are open, the list previews the nominees relevant to the user."""

def setUp(self):
self.nominator = UserFactory(first_name="Alan", last_name="Turing")
self.election = open_election("2026 Board Election")
self.other_election = open_election("2026 Packaging Council Election", kind=packaging_council_kind())

self.nominee = self._nominee(self.election, "Grace", "Hopper")
self._nomination(self.election, self.nominator, self.nominee)

# The same user is a candidate in an unrelated election.
self.nominator_as_nominee = Nominee.objects.create(user=self.nominator, election=self.other_election)
self._nomination(
self.other_election, UserFactory(first_name="Barbara", last_name="Liskov"), self.nominator_as_nominee
)

# Somebody else's nomination in the election under test.
self.unrelated_nominee = self._nominee(self.election, "Ada", "Lovelace")
self._nomination(self.election, UserFactory(first_name="Ken", last_name="Thompson"), self.unrelated_nominee)

def _nominee(self, election, first_name, last_name):
user = UserFactory(first_name=first_name, last_name=last_name)
return Nominee.objects.create(user=user, election=election)

def _nomination(self, election, nominator, nominee):
return Nomination.objects.create(
election=election,
nominator=nominator,
nominee=nominee,
name=f"{nominee.user.first_name} {nominee.user.last_name}",
email=nominee.user.email,
nomination_statement="A strong candidate.",
)

def _url(self, election):
return reverse("nominations:nominees_list", kwargs={"election": election.slug})

def test_preview_lists_people_the_user_nominated(self):
self.client.force_login(self.nominator)
response = self.client.get(self._url(self.election))
self.assertEqual(response.status_code, 200)
self.assertIn(self.nominee, response.context["object_list"])

def test_preview_excludes_other_elections(self):
self.client.force_login(self.nominator)
response = self.client.get(self._url(self.election))
self.assertNotIn(self.nominator_as_nominee, response.context["object_list"])

def test_preview_excludes_nominations_by_other_people(self):
self.client.force_login(self.nominator)
response = self.client.get(self._url(self.election))
self.assertNotIn(self.unrelated_nominee, response.context["object_list"])

def test_preview_includes_the_user_as_a_candidate(self):
self.client.force_login(self.nominator)
response = self.client.get(self._url(self.other_election))
self.assertIn(self.nominator_as_nominee, response.context["object_list"])

def test_nominator_can_open_the_nominee_they_nominated(self):
self.client.force_login(self.nominator)
response = self.client.get(self.nominee.get_absolute_url())
self.assertEqual(response.status_code, 200)

def test_other_users_still_cannot_open_the_nominee(self):
self.client.force_login(UserFactory(first_name="Guido", last_name="van Rossum"))
response = self.client.get(self.nominee.get_absolute_url())
self.assertEqual(response.status_code, 404)
14 changes: 13 additions & 1 deletion apps/nominations/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -72,7 +73,18 @@ 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)
# Before the results are public, preview the nominees relevant to
# this user in this election: the people they nominated, plus
# themselves when somebody nominated them.
return (
Nominee.objects.filter(
Q(user=self.request.user) | Q(nominations__nominator=self.request.user),
election=election,
)
.exclude(user=None)
.distinct()
.select_related("user")
)
Comment thread
Copilot marked this conversation as resolved.
return None


Expand Down