+
+ Your self-nomination for the {{ nomination.election.name }} Election has been received
+
+
+ What you need to know
+
+ - Your nomination is already marked as accepted. You don't need to accept it again.
+ - You can edit it until nominations close{% if nomination.election.nominations_close_at %} on {{ nomination.election.nominations_close_at|date:"F j, Y, P T" }}{% endif %}.
+ - PSF staff will review your nomination before it is published.
+ - Approved nominees are listed publicly once nominations close.
+
+
+ View your nomination
+ {% if nomination.election.nominations_open %}| Edit it{% endif %}
+ | See all your nominations
+
+
+ {% if nomination.election.nominations_open %}
+ {% include "nominations/_supporting_statements.html" %}
+ {% endif %}
+
+{% endblock content %}
diff --git a/apps/nominations/tests/test_forms.py b/apps/nominations/tests/test_forms.py
index f53378a28..6154ac05c 100644
--- a/apps/nominations/tests/test_forms.py
+++ b/apps/nominations/tests/test_forms.py
@@ -40,6 +40,17 @@ def test_self_nomination_valid_with_coc(self):
form = self._form(nomination_payload(self_nomination="on", coc_acknowledged="on"))
self.assertTrue(form.is_valid(), form.errors)
+ def test_self_nomination_requires_account_email(self):
+ self.request.user.email = ""
+ form = self._form(nomination_payload(self_nomination="on", coc_acknowledged="on"))
+ self.assertFalse(form.is_valid())
+ self.assertIn("self_nomination", form.errors)
+
+ def test_third_party_nomination_allowed_without_account_email(self):
+ self.request.user.email = ""
+ form = self._form(nomination_payload())
+ self.assertTrue(form.is_valid(), form.errors)
+
def test_mission_alignment_is_optional(self):
form = self._form(nomination_payload(self_nomination="on", coc_acknowledged="on"))
self.assertTrue(form.is_valid(), form.errors)
diff --git a/apps/nominations/tests/test_views.py b/apps/nominations/tests/test_views.py
index 441a00d3c..6571aff25 100644
--- a/apps/nominations/tests/test_views.py
+++ b/apps/nominations/tests/test_views.py
@@ -1,6 +1,7 @@
import datetime
from django.contrib.auth import get_user_model
+from django.core import mail
from django.test import TestCase
from django.urls import reverse
@@ -196,6 +197,62 @@ def test_packaging_council_self_nomination_requires_eligibility(self):
self.assertTrue(nomination.eligibility_confirmed)
+class NominationSubmittedTests(TestCase):
+ def setUp(self):
+ self.user = UserFactory(first_name="Grace", last_name="Hopper")
+ self.client.force_login(self.user)
+ self.election = open_election("2026 Board Election")
+ self.create_url = reverse("nominations:nomination_create", kwargs={"election": self.election.slug})
+
+ def test_self_nomination_redirects_to_next_steps(self):
+ response = self.client.post(self.create_url, nomination_payload(self_nomination="on", coc_acknowledged="on"))
+ nomination = Nomination.objects.get(election=self.election)
+ submitted_url = reverse(
+ "nominations:nomination_submitted", kwargs={"election": self.election.slug, "pk": nomination.pk}
+ )
+ self.assertRedirects(response, submitted_url)
+
+ def test_third_party_nomination_redirects_to_detail(self):
+ response = self.client.post(self.create_url, nomination_payload())
+ nomination = Nomination.objects.get(election=self.election)
+ self.assertRedirects(response, nomination.get_absolute_url())
+
+ def test_next_steps_forbidden_to_other_users(self):
+ self.client.post(self.create_url, nomination_payload(self_nomination="on", coc_acknowledged="on"))
+ nomination = Nomination.objects.get(election=self.election)
+ self.client.force_login(UserFactory(is_staff=True))
+ response = self.client.get(
+ reverse("nominations:nomination_submitted", kwargs={"election": self.election.slug, "pk": nomination.pk})
+ )
+ self.assertEqual(response.status_code, 403)
+
+
+class SelfNominationEmailTests(TestCase):
+ def setUp(self):
+ self.user = UserFactory(first_name="Grace", last_name="Hopper", email="grace@python.example")
+ self.client.force_login(self.user)
+ self.election = open_election("2026 Board Election")
+ self.create_url = reverse("nominations:nomination_create", kwargs={"election": self.election.slug})
+
+ def test_self_nomination_emails_candidate_next_steps(self):
+ self.client.post(self.create_url, nomination_payload(self_nomination="on", coc_acknowledged="on"))
+ nomination = Nomination.objects.get(election=self.election)
+
+ self.assertEqual(len(mail.outbox), 1)
+ email = mail.outbox[0]
+ self.assertEqual(email.to, ["grace@python.example"])
+ submitted_path = reverse(
+ "nominations:nomination_submitted", kwargs={"election": self.election.slug, "pk": nomination.pk}
+ )
+ self.assertIn(f"http://testserver{submitted_path}", email.body)
+ self.assertIn(f"http://testserver{self.create_url}", email.body)
+
+ def test_third_party_nomination_sends_no_email(self):
+ self.client.post(self.create_url, nomination_payload())
+ self.assertTrue(Nomination.objects.filter(election=self.election).exists())
+ self.assertEqual(mail.outbox, [])
+
+
class NominationStatementPreviewTests(TestCase):
def test_renders_markdown_with_html_escaped(self):
self.client.force_login(UserFactory())
diff --git a/apps/nominations/urls.py b/apps/nominations/urls.py
index 08abcacc7..f120dd2a4 100644
--- a/apps/nominations/urls.py
+++ b/apps/nominations/urls.py
@@ -33,6 +33,11 @@
views.NominationView.as_view(),
name="nomination_detail",
),
+ path(
+ "