Repository: comic/grand-challenge.org

Issue:
Reader study detail page is slow for editors
With many readers the reader study detail page has slowed down for the reader study editor (28 readers, almost 1s TTFB). 

We should try to optimise some of these queries, and split up the pages into separate views.


Discussion:
I think this is mainly caused by the reader progress queries, so that would be the first place to optimize.
This is mainly caused by a massive amount of duplicate queries. I should still have some local WIP for this, but I'd have to dig a bit to recover it. This will take quite a bit of time to properly debug and fix I think.

Resolving patch:
diff --git a/app/grandchallenge/reader_studies/urls.py b/app/grandchallenge/reader_studies/urls.py
--- a/app/grandchallenge/reader_studies/urls.py
+++ b/app/grandchallenge/reader_studies/urls.py
@@ -20,6 +20,7 @@
     ReaderStudyPermissionRequestUpdate,
     ReaderStudyStatistics,
     ReaderStudyUpdate,
+    ReadersProgress,
     ReadersUpdate,
 )
 
@@ -83,6 +84,11 @@
         ReadersUpdate.as_view(),
         name="readers-update",
     ),
+    path(
+        "<slug>/readers/progress",
+        ReadersProgress.as_view(),
+        name="readers-progress",
+    ),
     path(
         "<slug>/permission-requests/",
         ReaderStudyPermissionRequestList.as_view(),
diff --git a/app/grandchallenge/reader_studies/views.py b/app/grandchallenge/reader_studies/views.py
--- a/app/grandchallenge/reader_studies/views.py
+++ b/app/grandchallenge/reader_studies/views.py
@@ -186,33 +186,30 @@ def get_context_data(self, **kwargs):
         object_perms = get_perms(self.request.user, self.object)
 
         if f"change_{ReaderStudy._meta.model_name}" in object_perms:
-            readers = [
-                {
-                    "obj": reader,
-                    "progress": self.object.get_progress_for_user(reader),
-                }
-                for reader in self.object.readers_group.user_set.all()
-            ]
-
             reader_remove_form = ReadersForm()
             reader_remove_form.fields["action"].initial = ReadersForm.REMOVE
 
             editor_remove_form = EditorsForm()
             editor_remove_form.fields["action"].initial = EditorsForm.REMOVE
 
-            answers_remove_form = AnswersRemoveForm()
-
             pending_permission_requests = ReaderStudyPermissionRequest.objects.filter(
                 reader_study=context["object"],
                 status=ReaderStudyPermissionRequest.PENDING,
             ).count()
 
+            readers = (
+                self.object.readers_group.user_set.select_related(
+                    "user_profile", "verification"
+                )
+                .order_by("username")
+                .all()
+            )
             context.update(
                 {
                     "readers": readers,
-                    "editor_remove_form": editor_remove_form,
+                    "num_readers": self.object.readers_group.user_set.count(),
                     "reader_remove_form": reader_remove_form,
-                    "answers_remove_form": answers_remove_form,
+                    "editor_remove_form": editor_remove_form,
                     "example_ground_truth": self.object.get_example_ground_truth_csv_text(
                         limit=2
                     ),
@@ -632,6 +629,46 @@ class ReadersUpdate(ReaderStudyUserGroupUpdateMixin):
     success_message = "Readers successfully updated"
 
 
+class ReadersProgress(
+    LoginRequiredMixin, ObjectPermissionRequiredMixin, DetailView
+):
+    model = ReaderStudy
+    template_name = "reader_studies/readers_progress.html"
+    permission_required = (
+        f"{ReaderStudy._meta.app_label}.change_{ReaderStudy._meta.model_name}"
+    )
+    raise_exception = True
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+
+        reader_remove_form = ReadersForm()
+        reader_remove_form.fields["action"].initial = ReadersForm.REMOVE
+
+        readers = [
+            {
+                "obj": reader,
+                "progress": self.object.get_progress_for_user(reader),
+            }
+            for reader in self.object.readers_group.user_set.select_related(
+                "user_profile", "verification"
+            )
+            .order_by("username")
+            .all()
+        ]
+
+        context.update(
+            {
+                "reader_study": self.object,
+                "readers": readers,
+                "reader_remove_form": reader_remove_form,
+                "num_readers": self.object.readers_group.user_set.count(),
+            }
+        )
+
+        return context
+
+
 class AnswersRemove(
     LoginRequiredMixin,
     ObjectPermissionRequiredMixin,


Test patch:
diff --git a/app/tests/reader_studies_tests/test_permissions.py b/app/tests/reader_studies_tests/test_permissions.py
--- a/app/tests/reader_studies_tests/test_permissions.py
+++ b/app/tests/reader_studies_tests/test_permissions.py
@@ -124,6 +124,7 @@ def test_rs_detail_view_permissions(client):
         "add-question",
         "editors-update",
         "readers-update",
+        "readers-progress",
     ],
 )
 def test_rs_edit_view_permissions(client, view_name):