Repository: comic/grand-challenge.org

Issue:
Add check for null image on job creation form
It seems that people navigate directly to the form.
Change Pending Job text on Submission detail page
Should be Prerequisite Jobs as the jobs are often in their final state, not pending. Causes some confusion.


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
@@ -82,6 +82,10 @@ class ModelFactsTextField(Field):
 
 
 class JobCreateForm(SaveFormInitMixin, Form):
+    algorithm_image = ModelChoiceField(
+        queryset=None, disabled=True, required=True, widget=HiddenInput
+    )
+
     def __init__(self, *args, algorithm, user, **kwargs):
         super().__init__(*args, **kwargs)
 
@@ -90,6 +94,14 @@ def __init__(self, *args, algorithm, user, **kwargs):
 
         self.helper = FormHelper()
 
+        active_image = self._algorithm.active_image
+
+        if active_image:
+            self.fields[
+                "algorithm_image"
+            ].queryset = AlgorithmImage.objects.filter(pk=active_image.pk)
+            self.fields["algorithm_image"].initial = active_image
+
         for inp in self._algorithm.inputs.all():
             self.fields[inp.slug] = InterfaceFormField(
                 instance=inp,
@@ -109,6 +121,9 @@ def clean(self):
         if self.jobs_limit is not None and self.jobs_limit < 1:
             raise ValidationError("You have run out of algorithm credits")
 
+        if not cleaned_data.get("algorithm_image"):
+            raise ValidationError("This algorithm is not ready to be used")
+
         return cleaned_data
 
 
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
@@ -477,7 +477,11 @@ def create_upload_session(image_files):
         interfaces = {ci.slug: ci for ci in self.algorithm.inputs.all()}
 
         for slug, value in form.cleaned_data.items():
+            if slug == "algorithm_image":
+                continue
+
             ci = interfaces[slug]
+
             if ci.is_image_kind:
                 if value:
                     widget = form.data[f"WidgetChoice-{ci.slug}"]
@@ -518,7 +522,7 @@ def create_upload_session(image_files):
 
         job = Job.objects.create(
             creator=self.request.user,
-            algorithm_image=self.algorithm.active_image,
+            algorithm_image=form.cleaned_data["algorithm_image"],
             extra_logs_viewer_groups=[self.algorithm.editors_group],
             input_civ_set=component_interface_values,
             time_limit=self.algorithm.time_limit,
diff --git a/app/grandchallenge/datatables/views.py b/app/grandchallenge/datatables/views.py
--- a/app/grandchallenge/datatables/views.py
+++ b/app/grandchallenge/datatables/views.py
@@ -84,7 +84,7 @@ def filter_queryset(self, queryset, search, order_by):
                 Q(),
             )
             queryset = queryset.filter(q)
-        return queryset.order_by(order_by)
+        return queryset.order_by(order_by).distinct()
 
 
 @dataclass


Test patch:
diff --git a/app/tests/algorithms_tests/test_forms.py b/app/tests/algorithms_tests/test_forms.py
--- a/app/tests/algorithms_tests/test_forms.py
+++ b/app/tests/algorithms_tests/test_forms.py
@@ -308,7 +308,9 @@ def test_create_job_json_input_field_validation(
         user=creator,
     )
     assert response.context["form"].errors == {
-        slug: ["This field is required."]
+        slug: ["This field is required."],
+        "algorithm_image": ["This field is required."],
+        "__all__": ["This algorithm is not ready to be used"],
     }
 
 
@@ -433,12 +435,19 @@ def test_form_invalid_without_enough_credits(self):
 
         assert not form.is_valid()
         assert form.errors == {
-            "__all__": ["You have run out of algorithm credits"]
+            "__all__": ["You have run out of algorithm credits"],
+            "algorithm_image": ["This field is required."],
         }
 
     def test_form_valid_for_editor(self):
         algorithm = AlgorithmFactory(credits_per_job=100)
         algorithm.inputs.clear()
+        algorithm_image = AlgorithmImageFactory(
+            algorithm=algorithm,
+            is_manifest_valid=True,
+            is_in_registry=True,
+            is_desired_version=True,
+        )
         user = UserFactory()
 
         user.user_credit.credits = 0
@@ -446,16 +455,30 @@ def test_form_valid_for_editor(self):
 
         algorithm.add_editor(user=user)
 
-        form = JobCreateForm(algorithm=algorithm, user=user, data={})
+        form = JobCreateForm(
+            algorithm=algorithm,
+            user=user,
+            data={"algorithm_image": str(algorithm_image.pk)},
+        )
 
         assert form.is_valid()
 
     def test_form_valid_with_credits(self):
         algorithm = AlgorithmFactory(credits_per_job=1)
         algorithm.inputs.clear()
+        algorithm_image = AlgorithmImageFactory(
+            algorithm=algorithm,
+            is_manifest_valid=True,
+            is_in_registry=True,
+            is_desired_version=True,
+        )
         user = UserFactory()
 
-        form = JobCreateForm(algorithm=algorithm, user=user, data={})
+        form = JobCreateForm(
+            algorithm=algorithm,
+            user=user,
+            data={"algorithm_image": str(algorithm_image.pk)},
+        )
 
         assert form.is_valid()