prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Duplicate email to algorithm user that is also an editor when job fails
  A tiny improvement request: When an algorithm job fails, the editors and the job creator are emailed (#1018), but there is apparently no check whether the job creator is one of the algorithm editor and would receive an email anyway - I was testing a new algorithm and always received two emails when a job failed.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `app/grandchallenge/algorithms/emails.py`
  Content:
  ```
  1 from django.conf import settings
  2 from django.contrib.sites.models import Site
  3 from django.core.mail import send_mail
  4 
  5 from grandchallenge.core.utils.email import send_templated_email
  6 from grandchallenge.evaluation.templatetags.evaluation_extras import user_error
  7 
  8 
  9 def send_permission_request_email(obj):
  10     """
  11     Emails the editors that someone has requested to view an algorithm.
  12 
  13     Parameters
  14     ----------
  15     obj:
  16         AlgorithmPermissionRequest object containing info on which
  17         user requested access to which algorithm.
  18     """
  19     title = f"[{obj.algorithm.title}] New access request"
  20     kwargs = {
  21         "user": obj.user,
  22         "site": Site.objects.get_current(),
  23         "algorithm": obj.algorithm,
  24     }
  25     for editor in obj.algorithm.editors_group.user_set.all():
  26         kwargs["editor"] = editor
  27         send_templated_email(
  28             title,
  29             "algorithms/emails/access_request.html",
  30             kwargs,
  31             [editor.email],
  32         )
  33 
  34 
  35 def send_permission_granted_email(obj):
  36     """
  37     Emails the requester that their request has been approved.
  38 
  39     Parameters
  40     ----------
  41     obj:
  42         AlgorithmPermissionRequest object containing info on which
  43         user requested access to which algorithm.
  44     """
  45     title = f"[{obj.algorithm.title}] Access granted"
  46     kwargs = {
  47         "user": obj.user,
  48         "site": Site.objects.get_current(),
  49         "algorithm": obj.algorithm,
  50     }
  51     send_templated_email(
  52         title,
  53         "algorithms/emails/access_granted.html",
  54         kwargs,
  55         [obj.user.email],
  56     )
  57 
  58 
  59 def send_permission_denied_email(obj):
  60     """
  61     Emails the requester that their request has been approved.
  62 
  63     Parameters
  64     ----------
  65     obj:
  66         AlgorithmPermissionRequest object containing info on which
  67         user requested access to which algorithm and optionally the
  68         reason for rejection.
  69     """
  70     title = f"[{obj.algorithm.title}] Access denied"
  71     kwargs = {
  72         "user": obj.user,
  73         "site": Site.objects.get_current(),
  74         "algorithm": obj.algorithm,
  75         "permission_request": obj,
  76     }
  77     send_templated_email(
  78         title,
  79         "algorithms/emails/access_denied.html",
  80         kwargs,
  81         [obj.user.email],
  82     )
  83 
  84 
  85 def send_failed_job_email(job):
  86     algorithm = job.algorithm_image.algorithm
  87     message = (
  88         f"Unfortunately your job for algorithm "
  89         f"'{algorithm.title}' failed with an error. "
  90         f"The error message is:\n\n"
  91         f"{user_error(job.output)}\n\n"
  92         f"You may wish to try and correct this, or contact the challenge "
  93         f"organizers. The following information may help them:\n"
  94         f"User: {job.creator.username}\n"
  95         f"Job ID: {job.pk}\n"
  96         f"Submission ID: {job.pk}"
  97     )
  98     recipient_emails = [
  99         o.email for o in algorithm.editors_group.user_set.all()
  100     ]
  101     recipient_emails.append(job.creator.email)
  102     for email in recipient_emails:
  103         send_mail(
  104             subject=(
  105                 f"[{Site.objects.get_current().domain.lower()}] "
  106                 f"[{algorithm.title.lower()}] "
  107                 f"Job Failed"
  108             ),
  109             message=message,
  110             from_email=settings.DEFAULT_FROM_EMAIL,
  111             recipient_list=[email],
  112         )
  113
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/app/grandchallenge/algorithms/emails.py b/app/grandchallenge/algorithms/emails.py
  --- a/app/grandchallenge/algorithms/emails.py
  +++ b/app/grandchallenge/algorithms/emails.py
  @@ -99,7 +99,8 @@
           o.email for o in algorithm.editors_group.user_set.all()
       ]
       recipient_emails.append(job.creator.email)
  -    for email in recipient_emails:
  +
  +    for email in {*recipient_emails}:
           send_mail(
               subject=(
                   f"[{Site.objects.get_current().domain.lower()}] "
  
verification_info: {"golden_diff": "diff --git a/app/grandchallenge/algorithms/emails.py b/app/grandchallenge/algorithms/emails.py\n--- a/app/grandchallenge/algorithms/emails.py\n+++ b/app/grandchallenge/algorithms/emails.py\n@@ -99,7 +99,8 @@\n         o.email for o in algorithm.editors_group.user_set.all()\n     ]\n     recipient_emails.append(job.creator.email)\n-    for email in recipient_emails:\n+\n+    for email in {*recipient_emails}:\n         send_mail(\n             subject=(\n                 f\"[{Site.objects.get_current().domain.lower()}] \"\n", "issue": "Duplicate email to algorithm user that is also an editor when job fails\nA tiny improvement request: When an algorithm job fails, the editors and the job creator are emailed (#1018), but there is apparently no check whether the job creator is one of the algorithm editor and would receive an email anyway - I was testing a new algorithm and always received two emails when a job failed.\n", "before_files": [{"content": "from django.conf import settings\nfrom django.contrib.sites.models import Site\nfrom django.core.mail import send_mail\n\nfrom grandchallenge.core.utils.email import send_templated_email\nfrom grandchallenge.evaluation.templatetags.evaluation_extras import user_error\n\n\ndef send_permission_request_email(obj):\n    \"\"\"\n    Emails the editors that someone has requested to view an algorithm.\n\n    Parameters\n    ----------\n    obj:\n        AlgorithmPermissionRequest object containing info on which\n        user requested access to which algorithm.\n    \"\"\"\n    title = f\"[{obj.algorithm.title}] New access request\"\n    kwargs = {\n        \"user\": obj.user,\n        \"site\": Site.objects.get_current(),\n        \"algorithm\": obj.algorithm,\n    }\n    for editor in obj.algorithm.editors_group.user_set.all():\n        kwargs[\"editor\"] = editor\n        send_templated_email(\n            title,\n            \"algorithms/emails/access_request.html\",\n            kwargs,\n            [editor.email],\n        )\n\n\ndef send_permission_granted_email(obj):\n    \"\"\"\n    Emails the requester that their request has been approved.\n\n    Parameters\n    ----------\n    obj:\n        AlgorithmPermissionRequest object containing info on which\n        user requested access to which algorithm.\n    \"\"\"\n    title = f\"[{obj.algorithm.title}] Access granted\"\n    kwargs = {\n        \"user\": obj.user,\n        \"site\": Site.objects.get_current(),\n        \"algorithm\": obj.algorithm,\n    }\n    send_templated_email(\n        title,\n        \"algorithms/emails/access_granted.html\",\n        kwargs,\n        [obj.user.email],\n    )\n\n\ndef send_permission_denied_email(obj):\n    \"\"\"\n    Emails the requester that their request has been approved.\n\n    Parameters\n    ----------\n    obj:\n        AlgorithmPermissionRequest object containing info on which\n        user requested access to which algorithm and optionally the\n        reason for rejection.\n    \"\"\"\n    title = f\"[{obj.algorithm.title}] Access denied\"\n    kwargs = {\n        \"user\": obj.user,\n        \"site\": Site.objects.get_current(),\n        \"algorithm\": obj.algorithm,\n        \"permission_request\": obj,\n    }\n    send_templated_email(\n        title,\n        \"algorithms/emails/access_denied.html\",\n        kwargs,\n        [obj.user.email],\n    )\n\n\ndef send_failed_job_email(job):\n    algorithm = job.algorithm_image.algorithm\n    message = (\n        f\"Unfortunately your job for algorithm \"\n        f\"'{algorithm.title}' failed with an error. \"\n        f\"The error message is:\\n\\n\"\n        f\"{user_error(job.output)}\\n\\n\"\n        f\"You may wish to try and correct this, or contact the challenge \"\n        f\"organizers. The following information may help them:\\n\"\n        f\"User: {job.creator.username}\\n\"\n        f\"Job ID: {job.pk}\\n\"\n        f\"Submission ID: {job.pk}\"\n    )\n    recipient_emails = [\n        o.email for o in algorithm.editors_group.user_set.all()\n    ]\n    recipient_emails.append(job.creator.email)\n    for email in recipient_emails:\n        send_mail(\n            subject=(\n                f\"[{Site.objects.get_current().domain.lower()}] \"\n                f\"[{algorithm.title.lower()}] \"\n                f\"Job Failed\"\n            ),\n            message=message,\n            from_email=settings.DEFAULT_FROM_EMAIL,\n            recipient_list=[email],\n        )\n", "path": "app/grandchallenge/algorithms/emails.py"}], "after_files": [{"content": "from django.conf import settings\nfrom django.contrib.sites.models import Site\nfrom django.core.mail import send_mail\n\nfrom grandchallenge.core.utils.email import send_templated_email\nfrom grandchallenge.evaluation.templatetags.evaluation_extras import user_error\n\n\ndef send_permission_request_email(obj):\n    \"\"\"\n    Emails the editors that someone has requested to view an algorithm.\n\n    Parameters\n    ----------\n    obj:\n        AlgorithmPermissionRequest object containing info on which\n        user requested access to which algorithm.\n    \"\"\"\n    title = f\"[{obj.algorithm.title}] New access request\"\n    kwargs = {\n        \"user\": obj.user,\n        \"site\": Site.objects.get_current(),\n        \"algorithm\": obj.algorithm,\n    }\n    for editor in obj.algorithm.editors_group.user_set.all():\n        kwargs[\"editor\"] = editor\n        send_templated_email(\n            title,\n            \"algorithms/emails/access_request.html\",\n            kwargs,\n            [editor.email],\n        )\n\n\ndef send_permission_granted_email(obj):\n    \"\"\"\n    Emails the requester that their request has been approved.\n\n    Parameters\n    ----------\n    obj:\n        AlgorithmPermissionRequest object containing info on which\n        user requested access to which algorithm.\n    \"\"\"\n    title = f\"[{obj.algorithm.title}] Access granted\"\n    kwargs = {\n        \"user\": obj.user,\n        \"site\": Site.objects.get_current(),\n        \"algorithm\": obj.algorithm,\n    }\n    send_templated_email(\n        title,\n        \"algorithms/emails/access_granted.html\",\n        kwargs,\n        [obj.user.email],\n    )\n\n\ndef send_permission_denied_email(obj):\n    \"\"\"\n    Emails the requester that their request has been approved.\n\n    Parameters\n    ----------\n    obj:\n        AlgorithmPermissionRequest object containing info on which\n        user requested access to which algorithm and optionally the\n        reason for rejection.\n    \"\"\"\n    title = f\"[{obj.algorithm.title}] Access denied\"\n    kwargs = {\n        \"user\": obj.user,\n        \"site\": Site.objects.get_current(),\n        \"algorithm\": obj.algorithm,\n        \"permission_request\": obj,\n    }\n    send_templated_email(\n        title,\n        \"algorithms/emails/access_denied.html\",\n        kwargs,\n        [obj.user.email],\n    )\n\n\ndef send_failed_job_email(job):\n    algorithm = job.algorithm_image.algorithm\n    message = (\n        f\"Unfortunately your job for algorithm \"\n        f\"'{algorithm.title}' failed with an error. \"\n        f\"The error message is:\\n\\n\"\n        f\"{user_error(job.output)}\\n\\n\"\n        f\"You may wish to try and correct this, or contact the challenge \"\n        f\"organizers. The following information may help them:\\n\"\n        f\"User: {job.creator.username}\\n\"\n        f\"Job ID: {job.pk}\\n\"\n        f\"Submission ID: {job.pk}\"\n    )\n    recipient_emails = [\n        o.email for o in algorithm.editors_group.user_set.all()\n    ]\n    recipient_emails.append(job.creator.email)\n\n    for email in {*recipient_emails}:\n        send_mail(\n            subject=(\n                f\"[{Site.objects.get_current().domain.lower()}] \"\n                f\"[{algorithm.title.lower()}] \"\n                f\"Job Failed\"\n            ),\n            message=message,\n            from_email=settings.DEFAULT_FROM_EMAIL,\n            recipient_list=[email],\n        )\n", "path": "app/grandchallenge/algorithms/emails.py"}]}