Repository: carpentries/amy

Issue:
Drop empty emails for Self-Organised action
In some cases the email action fails due to some empty residue from additional contact parsing.


Discussion:

Resolving patch:
diff --git a/amy/autoemails/actions.py b/amy/autoemails/actions.py
--- a/amy/autoemails/actions.py
+++ b/amy/autoemails/actions.py
@@ -573,11 +573,9 @@ def all_recipients(self) -> str:
         """If available, return string of all recipients."""
         try:
             request = self.context_objects["request"]
-            emails = [request.email]
-            if request.additional_contact:
-                for email in request.additional_contact.split(TAG_SEPARATOR):
-                    emails.append(email)
-            return ", ".join(emails)
+            emails = [request.email] + request.additional_contact.split(TAG_SEPARATOR)
+            all_emails = list(filter(bool, emails))
+            return ", ".join(all_emails)
         except (KeyError, AttributeError):
             return ""
 
@@ -632,12 +630,9 @@ def get_additional_context(self, objects, *args, **kwargs):
         # event starts in less (or equal) than 10 days
         context["short_notice"] = event.start <= (date.today() + timedelta(days=10))
 
-        # querying over Person.objects lets us get rid of duplicates
-        context["all_emails"] = [request.email]
         # additional contact info (see CommonRequest for details)
-        if request.additional_contact:
-            for email in request.additional_contact.split(TAG_SEPARATOR):
-                context["all_emails"].append(email)
+        emails = [request.email] + request.additional_contact.split(TAG_SEPARATOR)
+        context["all_emails"] = list(filter(bool, emails))
 
         context["assignee"] = (
             event.assigned_to.full_name if event.assigned_to else "Regional Coordinator"
@@ -678,12 +673,13 @@ def all_recipients(self) -> str:
         """If available, return string of all recipients."""
         try:
             event = self.context_objects["event"]
-            contacts = [email for email in event.contact.split(TAG_SEPARATOR)]
             task_emails = [
                 t.person.email
                 for t in event.task_set.filter(role__name__in=["host", "instructor"])
             ]
-            return ", ".join(task_emails + contacts)
+            contacts = event.contact.split(TAG_SEPARATOR)
+            all_emails = list(filter(bool, task_emails + contacts))
+            return ", ".join(all_emails)
         except KeyError:
             return ""
 
@@ -769,10 +765,9 @@ def get_additional_context(self, objects, *args, **kwargs):
         except IndexError:
             context["supporting_instructor2"] = None
 
-        additional_contacts = [
-            email for email in event.contact.split(TAG_SEPARATOR) if email
-        ]
-        context["all_emails"] = [t.person.email for t in tasks] + additional_contacts
+        task_emails = [t.person.email for t in tasks]
+        contacts = event.contact.split(TAG_SEPARATOR)
+        context["all_emails"] = list(filter(bool, task_emails + contacts))
 
         context["assignee"] = (
             event.assigned_to.full_name if event.assigned_to else "Regional Coordinator"


Test patch:
diff --git a/amy/autoemails/tests/test_instructorshostintroductionaction.py b/amy/autoemails/tests/test_instructorshostintroductionaction.py
--- a/amy/autoemails/tests/test_instructorshostintroductionaction.py
+++ b/amy/autoemails/tests/test_instructorshostintroductionaction.py
@@ -29,7 +29,7 @@ def setUp(self):
         )
         # by default there's only self-organized organization
         Organization.objects.bulk_create(
-            [Organization(domain="carpentries.org", fullname="Instructor Training"),]
+            [Organization(domain="carpentries.org", fullname="Instructor Training")]
         )
 
         self.host = Role.objects.create(name="host")
@@ -230,69 +230,6 @@ def testContext(self):
         )
         self.assertEqual(ctx, expected)
 
-    def testContextEmptyContact(self):
-        """Make sure `get_additional_context` works correctly when contacts are empty
-        for the event."""
-        a = InstructorsHostIntroductionAction(
-            trigger=Trigger(action="test-action", template=EmailTemplate())
-        )
-
-        # totally fake Event
-        e = Event.objects.create(
-            slug="test-event",
-            host=Organization.objects.first(),
-            administrator=Organization.objects.get(domain="carpentries.org"),
-            start=date.today() + timedelta(days=7),
-            end=date.today() + timedelta(days=8),
-            contact="",
-            country="GB",
-        )
-        e.tags.set(Tag.objects.filter(name__in=["LC", "automated-email"]))
-        # tasks
-        host = Task.objects.create(person=self.person1, role=self.host, event=e)
-        instructor1 = Task.objects.create(
-            person=self.person2, role=self.instructor, event=e,
-        )
-        instructor2 = Task.objects.create(
-            person=self.person3, role=self.instructor, event=e,
-        )
-        supporting_instructor1 = Task.objects.create(
-            person=self.person4, role=self.supporting_instructor, event=e,
-        )
-        supporting_instructor2 = Task.objects.create(
-            person=self.person5, role=self.supporting_instructor, event=e,
-        )
-
-        ctx = a.get_additional_context(objects=dict(event=e))
-        self.maxDiff = None
-        expected = dict(
-            workshop=e,
-            workshop_main_type="LC",
-            dates=e.human_readable_date,
-            workshop_host=Organization.objects.first(),
-            regional_coordinator_email=["admin-uk@carpentries.org"],
-            host=host.person,
-            instructors=[instructor1.person, instructor2.person],
-            instructor1=instructor1.person,
-            instructor2=instructor2.person,
-            supporting_instructors=[
-                supporting_instructor1.person,
-                supporting_instructor2.person,
-            ],
-            supporting_instructor1=supporting_instructor1.person,
-            supporting_instructor2=supporting_instructor2.person,
-            all_emails=[
-                "hp@magic.uk",
-                "rw@magic.uk",
-                "hg@magic.uk",
-                "peter@webslinger.net",
-                "me@stark.com",
-            ],
-            assignee="Regional Coordinator",
-            tags=["LC", "automated-email"],
-        )
-        self.assertEqual(ctx, expected)
-
     def testRecipients(self):
         """Make sure InstructorsHostIntroductionAction correctly renders recipients.
 
@@ -392,3 +329,37 @@ def test_all_recipients(self):
             a.all_recipients(),
             "hp@magic.uk, rw@magic.uk, hg@magic.uk, test@hogwart.com, test2@magic.uk",
         )
+
+    def test_drop_empty_contacts(self):
+        """Make sure `get_additional_context` works correctly when contacts are empty
+        for the event."""
+        e = Event.objects.create(
+            slug="test-event",
+            host=Organization.objects.first(),
+            administrator=Organization.objects.get(domain="carpentries.org"),
+            start=date.today() + timedelta(days=7),
+            end=date.today() + timedelta(days=8),
+            contact="",
+            country="GB",
+        )
+        e.tags.set(Tag.objects.filter(name__in=["LC", "automated-email"]))
+
+        a = InstructorsHostIntroductionAction(
+            trigger=Trigger(action="test-action", template=EmailTemplate()),
+            objects=dict(event=e),
+        )
+
+        self.person1.email = ""
+        self.person1.save()
+        Task.objects.create(person=self.person1, role=self.host, event=e)
+        Task.objects.create(
+            person=self.person2, role=self.instructor, event=e,
+        )
+        Task.objects.create(
+            person=self.person3, role=self.instructor, event=e,
+        )
+
+        ctx = a.get_additional_context(objects=dict(event=e))
+        expected = ["rw@magic.uk", "hg@magic.uk"]
+        self.assertEqual(ctx["all_emails"], expected)
+        self.assertEqual(a.all_recipients(), "rw@magic.uk, hg@magic.uk")
diff --git a/amy/autoemails/tests/test_selforganisedrequestaction.py b/amy/autoemails/tests/test_selforganisedrequestaction.py
--- a/amy/autoemails/tests/test_selforganisedrequestaction.py
+++ b/amy/autoemails/tests/test_selforganisedrequestaction.py
@@ -7,9 +7,6 @@
 from extrequests.models import SelfOrganisedSubmission
 from workshops.fields import TAG_SEPARATOR
 from workshops.models import (
-    Task,
-    Role,
-    Person,
     Event,
     Tag,
     Organization,
@@ -180,7 +177,7 @@ def testContext(self):
                 short_notice=True,
                 all_emails=["harry@hogwarts.edu", "hg@magic.uk", "rw@magic.uk"],
                 assignee="Regional Coordinator",
-                tags=['LC', 'automated-email', 'Circuits'],
+                tags=["LC", "automated-email", "Circuits"],
             ),
         )
 
@@ -288,3 +285,39 @@ def test_all_recipients(self):
             a.all_recipients(), "harry@hogwarts.edu, hg@magic.uk, rw@magic.uk"
         )
 
+    def test_drop_empty_contacts(self):
+        e = Event.objects.create(
+            slug="test-event",
+            host=Organization.objects.first(),
+            administrator=Organization.objects.get(domain="self-organized"),
+            start=date.today() + timedelta(days=7),
+            end=date.today() + timedelta(days=8),
+            country="GB",
+            contact="test@example.com",  # this won't be picked up
+        )
+        e.tags.set(Tag.objects.filter(name__in=["LC", "Circuits", "automated-email"]))
+        r = SelfOrganisedSubmission.objects.create(
+            state="p",
+            personal="Harry",
+            family="Potter",
+            email="",
+            institution_other_name="Hogwarts",
+            workshop_url="",
+            workshop_format="",
+            workshop_format_other="",
+            workshop_types_other_explain="",
+            language=Language.objects.get(name="English"),
+            event=e,
+            additional_contact=TAG_SEPARATOR,
+        )
+        r.workshop_types.set(Curriculum.objects.filter(carpentry="LC"))
+
+        a = SelfOrganisedRequestAction(
+            trigger=Trigger(action="test-action", template=EmailTemplate()),
+            objects=dict(event=e, request=r),
+        )
+
+        self.assertEqual(a.all_recipients(), "")
+        self.assertEqual(
+            a.get_additional_context(dict(event=e, request=r))["all_emails"], []
+        )