From 8c221b6d9364128b734b5e49ca0846901089a962 Mon Sep 17 00:00:00 2001
From: Erwan Rouchet <rouchet@teklia.com>
Date: Wed, 4 Dec 2024 12:47:08 +0000
Subject: [PATCH] Set a farm on export processes

---
 arkindex/process/serializers/imports.py       | 27 ++++++-
 .../tests/process/test_export_process.py      | 80 +++++++++++++++++++
 2 files changed, 105 insertions(+), 2 deletions(-)

diff --git a/arkindex/process/serializers/imports.py b/arkindex/process/serializers/imports.py
index 2806fa8fd5..a8e08198d2 100644
--- a/arkindex/process/serializers/imports.py
+++ b/arkindex/process/serializers/imports.py
@@ -415,14 +415,36 @@ class ExportProcessSerializer(ProcessDetailsSerializer):
         read_only=True,
     )
     state = EnumField(State, required=False, read_only=True)
+    farm_id = serializers.PrimaryKeyRelatedField(
+        queryset=Farm.objects.all(),
+        source="farm",
+        write_only=True,
+        allow_null=True,
+        default=None,
+    )
+
+    def validate_farm_id(self, farm):
+        """
+        Using the `default=` on the farm field would allow the default farm
+        to be used without having access rights to it, so we do the ACL checks
+        here during validation. We thus do not filter the field's queryset,
+        as we would be duplicating the ACL checks otherwise.
+        """
+        if farm is None:
+            farm = get_default_farm()
+
+        if farm and not farm.is_available(self.context["request"].user):
+            raise ValidationError(["You do not have access to this farm."])
+
+        return farm
 
     class Meta(ProcessDetailsSerializer.Meta):
-        model = Process
         fields = ProcessDetailsSerializer.Meta.fields + (
             "export_id",
             "format",
             "selection",
-            "configuration"
+            "configuration",
+            "farm_id",
         )
         read_only_fields = ProcessDetailsSerializer.Meta.read_only_fields + (
             "element_name_contains",
@@ -562,6 +584,7 @@ class ExportProcessSerializer(ProcessDetailsSerializer):
             corpus=corpus,
             creator=user,
             element=validated_data.get("element"),
+            farm=validated_data.get("farm"),
         )
         # If using the user selection, create process elements
         if validated_data.get("selection"):
diff --git a/arkindex/process/tests/process/test_export_process.py b/arkindex/process/tests/process/test_export_process.py
index 0ddd0fda9b..3fd3d5b8eb 100644
--- a/arkindex/process/tests/process/test_export_process.py
+++ b/arkindex/process/tests/process/test_export_process.py
@@ -1,11 +1,13 @@
 from datetime import datetime, timedelta, timezone
 from unittest.mock import call, patch
+from uuid import uuid4
 
 from django.test import override_settings
 from rest_framework import status
 from rest_framework.reverse import reverse
 
 from arkindex.documents.models import Corpus, CorpusExport, CorpusExportState
+from arkindex.ponos.models import Farm
 from arkindex.process.models import ArkindexFeature, Process, ProcessMode, WorkerVersion
 from arkindex.project.tests import FixtureAPITestCase
 from arkindex.users.models import Role, User
@@ -524,3 +526,81 @@ class TestExportProcess(FixtureAPITestCase):
             "order_by_name": False,
             "page_type": "page"
         })
+
+    def test_farm(self):
+        farm = Farm.objects.get()
+        self.client.force_login(self.user)
+
+        with self.assertNumQueries(25):
+            response = self.client.post(
+                reverse("api:export-process", kwargs={"corpus_id": str(self.corpus.id)}),
+                {
+                    "format": "pdf",
+                    "export_id": str(self.complete_export.id),
+                    "farm_id": str(farm.id),
+                },
+            )
+            self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+        process = Process.objects.get(id=response.json()["id"])
+        self.assertEqual(process.farm, farm)
+
+    @patch("arkindex.process.serializers.imports.get_default_farm")
+    def test_default_farm(self, get_default_farm_mock):
+        farm = Farm.objects.get()
+        get_default_farm_mock.return_value = farm
+        self.client.force_login(self.user)
+
+        with self.assertNumQueries(24):
+            response = self.client.post(
+                reverse("api:export-process", kwargs={"corpus_id": str(self.corpus.id)}),
+                {
+                    "format": "pdf",
+                    "export_id": str(self.complete_export.id),
+                },
+            )
+            self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+
+        process = Process.objects.get(id=response.json()["id"])
+        self.assertEqual(process.farm, farm)
+
+    @patch("arkindex.ponos.models.Farm.is_available", return_value=False)
+    def test_farm_unavailable(self, is_available_mock):
+        farm = Farm.objects.get()
+        self.client.force_login(self.user)
+
+        with self.assertNumQueries(5):
+            response = self.client.post(
+                reverse("api:export-process", kwargs={"corpus_id": str(self.corpus.id)}),
+                {
+                    "format": "pdf",
+                    "export_id": str(self.complete_export.id),
+                    "farm_id": farm.id,
+                },
+            )
+            self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+        self.assertEqual(response.json(), {
+            "farm_id": ["You do not have access to this farm."],
+        })
+        self.assertFalse(self.corpus.processes.filter(mode=ProcessMode.Export).exists())
+
+    def test_unknown_farm_id(self):
+        self.client.force_login(self.user)
+
+        farm_id = str(uuid4())
+        with self.assertNumQueries(5):
+            response = self.client.post(
+                reverse("api:export-process", kwargs={"corpus_id": str(self.corpus.id)}),
+                {
+                    "format": "pdf",
+                    "export_id": str(self.complete_export.id),
+                    "farm_id": farm_id,
+                },
+            )
+            self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+        self.assertEqual(response.json(), {
+            "farm_id": [f'Invalid pk "{farm_id}" - object does not exist.'],
+        })
+        self.assertFalse(self.corpus.processes.filter(mode=ProcessMode.Export).exists())
-- 
GitLab