Repository: carpentries/amy

Issue:
Scheduled email view should include email-specific information
In the current scheduled email view ([list view](https://test-amy2.carpentries.org/emails/scheduled_emails/) and [sample item view](https://test-amy2.carpentries.org/emails/scheduled_email/463f6747-e9a7-4665-b0ba-e13d7eb2b97a/) we do not easily see details about what email that is, especially as the template variables do not render.

@pbanaszkiewicz  Can you advise what options we have so we can tell exactly what email that is? Depending on the email, this may mean displaying the event slug, person name, award, etc.  One option may be for the scheduled email list, to include the related object in that table.  The ID currently displayed in the scheduled emails table view is not relevant to the end user so that space could be used for this instead.    Then for the item view, to move the related object (currently at the bottom of the view) to the top to make it more prominent.  




Discussion:
@maneesha 

Proposal for the scheduled email list:

<img width="1800" alt="image" src="https://github.com/carpentries/amy/assets/72821/fe48031b-5213-4abe-8ba3-4475498a8830">


Proposal for the details of scheduled email:

<img width="1800" alt="image" src="https://github.com/carpentries/amy/assets/72821/61093e8c-b415-41aa-b2bc-5d4c3bbfd212">


What do you think?

I made sure that all currently related objects can be linked (Instructor Recruitment Signup leads to Instructor Recruitment details, Award leads to Person details, and Event, Person and Membership lead to their detail pages).

Resolving patch:
diff --git a/amy/emails/utils.py b/amy/emails/utils.py
--- a/amy/emails/utils.py
+++ b/amy/emails/utils.py
@@ -85,16 +85,16 @@ def messages_missing_template_link(
 def messages_action_scheduled(
     request: HttpRequest, signal_name: str, scheduled_email: ScheduledEmail
 ) -> None:
+    name = scheduled_email.template.name if scheduled_email.template else signal_name
     messages.info(
         request,
         format_html(
-            "New email action ({}) was scheduled to run "
+            "New email action was scheduled to run "
             '<relative-time datetime="{}"></relative-time>: '
             '<a href="{}"><code>{}</code></a>.',
-            signal_name,
             scheduled_email.scheduled_at,
             scheduled_email.get_absolute_url(),
-            scheduled_email.pk,
+            name,
         ),
         extra_tags=settings.ONLY_FOR_ADMINS_TAG,
     )
@@ -103,12 +103,13 @@ def messages_action_scheduled(
 def messages_action_updated(
     request: HttpRequest, signal_name: str, scheduled_email: ScheduledEmail
 ) -> None:
+    name = scheduled_email.template.name if scheduled_email.template else signal_name
     messages.info(
         request,
         format_html(
             'Existing <a href="{}">email action ({})</a> was updated.',
             scheduled_email.get_absolute_url(),
-            signal_name,
+            name,
         ),
         extra_tags=settings.ONLY_FOR_ADMINS_TAG,
     )
@@ -117,12 +118,13 @@ def messages_action_updated(
 def messages_action_cancelled(
     request: HttpRequest, signal_name: str, scheduled_email: ScheduledEmail
 ) -> None:
+    name = scheduled_email.template.name if scheduled_email.template else signal_name
     messages.warning(
         request,
         format_html(
             'Existing <a href="{}">email action ({})</a> was cancelled.',
             scheduled_email.get_absolute_url(),
-            signal_name,
+            name,
         ),
         extra_tags=settings.ONLY_FOR_ADMINS_TAG,
     )
diff --git a/amy/recruitment/models.py b/amy/recruitment/models.py
--- a/amy/recruitment/models.py
+++ b/amy/recruitment/models.py
@@ -161,3 +161,8 @@ def __str__(self) -> str:
             f"Instructor Signup applicant {self.person.full_name} "
             f"for {self.recruitment.event}"
         )
+
+    def get_absolute_url(self) -> str:
+        return reverse(
+            "instructorrecruitment_details", kwargs={"pk": self.recruitment.pk}
+        )
diff --git a/amy/workshops/models.py b/amy/workshops/models.py
--- a/amy/workshops/models.py
+++ b/amy/workshops/models.py
@@ -1869,6 +1869,9 @@ def __str__(self):
             self.person, self.badge, self.awarded, self.event
         )
 
+    def get_absolute_url(self) -> str:
+        return reverse("person_details", args=[self.person.pk])
+
 
 # ------------------------------------------------------------
 


Test patch:
diff --git a/amy/emails/tests/test_utils.py b/amy/emails/tests/test_utils.py
--- a/amy/emails/tests/test_utils.py
+++ b/amy/emails/tests/test_utils.py
@@ -186,6 +186,9 @@ def test_messages_action_scheduled(self, mock_messages_info) -> None:
         signal_name = "test_signal"
         scheduled_at = timezone.now()
         scheduled_email = ScheduledEmail(scheduled_at=scheduled_at)
+        name = (
+            scheduled_email.template.name if scheduled_email.template else signal_name
+        )
 
         # Act
         messages_action_scheduled(request, signal_name, scheduled_email)
@@ -193,10 +196,10 @@ def test_messages_action_scheduled(self, mock_messages_info) -> None:
         # Assert
         mock_messages_info.assert_called_once_with(
             request,
-            f"New email action ({signal_name}) was scheduled to run "
+            f"New email action was scheduled to run "
             f'<relative-time datetime="{scheduled_at}"></relative-time>: '
             f'<a href="{scheduled_email.get_absolute_url()}"><code>'
-            f"{scheduled_email.pk}</code></a>.",
+            f"{name}</code></a>.",
             extra_tags=settings.ONLY_FOR_ADMINS_TAG,
         )
 
@@ -209,6 +212,9 @@ def test_messages_action_updated(self, mock_messages_info) -> None:
         signal_name = "test_signal"
         scheduled_at = timezone.now()
         scheduled_email = ScheduledEmail(scheduled_at=scheduled_at)
+        name = (
+            scheduled_email.template.name if scheduled_email.template else signal_name
+        )
 
         # Act
         messages_action_updated(request, signal_name, scheduled_email)
@@ -217,7 +223,7 @@ def test_messages_action_updated(self, mock_messages_info) -> None:
         mock_messages_info.assert_called_once_with(
             request,
             f'Existing <a href="{scheduled_email.get_absolute_url()}">email action '
-            f"({signal_name})</a> was updated.",
+            f"({name})</a> was updated.",
             extra_tags=settings.ONLY_FOR_ADMINS_TAG,
         )
 
@@ -230,6 +236,9 @@ def test_messages_action_cancelled(self, mock_messages_warning) -> None:
         signal_name = "test_signal"
         scheduled_at = timezone.now()
         scheduled_email = ScheduledEmail(scheduled_at=scheduled_at)
+        name = (
+            scheduled_email.template.name if scheduled_email.template else signal_name
+        )
 
         # Act
         messages_action_cancelled(request, signal_name, scheduled_email)
@@ -238,7 +247,7 @@ def test_messages_action_cancelled(self, mock_messages_warning) -> None:
         mock_messages_warning.assert_called_once_with(
             request,
             f'Existing <a href="{scheduled_email.get_absolute_url()}">email action '
-            f"({signal_name})</a> was cancelled.",
+            f"({name})</a> was cancelled.",
             extra_tags=settings.ONLY_FOR_ADMINS_TAG,
         )