prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Reduce database calls for `update_challenge_results_cache()`
  
  
  --- 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/challenges/tasks.py`
  Content:
  ```
  1 from celery import shared_task
  2 from django.core.mail import mail_managers
  3 from requests import exceptions, get
  4 
  5 from grandchallenge.challenges.models import Challenge, ExternalChallenge
  6 from grandchallenge.evaluation.models import Evaluation
  7 from grandchallenge.subdomains.utils import reverse
  8 
  9 
  10 @shared_task
  11 def update_challenge_results_cache():
  12     for c in Challenge.objects.all():
  13         kwargs = {
  14             "cached_num_participants": c.participants_group.user_set.all().count()
  15         }
  16 
  17         challenge_results = Evaluation.objects.filter(
  18             submission__phase__challenge=c, published=True
  19         ).order_by("-created")
  20 
  21         try:
  22             kwargs.update(
  23                 {
  24                     "cached_num_results": challenge_results.count(),
  25                     "cached_latest_result": challenge_results.first().created,
  26                 }
  27             )
  28         except AttributeError:
  29             # No results for this challenge
  30             kwargs.update(
  31                 {"cached_num_results": 0, "cached_latest_result": None}
  32             )
  33 
  34         Challenge.objects.filter(pk=c.pk).update(**kwargs)
  35 
  36 
  37 @shared_task
  38 def check_external_challenge_urls():
  39     """
  40     Checks that all external challenge urls are reachable.
  41 
  42     Emails the managers if any of the challenges are not.
  43     """
  44     challenges = ExternalChallenge.objects.filter(hidden=False)
  45     errors = []
  46 
  47     for challenge in challenges:
  48         try:
  49             url = challenge.homepage
  50             if not url.startswith("http"):
  51                 url = "http://" + url
  52             r = get(url, timeout=60)
  53             # raise an exception when we receive a http error (e.g., 404)
  54             r.raise_for_status()
  55         except exceptions.RequestException as err:
  56             update_url = reverse(
  57                 "challenges:external-update",
  58                 kwargs={"short_name": challenge.short_name},
  59             )
  60             errors.append(
  61                 f"Error when trying to access '{challenge}': {err}. You can "
  62                 f"update it here: {update_url}"
  63             )
  64 
  65     if errors:
  66         mail_managers(
  67             subject=f"Unreachable external challenges ({len(errors)})",
  68             message="\n\n".join(errors),
  69         )
  70
  ```
  --- 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/challenges/tasks.py b/app/grandchallenge/challenges/tasks.py
  --- a/app/grandchallenge/challenges/tasks.py
  +++ b/app/grandchallenge/challenges/tasks.py
  @@ -1,5 +1,7 @@
   from celery import shared_task
  +from django.contrib.auth import get_user_model
   from django.core.mail import mail_managers
  +from django.db.models import Count, Max
   from requests import exceptions, get
   
   from grandchallenge.challenges.models import Challenge, ExternalChallenge
  @@ -9,29 +11,47 @@
   
   @shared_task
   def update_challenge_results_cache():
  -    for c in Challenge.objects.all():
  -        kwargs = {
  -            "cached_num_participants": c.participants_group.user_set.all().count()
  -        }
  -
  -        challenge_results = Evaluation.objects.filter(
  -            submission__phase__challenge=c, published=True
  -        ).order_by("-created")
  +    challenges = Challenge.objects.all()
  +    evaluation_info = (
  +        Evaluation.objects.filter(published=True)
  +        .values("submission__phase__challenge_id")
  +        .annotate(
  +            cached_num_results=Count("submission__phase__challenge_id"),
  +            cached_latest_result=Max("created"),
  +        )
  +    )
  +    evaluation_info_by_challenge = {
  +        str(v["submission__phase__challenge_id"]): v for v in evaluation_info
  +    }
  +    participant_counts = (
  +        get_user_model()
  +        .objects.values("groups__participants_of_challenge")
  +        .annotate(cached_num_participants=Count("pk"))
  +    )
  +    participant_counts_by_challenge = {
  +        str(v["groups__participants_of_challenge"]): v
  +        for v in participant_counts
  +    }
   
  -        try:
  -            kwargs.update(
  -                {
  -                    "cached_num_results": challenge_results.count(),
  -                    "cached_latest_result": challenge_results.first().created,
  -                }
  -            )
  -        except AttributeError:
  -            # No results for this challenge
  -            kwargs.update(
  -                {"cached_num_results": 0, "cached_latest_result": None}
  -            )
  +    for c in challenges:
  +        c.cached_num_results = evaluation_info_by_challenge.get(
  +            str(c.pk), {}
  +        ).get("cached_num_results", 0)
  +        c.cached_latest_result = evaluation_info_by_challenge.get(
  +            str(c.pk), {}
  +        ).get("cached_latest_result", None)
  +        c.cached_num_participants = participant_counts_by_challenge.get(
  +            str(c.pk), {}
  +        ).get("cached_num_participants", 0)
   
  -        Challenge.objects.filter(pk=c.pk).update(**kwargs)
  +    Challenge.objects.bulk_update(
  +        challenges,
  +        [
  +            "cached_num_results",
  +            "cached_num_participants",
  +            "cached_latest_result",
  +        ],
  +    )
   
   
   @shared_task
  
verification_info: {"golden_diff": "diff --git a/app/grandchallenge/challenges/tasks.py b/app/grandchallenge/challenges/tasks.py\n--- a/app/grandchallenge/challenges/tasks.py\n+++ b/app/grandchallenge/challenges/tasks.py\n@@ -1,5 +1,7 @@\n from celery import shared_task\n+from django.contrib.auth import get_user_model\n from django.core.mail import mail_managers\n+from django.db.models import Count, Max\n from requests import exceptions, get\n \n from grandchallenge.challenges.models import Challenge, ExternalChallenge\n@@ -9,29 +11,47 @@\n \n @shared_task\n def update_challenge_results_cache():\n-    for c in Challenge.objects.all():\n-        kwargs = {\n-            \"cached_num_participants\": c.participants_group.user_set.all().count()\n-        }\n-\n-        challenge_results = Evaluation.objects.filter(\n-            submission__phase__challenge=c, published=True\n-        ).order_by(\"-created\")\n+    challenges = Challenge.objects.all()\n+    evaluation_info = (\n+        Evaluation.objects.filter(published=True)\n+        .values(\"submission__phase__challenge_id\")\n+        .annotate(\n+            cached_num_results=Count(\"submission__phase__challenge_id\"),\n+            cached_latest_result=Max(\"created\"),\n+        )\n+    )\n+    evaluation_info_by_challenge = {\n+        str(v[\"submission__phase__challenge_id\"]): v for v in evaluation_info\n+    }\n+    participant_counts = (\n+        get_user_model()\n+        .objects.values(\"groups__participants_of_challenge\")\n+        .annotate(cached_num_participants=Count(\"pk\"))\n+    )\n+    participant_counts_by_challenge = {\n+        str(v[\"groups__participants_of_challenge\"]): v\n+        for v in participant_counts\n+    }\n \n-        try:\n-            kwargs.update(\n-                {\n-                    \"cached_num_results\": challenge_results.count(),\n-                    \"cached_latest_result\": challenge_results.first().created,\n-                }\n-            )\n-        except AttributeError:\n-            # No results for this challenge\n-            kwargs.update(\n-                {\"cached_num_results\": 0, \"cached_latest_result\": None}\n-            )\n+    for c in challenges:\n+        c.cached_num_results = evaluation_info_by_challenge.get(\n+            str(c.pk), {}\n+        ).get(\"cached_num_results\", 0)\n+        c.cached_latest_result = evaluation_info_by_challenge.get(\n+            str(c.pk), {}\n+        ).get(\"cached_latest_result\", None)\n+        c.cached_num_participants = participant_counts_by_challenge.get(\n+            str(c.pk), {}\n+        ).get(\"cached_num_participants\", 0)\n \n-        Challenge.objects.filter(pk=c.pk).update(**kwargs)\n+    Challenge.objects.bulk_update(\n+        challenges,\n+        [\n+            \"cached_num_results\",\n+            \"cached_num_participants\",\n+            \"cached_latest_result\",\n+        ],\n+    )\n \n \n @shared_task\n", "issue": "Reduce database calls for `update_challenge_results_cache()`\n\n", "before_files": [{"content": "from celery import shared_task\nfrom django.core.mail import mail_managers\nfrom requests import exceptions, get\n\nfrom grandchallenge.challenges.models import Challenge, ExternalChallenge\nfrom grandchallenge.evaluation.models import Evaluation\nfrom grandchallenge.subdomains.utils import reverse\n\n\n@shared_task\ndef update_challenge_results_cache():\n    for c in Challenge.objects.all():\n        kwargs = {\n            \"cached_num_participants\": c.participants_group.user_set.all().count()\n        }\n\n        challenge_results = Evaluation.objects.filter(\n            submission__phase__challenge=c, published=True\n        ).order_by(\"-created\")\n\n        try:\n            kwargs.update(\n                {\n                    \"cached_num_results\": challenge_results.count(),\n                    \"cached_latest_result\": challenge_results.first().created,\n                }\n            )\n        except AttributeError:\n            # No results for this challenge\n            kwargs.update(\n                {\"cached_num_results\": 0, \"cached_latest_result\": None}\n            )\n\n        Challenge.objects.filter(pk=c.pk).update(**kwargs)\n\n\n@shared_task\ndef check_external_challenge_urls():\n    \"\"\"\n    Checks that all external challenge urls are reachable.\n\n    Emails the managers if any of the challenges are not.\n    \"\"\"\n    challenges = ExternalChallenge.objects.filter(hidden=False)\n    errors = []\n\n    for challenge in challenges:\n        try:\n            url = challenge.homepage\n            if not url.startswith(\"http\"):\n                url = \"http://\" + url\n            r = get(url, timeout=60)\n            # raise an exception when we receive a http error (e.g., 404)\n            r.raise_for_status()\n        except exceptions.RequestException as err:\n            update_url = reverse(\n                \"challenges:external-update\",\n                kwargs={\"short_name\": challenge.short_name},\n            )\n            errors.append(\n                f\"Error when trying to access '{challenge}': {err}. You can \"\n                f\"update it here: {update_url}\"\n            )\n\n    if errors:\n        mail_managers(\n            subject=f\"Unreachable external challenges ({len(errors)})\",\n            message=\"\\n\\n\".join(errors),\n        )\n", "path": "app/grandchallenge/challenges/tasks.py"}], "after_files": [{"content": "from celery import shared_task\nfrom django.contrib.auth import get_user_model\nfrom django.core.mail import mail_managers\nfrom django.db.models import Count, Max\nfrom requests import exceptions, get\n\nfrom grandchallenge.challenges.models import Challenge, ExternalChallenge\nfrom grandchallenge.evaluation.models import Evaluation\nfrom grandchallenge.subdomains.utils import reverse\n\n\n@shared_task\ndef update_challenge_results_cache():\n    challenges = Challenge.objects.all()\n    evaluation_info = (\n        Evaluation.objects.filter(published=True)\n        .values(\"submission__phase__challenge_id\")\n        .annotate(\n            cached_num_results=Count(\"submission__phase__challenge_id\"),\n            cached_latest_result=Max(\"created\"),\n        )\n    )\n    evaluation_info_by_challenge = {\n        str(v[\"submission__phase__challenge_id\"]): v for v in evaluation_info\n    }\n    participant_counts = (\n        get_user_model()\n        .objects.values(\"groups__participants_of_challenge\")\n        .annotate(cached_num_participants=Count(\"pk\"))\n    )\n    participant_counts_by_challenge = {\n        str(v[\"groups__participants_of_challenge\"]): v\n        for v in participant_counts\n    }\n\n    for c in challenges:\n        c.cached_num_results = evaluation_info_by_challenge.get(\n            str(c.pk), {}\n        ).get(\"cached_num_results\", 0)\n        c.cached_latest_result = evaluation_info_by_challenge.get(\n            str(c.pk), {}\n        ).get(\"cached_latest_result\", None)\n        c.cached_num_participants = participant_counts_by_challenge.get(\n            str(c.pk), {}\n        ).get(\"cached_num_participants\", 0)\n\n    Challenge.objects.bulk_update(\n        challenges,\n        [\n            \"cached_num_results\",\n            \"cached_num_participants\",\n            \"cached_latest_result\",\n        ],\n    )\n\n\n@shared_task\ndef check_external_challenge_urls():\n    \"\"\"\n    Checks that all external challenge urls are reachable.\n\n    Emails the managers if any of the challenges are not.\n    \"\"\"\n    challenges = ExternalChallenge.objects.filter(hidden=False)\n    errors = []\n\n    for challenge in challenges:\n        try:\n            url = challenge.homepage\n            if not url.startswith(\"http\"):\n                url = \"http://\" + url\n            r = get(url, timeout=60)\n            # raise an exception when we receive a http error (e.g., 404)\n            r.raise_for_status()\n        except exceptions.RequestException as err:\n            update_url = reverse(\n                \"challenges:external-update\",\n                kwargs={\"short_name\": challenge.short_name},\n            )\n            errors.append(\n                f\"Error when trying to access '{challenge}': {err}. You can \"\n                f\"update it here: {update_url}\"\n            )\n\n    if errors:\n        mail_managers(\n            subject=f\"Unreachable external challenges ({len(errors)})\",\n            message=\"\\n\\n\".join(errors),\n        )\n", "path": "app/grandchallenge/challenges/tasks.py"}]}