Repository: comic/grand-challenge.org

Issue:
Unwanted item added in archives
**Describe the bug**
Archives items always adds a 'corrField Configuration'

**To Reproduce**
Steps to reproduce the behavior:
1. Create an Archive
2. Click on `Add Cases`, add an image
3. Click on `View Items`
4. Add a regular overlay (e.g. Heatmap)
5. Go back to `View Items`
**Expected behavior**
Only the Heatmap is added and visible under 'View Items'

**Screenshots**
![corrfield_capture](https://user-images.githubusercontent.com/7871310/135039131-f7085205-5828-4212-bfb8-7c8cb5490a5c.PNG)





Discussion:
This can be replicated in development by adding a default value to an interface. Not a show stopper for running algorithms as these are filtered out. I also think that something is up with the display of the Archive Items list.

Resolving patch:
diff --git a/app/grandchallenge/archives/forms.py b/app/grandchallenge/archives/forms.py
--- a/app/grandchallenge/archives/forms.py
+++ b/app/grandchallenge/archives/forms.py
@@ -189,7 +189,7 @@ def save(self, *args, **kwargs):
 
 class ArchiveItemForm(SaveFormInitMixin, Form):
     def __init__(
-        self, *args, archive=None, user=None, archive_item=None, **kwargs
+        self, *args, user=None, archive_item=None, interface=None, **kwargs,
     ):
         super().__init__(*args, **kwargs)
 
@@ -200,15 +200,18 @@ def __init__(
         else:
             values = ComponentInterfaceValue.objects.none()
 
-        for inp in ComponentInterface.objects.all():
-            initial = values.filter(interface=inp).first()
-            if initial:
-                initial = initial.value
-            self.fields[inp.slug] = InterfaceFormField(
-                kind=inp.kind,
-                schema=inp.schema,
-                initial=initial or inp.default_value,
-                required=False,
-                user=user,
-                help_text=clean(inp.description) if inp.description else "",
-            ).field
+        initial = values.filter(interface=interface).first()
+
+        if initial:
+            initial = initial.value
+
+        self.fields[interface.slug] = InterfaceFormField(
+            kind=interface.kind,
+            schema=interface.schema,
+            initial=initial or interface.default_value,
+            required=False,
+            user=user,
+            help_text=clean(interface.description)
+            if interface.description
+            else "",
+        ).field
diff --git a/app/grandchallenge/archives/urls.py b/app/grandchallenge/archives/urls.py
--- a/app/grandchallenge/archives/urls.py
+++ b/app/grandchallenge/archives/urls.py
@@ -63,7 +63,7 @@
         name="cases-create",
     ),
     path(
-        "<slug>/items/<id>/edit",
+        "<slug:archive_slug>/items/<uuid:pk>/edit/<slug:interface_slug>/",
         ArchiveEditArchiveItem.as_view(),
         name="item-edit",
     ),
diff --git a/app/grandchallenge/archives/views.py b/app/grandchallenge/archives/views.py
--- a/app/grandchallenge/archives/views.py
+++ b/app/grandchallenge/archives/views.py
@@ -377,16 +377,22 @@ def get_permission_object(self):
 
     @cached_property
     def archive(self):
-        return get_object_or_404(Archive, slug=self.kwargs["slug"])
+        return get_object_or_404(Archive, slug=self.kwargs["archive_slug"])
 
     @cached_property
     def archive_item(self):
-        return get_object_or_404(ArchiveItem, pk=self.kwargs["id"])
+        return get_object_or_404(ArchiveItem, pk=self.kwargs["pk"])
+
+    @cached_property
+    def interface(self):
+        return get_object_or_404(
+            ComponentInterface, slug=self.kwargs["interface_slug"]
+        )
 
     def get_form_kwargs(self):
         kwargs = super().get_form_kwargs()
         kwargs.update(
-            {"archive": self.archive, "archive_item": self.archive_item}
+            {"archive_item": self.archive_item, "interface": self.interface}
         )
         return kwargs
 
@@ -465,9 +471,7 @@ def create_upload(image_files):
         on_commit(tasks.apply_async)
 
         return HttpResponseRedirect(
-            reverse(
-                "archives:items-list", kwargs={"slug": self.kwargs["slug"]},
-            )
+            reverse("archives:items-list", kwargs={"slug": self.archive.slug},)
         )
 
 
@@ -490,7 +494,7 @@ class ArchiveItemsList(
     ]
     columns = [
         Column(title="Values", sort_field="created"),
-        Column(title="Edit", sort_field="pk"),
+        Column(title="Add", sort_field="pk"),
     ]
 
     @cached_property
@@ -502,9 +506,22 @@ def get_permission_object(self):
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
-        context.update({"archive": self.archive})
+        context.update(
+            {
+                "archive": self.archive,
+                "interfaces": ComponentInterface.objects.all().order_by(
+                    "title"
+                ),
+            }
+        )
         return context
 
+    def render_row(self, *, object_, page_context):
+        page_context["object_interfaces"] = [
+            v.interface for v in object_.values.all()
+        ]
+        return super().render_row(object_=object_, page_context=page_context)
+
     def get_queryset(self):
         qs = super().get_queryset()
         return qs.filter(archive=self.archive).prefetch_related("values")


Test patch:
diff --git a/app/tests/archives_tests/test_forms.py b/app/tests/archives_tests/test_forms.py
--- a/app/tests/archives_tests/test_forms.py
+++ b/app/tests/archives_tests/test_forms.py
@@ -9,7 +9,6 @@
     ArchivePermissionRequest,
 )
 from grandchallenge.components.models import (
-    ComponentInterface,
     ComponentInterfaceValue,
     InterfaceKind,
 )
@@ -221,14 +220,17 @@ def test_archive_item_form(client, settings):
         viewname="archives:item-edit",
         client=client,
         method=client.get,
-        reverse_kwargs={"slug": archive.slug, "id": ai.pk},
+        reverse_kwargs={
+            "archive_slug": archive.slug,
+            "pk": ai.pk,
+            "interface_slug": ci.slug,
+        },
         follow=True,
         user=editor,
     )
     assert response.status_code == 200
 
-    for _ci in ComponentInterface.objects.all():
-        assert _ci.slug in response.rendered_content
+    assert ci.slug in response.rendered_content
 
     assert f'id="id_{ci.slug}" checked' in response.rendered_content
 
@@ -250,7 +252,11 @@ def test_archive_item_form(client, settings):
                 viewname="archives:item-edit",
                 client=client,
                 method=client.post,
-                reverse_kwargs={"slug": archive.slug, "id": ai.pk},
+                reverse_kwargs={
+                    "archive_slug": archive.slug,
+                    "pk": ai.pk,
+                    "interface_slug": ci.slug,
+                },
                 data={ci.slug: False},
                 follow=True,
                 user=editor,
@@ -270,7 +276,11 @@ def test_archive_item_form(client, settings):
                 viewname="archives:item-edit",
                 client=client,
                 method=client.post,
-                reverse_kwargs={"slug": archive.slug, "id": ai.pk},
+                reverse_kwargs={
+                    "archive_slug": archive.slug,
+                    "pk": ai.pk,
+                    "interface_slug": ci.slug,
+                },
                 data={ci.slug: True},
                 follow=True,
                 user=editor,