From 104d10a15f7c0dfbe5d13c07aee9591f265a0ef2 Mon Sep 17 00:00:00 2001
From: Erwan Rouchet <rouchet@teklia.com>
Date: Thu, 15 Oct 2020 11:50:47 +0200
Subject: [PATCH] Optimize Revision.state and ListWorkerVersions

---
 arkindex/dataimport/api.py                 |  8 +++++---
 arkindex/dataimport/models.py              | 23 ++++++++++++++++------
 arkindex/dataimport/serializers/workers.py |  5 +++++
 arkindex/dataimport/tests/test_workers.py  |  9 ++++++---
 4 files changed, 33 insertions(+), 12 deletions(-)

diff --git a/arkindex/dataimport/api.py b/arkindex/dataimport/api.py
index dfb651edbd..9db544c1ba 100644
--- a/arkindex/dataimport/api.py
+++ b/arkindex/dataimport/api.py
@@ -694,9 +694,11 @@ class WorkerVersionList(ListCreateAPIView):
     }
 
     def get_queryset(self):
-        return WorkerVersion.objects.filter(
-            worker_id=self.kwargs['pk']
-        ).prefetch_related('revision').order_by('-revision__created')
+        return WorkerVersion.objects \
+            .filter(worker_id=self.kwargs['pk']) \
+            .select_related('revision__repo', 'worker__repository') \
+            .prefetch_related('revision__refs', 'revision__versions') \
+            .order_by('-revision__created')
 
     def create(self, request, *args, **kwargs):
         serializer = self.get_serializer(data=request.data)
diff --git a/arkindex/dataimport/models.py b/arkindex/dataimport/models.py
index f7e7c91106..19f703e1c3 100644
--- a/arkindex/dataimport/models.py
+++ b/arkindex/dataimport/models.py
@@ -352,13 +352,24 @@ class Revision(IndexableModel):
 
     @property
     def state(self):
-        # Computes revision state according to its versions one
+        """
+        Computes revision state according to its versions'
 
-        # If there is one version in error, revision state is too
-        # Else if there is one version processing, revision state is too
-        # Else if all versions are available, then the revision is too
-        # Else, the revision is created since it has either no version or versions mixing processing/created states
-        states = set(self.versions.values_list('state', flat=True))
+        If there is one version in error, revision state is too
+        Else if there is one version processing, revision state is too
+        Else if all versions are available, then the revision is too
+        Else, the revision is created since it has either no version or versions mixing processing/created states
+        """
+        # This prevents performing another SQL request when versions have already been prefetched.
+        # See https://stackoverflow.com/a/19651840/5990435
+        if (
+            hasattr(self, "_prefetched_objects_cache")
+            and self.versions.field.remote_field.get_cache_name()
+            in self._prefetched_objects_cache
+        ):
+            states = set(version.state for version in self.versions.all())
+        else:
+            states = set(self.versions.values_list('state', flat=True))
 
         if WorkerVersionState.Error in states:
             return WorkerVersionState.Error
diff --git a/arkindex/dataimport/serializers/workers.py b/arkindex/dataimport/serializers/workers.py
index 9598539dca..71d4405fb0 100644
--- a/arkindex/dataimport/serializers/workers.py
+++ b/arkindex/dataimport/serializers/workers.py
@@ -46,6 +46,11 @@ class WorkerVersionSerializer(serializers.ModelSerializer):
             'worker',
         )
         read_only_fields = ('docker_image_name',)
+        # Avoid loading all revisions and all Ponos artifacts when opening this endpoint in a browser
+        extra_kwargs = {
+            'revision': {'style': {'base_template': 'input.html'}},
+            'docker_image': {'style': {'base_template': 'input.html'}},
+        }
 
     def to_representation(self, instance):
         self.fields['revision'] = RevisionWithRefsSerializer(read_only=True)
diff --git a/arkindex/dataimport/tests/test_workers.py b/arkindex/dataimport/tests/test_workers.py
index f0101cc058..0fe294824d 100644
--- a/arkindex/dataimport/tests/test_workers.py
+++ b/arkindex/dataimport/tests/test_workers.py
@@ -162,12 +162,14 @@ class TestWorkersWorkerVersions(FixtureAPITestCase):
 
     # Tests on get_query_set for WorkerVersionList
     def test_versions_list_requires_login(self):
-        response = self.client.get(reverse('api:worker-versions', kwargs={'pk': str(self.worker_1.id)}))
+        with self.assertNumQueries(0):
+            response = self.client.get(reverse('api:worker-versions', kwargs={'pk': str(self.worker_1.id)}))
         self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
 
     def test_versions_list(self):
         self.client.force_login(self.user)
-        response = self.client.get(reverse('api:worker-versions', kwargs={'pk': str(self.worker_1.id)}))
+        with self.assertNumQueries(5):
+            response = self.client.get(reverse('api:worker-versions', kwargs={'pk': str(self.worker_1.id)}))
         self.assertEqual(response.status_code, status.HTTP_200_OK)
         data = response.json()
         self.assertEqual(len(data), 1)
@@ -190,7 +192,8 @@ class TestWorkersWorkerVersions(FixtureAPITestCase):
             configuration={"test": "test2"}
         )
 
-        response = self.client.get(reverse('api:worker-versions', kwargs={'pk': str(worker_2.id)}))
+        with self.assertNumQueries(5):
+            response = self.client.get(reverse('api:worker-versions', kwargs={'pk': str(worker_2.id)}))
         self.assertEqual(response.status_code, status.HTTP_200_OK)
         data = response.json()
         self.assertEqual(len(data), 1)
-- 
GitLab