Repository: comic/grand-challenge.org

Issue:
Algorithm interfaces may not match at runtime
Scenario:

- User makes an algorithm submission
- During the evaluation the user changes the input/output interfaces
- Jobs for the algorithm cannot be scheduled as the input and output interfaces do not match, submission remains incomplete

Changing the algorithm interfaces also allows the user to bypass the algorithm count check, we should add something that locks the interfaces for challenge algorithms.


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/algorithms/forms.py b/app/grandchallenge/algorithms/forms.py
--- a/app/grandchallenge/algorithms/forms.py
+++ b/app/grandchallenge/algorithms/forms.py
@@ -578,10 +578,15 @@ class AlgorithmUpdateForm(AlgorithmForm):
     class Meta(AlgorithmForm.Meta):
         fields = AlgorithmForm.Meta.fields + ("repo_name",)
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, *args, interfaces_editable, **kwargs):
         super().__init__(*args, **kwargs)
+
         self.helper.layout[0].append("repo_name")
 
+        if not interfaces_editable:
+            for field_key in ("inputs", "outputs"):
+                self.fields.pop(field_key)
+
 
 class AlgorithmImageForm(ContainerImageForm):
     requires_memory_gb = IntegerField(
diff --git a/app/grandchallenge/algorithms/views.py b/app/grandchallenge/algorithms/views.py
--- a/app/grandchallenge/algorithms/views.py
+++ b/app/grandchallenge/algorithms/views.py
@@ -220,6 +220,22 @@ class AlgorithmUpdate(
     permission_required = "algorithms.change_algorithm"
     raise_exception = True
 
+    def get_form_kwargs(self):
+        kwargs = super().get_form_kwargs()
+
+        # Only users with the add_algorithm permission can change
+        # the input and output interfaces, other users must use
+        # the interfaces pre-set by the Phase
+        kwargs.update(
+            {
+                "interfaces_editable": self.request.user.has_perm(
+                    "algorithms.add_algorithm"
+                )
+            }
+        )
+
+        return kwargs
+
 
 class AlgorithmDescriptionUpdate(
     LoginRequiredMixin,


Test patch:
diff --git a/app/tests/algorithms_tests/test_views.py b/app/tests/algorithms_tests/test_views.py
--- a/app/tests/algorithms_tests/test_views.py
+++ b/app/tests/algorithms_tests/test_views.py
@@ -1063,3 +1063,28 @@ def test_algorithm_image_activate(settings, client, algorithm_io_image):
     assert i2.is_desired_version
     assert alg.active_image == i2
     assert i2.is_in_registry
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize("interfaces_editable", (True, False))
+def test_algorithm_interfaces_editable(client, interfaces_editable):
+    creator = UserFactory()
+    VerificationFactory(user=creator, is_verified=True)
+
+    if interfaces_editable:
+        assign_perm("algorithms.add_algorithm", creator)
+
+    alg = AlgorithmFactory()
+    alg.add_editor(user=creator)
+
+    response = get_view_for_user(
+        viewname="algorithms:update",
+        client=client,
+        reverse_kwargs={"slug": alg.slug},
+        user=creator,
+    )
+
+    assert ("inputs" in response.context["form"].fields) is interfaces_editable
+    assert (
+        "outputs" in response.context["form"].fields
+    ) is interfaces_editable
diff --git a/app/tests/evaluation_tests/test_views.py b/app/tests/evaluation_tests/test_views.py
--- a/app/tests/evaluation_tests/test_views.py
+++ b/app/tests/evaluation_tests/test_views.py
@@ -710,7 +710,7 @@ def test_create_algorithm_for_phase_presets(client):
     assert algorithm.view_content == phase.view_content
     assert algorithm.workstation.slug == settings.DEFAULT_WORKSTATION_SLUG
     assert algorithm.contact_email == admin.email
-    assert algorithm.display_editors
+    assert algorithm.display_editors is True
     assert list(algorithm.structures.all()) == []
     assert list(algorithm.modalities.all()) == []
     assert algorithm.logo == phase.challenge.logo