internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: comic/grand-challenge.org

Issue:
Overlay Segments can be `None` in Question Update view, causes transaction error
https://sentry.io/organizations/grand-challenge/issues/3453755278/


Discussion:
Also here: https://grand-challenge.sentry.io/issues/3810511568/?project=303639&query=is%3Aunresolved&referrer=issue-stream

Deep in the cleaning methods, this is returning `None`:
`form.fields['overlay_segments'].to_python([])`

Resolving patch:
diff --git a/app/grandchallenge/reader_studies/forms.py b/app/grandchallenge/reader_studies/forms.py
--- a/app/grandchallenge/reader_studies/forms.py
+++ b/app/grandchallenge/reader_studies/forms.py
@@ -271,6 +271,10 @@ class QuestionForm(SaveFormInitMixin, DynamicFormMixin, ModelForm):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
+        for field_name in self.instance.read_only_fields:
+            self.fields[field_name].required = False
+            self.fields[field_name].disabled = True
+
         self.helper = FormHelper()
         self.helper.form_tag = True
         self.helper.layout = Layout(
@@ -329,19 +333,6 @@ def clean(self):
             )
         return super().clean()
 
-    def full_clean(self):
-        """Override of the form's full_clean method.
-
-        Some fields are made readonly once the question has been answered.
-        Because disabled fields do not get included in the post data, this
-        causes issues with required fields. Therefore we populate them here.
-        """
-        data = self.data.copy()
-        for field in self.instance.read_only_fields:
-            data[field] = getattr(self.instance, field)
-        self.data = data
-        return super().full_clean()
-
     class Meta:
         model = Question
         fields = (
diff --git a/app/grandchallenge/reader_studies/views.py b/app/grandchallenge/reader_studies/views.py
--- a/app/grandchallenge/reader_studies/views.py
+++ b/app/grandchallenge/reader_studies/views.py
@@ -494,7 +494,7 @@ class QuestionOptionMixin:
     def validate_options(self, form, _super):
         context = self.get_context_data()
         options = context["options"]
-        if form.data["answer_type"] not in [
+        if form.cleaned_data["answer_type"] not in [
             Question.AnswerType.CHOICE,
             Question.AnswerType.MULTIPLE_CHOICE,
             Question.AnswerType.MULTIPLE_CHOICE_DROPDOWN,
@@ -547,10 +547,6 @@ def reader_study(self):
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
-        form_fields = context["form"].fields
-        for field_name in self.object.read_only_fields:
-            form_fields[field_name].required = False
-            form_fields[field_name].disabled = True
         if self.request.POST:
             context["options"] = CategoricalOptionFormSet(
                 self.request.POST, instance=self.object


Test patch:
Repository: comic/grand-challenge.org

Issue:
The help text for View content in Algorithm settings only list input interfaces.
**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to an algorithm with inputs and outputs defined
2. Click on Update settings
3. Scroll down to View content


**Expected behavior**
A clear and concise description of what you expected to happen.

The help text of View content will only list the interfaces defined as inputs, it should list the output interfaces as well.

**Screenshots**
If applicable, add screenshots to help explain your problem.

![image](https://user-images.githubusercontent.com/51329283/200324945-bf5de935-cff8-4ab7-be16-eb5f3eba7b93.png)



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
@@ -344,9 +344,14 @@ def __init__(self, *args, **kwargs):
         self.fields["contact_email"].required = True
         self.fields["display_editors"].required = True
         if self.instance:
+            interface_slugs = (
+                (self.instance.inputs.all() | self.instance.outputs.all())
+                .distinct()
+                .values_list("slug", flat=True)
+            )
             self.fields["view_content"].help_text += (
                 " The following interfaces are used in your algorithm: "
-                f"{', '.join(self.instance.inputs.values_list('slug', flat=True).distinct())}."
+                f"{oxford_comma(interface_slugs)}."
             )
 
 


Test patch:
Repository: comic/grand-challenge.org

Issue:
Documentation links are green
![c384765a-c50d-4f2c-8670-535a0225cbf0](https://github.com/comic/grand-challenge.org/assets/12661555/66e1f26a-c7bf-4083-ba8a-e703e2948b9c)



Discussion:

Resolving patch:
diff --git a/app/config/settings.py b/app/config/settings.py
--- a/app/config/settings.py
+++ b/app/config/settings.py
@@ -363,7 +363,6 @@
 # CSS Compression settings
 COMPRESS_PRECOMPILERS = (("text/x-scss", "django_libsass.SassCompiler"),)
 LIBSASS_OUTPUT_STYLE = "compressed"
-COMPRESS_ENABLED = True
 COMPRESS_OFFLINE = strtobool(os.environ.get("COMPRESS_OFFLINE", "True"))
 
 # Make this unique, and don't share it with anybody.
@@ -371,6 +370,13 @@
     "SECRET_KEY", "d=%^l=xa02an9jn-$!*hy1)5yox$a-$2(ejt-2smimh=j4%8*b"
 )
 
+default_loaders = [
+    "django.template.loaders.filesystem.Loader",
+    "django.template.loaders.app_directories.Loader",
+]
+
+cached_loaders = [("django.template.loaders.cached.Loader", default_loaders)]
+
 TEMPLATES = [
     {
         "BACKEND": "django.template.backends.django.DjangoTemplates",
@@ -402,10 +408,7 @@
                 "grandchallenge.core.context_processors.workstation_domains",
                 "machina.core.context_processors.metadata",
             ],
-            "loaders": [
-                "django.template.loaders.filesystem.Loader",
-                "django.template.loaders.app_directories.Loader",
-            ],
+            "loaders": default_loaders if DEBUG else cached_loaders,
         },
     }
 ]


Test patch:
Repository: comic/grand-challenge.org

Issue:
Remove usage of jsdelivr and include scss on admin page
Follow up from #2870 


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/charts/specs.py b/app/grandchallenge/charts/specs.py
--- a/app/grandchallenge/charts/specs.py
+++ b/app/grandchallenge/charts/specs.py
@@ -1,3 +1,6 @@
+from django.templatetags.static import static
+
+
 def bar(*, values, lookup, title):
     chart = {
         "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
@@ -135,7 +138,7 @@ def world_map(*, values):
         "padding": 0,
         "view": {"stroke": "transparent", "fill": "#c9eeff"},
         "data": {
-            "url": "https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json",
+            "url": static("vendored/world-atlas/countries-110m.json"),
             "format": {"type": "topojson", "feature": "countries"},
         },
         "transform": [
diff --git a/app/grandchallenge/core/widgets.py b/app/grandchallenge/core/widgets.py
--- a/app/grandchallenge/core/widgets.py
+++ b/app/grandchallenge/core/widgets.py
@@ -75,15 +75,14 @@ def media(self):
 
 
 class MarkdownEditorAdminWidget(AdminMarkdownxWidget):
+    template_name = "markdownx/admin_widget.html"
+
     @property
     def media(self):
         return forms.Media(
             css={
                 "all": [
                     *AdminMarkdownxWidget.Media.css["all"],
-                    "vendor/css/base.min.css",
-                    "vendored/font-awesome/css/all.min.css",
-                    "css/markdown.css",
                 ]
             },
             js=[


Test patch:
Repository: comic/grand-challenge.org

Issue:
The Phase (algorithm) input and output selects are annoying to use in the admin
A select 2 widget would be better.


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/evaluation/admin.py b/app/grandchallenge/evaluation/admin.py
--- a/app/grandchallenge/evaluation/admin.py
+++ b/app/grandchallenge/evaluation/admin.py
@@ -93,6 +93,13 @@ class PhaseAdmin(admin.ModelAdmin):
         "submission_kind",
         "challenge__short_name",
     )
+    autocomplete_fields = (
+        "inputs",
+        "outputs",
+        "algorithm_inputs",
+        "algorithm_outputs",
+        "archive",
+    )
     form = PhaseAdminForm
 
     @admin.display(boolean=True)


Test patch:
Repository: comic/grand-challenge.org

Issue:
Add anchors to section titles in Documentation
It would be nice to link to specific sections of the docs.


Discussion:
Would it be possible to create them in the rendering, and add them to header tags by default? Adding <a id=...> is cumbersome and actually doesn't work quite as nicely since you can't get it to align perfectly with the headers.
Suggestion we should use the permalink option in the markdown extension toc:
https://python-markdown.github.io/extensions/toc/

Quick test shows:
```Python
>>> markdown.markdown("### A Title", extensions=[TocExtension(permalink=True)])
'<h3 id="a-title">A Title<a class="headerlink" href="#a-title" title="Permanent link">&para;</a></h3>'
```

Yes that looks like exactly what we need.

Resolving patch:
diff --git a/app/config/settings.py b/app/config/settings.py
--- a/app/config/settings.py
+++ b/app/config/settings.py
@@ -11,6 +11,7 @@
 from django.core.exceptions import ImproperlyConfigured
 from django.urls import reverse
 from machina import MACHINA_MAIN_STATIC_DIR, MACHINA_MAIN_TEMPLATE_DIR
+from markdown.extensions.toc import TocExtension
 from sentry_sdk.integrations.celery import CeleryIntegration
 from sentry_sdk.integrations.django import DjangoIntegration
 from sentry_sdk.integrations.logging import ignore_logger
@@ -766,6 +767,9 @@
     "markdown.extensions.sane_lists",
     "markdown.extensions.codehilite",
     "markdown.extensions.attr_list",
+    TocExtension(
+        permalink=True, permalink_class="headerlink text-muted small pl-1"
+    ),
     BS4Extension(),
 ]
 MARKDOWNX_MARKDOWNIFY_FUNCTION = (


Test patch:
Repository: comic/grand-challenge.org

Issue:
Failed/Cancelled jobs for an Evaluation not obvious
It is sometimes tricky to work out why an evaluation failed. It would be useful for challenge admins to have a list of incomplete jobs for a particular evaluation on its detail page.


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/algorithms/models.py b/app/grandchallenge/algorithms/models.py
--- a/app/grandchallenge/algorithms/models.py
+++ b/app/grandchallenge/algorithms/models.py
@@ -528,7 +528,12 @@ class Meta(UUIDModel.Meta, ComponentImage.Meta):
         ordering = ("created", "creator")
 
     def __str__(self):
-        return f"Algorithm image {self.pk} for {self.algorithm} ({truncatewords(self.comment, 4)})"
+        out = f"Algorithm image {self.pk} for {self.algorithm}"
+
+        if self.comment:
+            out += f" ({truncatewords(self.comment, 4)})"
+
+        return out
 
     def get_absolute_url(self):
         return reverse(
diff --git a/app/grandchallenge/evaluation/views/__init__.py b/app/grandchallenge/evaluation/views/__init__.py
--- a/app/grandchallenge/evaluation/views/__init__.py
+++ b/app/grandchallenge/evaluation/views/__init__.py
@@ -20,7 +20,7 @@
 from guardian.mixins import LoginRequiredMixin
 
 from grandchallenge.algorithms.forms import AlgorithmForPhaseForm
-from grandchallenge.algorithms.models import Algorithm
+from grandchallenge.algorithms.models import Algorithm, Job
 from grandchallenge.components.models import InterfaceKind
 from grandchallenge.core.forms import UserFormKwargsMixin
 from grandchallenge.core.guardian import (
@@ -500,6 +500,18 @@ def get_context_data(self, **kwargs):
             ]:
                 json.append(output)
 
+        incomplete_jobs = filter_by_permission(
+            queryset=Job.objects.exclude(status=Job.SUCCESS)
+            .filter(
+                algorithm_image=self.object.submission.algorithm_image,
+                inputs__archive_items__archive=self.object.submission.phase.archive,
+            )
+            .distinct()
+            .order_by("status"),
+            user=self.request.user,
+            codename="view_job",
+        )
+
         context.update(
             {
                 "metrics": metrics,
@@ -508,6 +520,7 @@ def get_context_data(self, **kwargs):
                 "thumbnails": thumbnails,
                 "json": json,
                 "predictions": predictions,
+                "incomplete_jobs": incomplete_jobs,
             }
         )
 


Test patch:
Repository: comic/grand-challenge.org

Issue:
Cannot edit or delete challenge pages with slug `evaluation`



Discussion:

Resolving patch:
diff --git a/app/grandchallenge/pages/forms.py b/app/grandchallenge/pages/forms.py
--- a/app/grandchallenge/pages/forms.py
+++ b/app/grandchallenge/pages/forms.py
@@ -1,4 +1,5 @@
 from django import forms
+from django.core.exceptions import ValidationError
 from django.db.models import BLANK_CHOICE_DASH
 from django_summernote.widgets import SummernoteInplaceWidget
 
@@ -33,6 +34,17 @@ class Meta:
             )
         }
 
+    def clean_display_title(self):
+        display_title = self.cleaned_data["display_title"]
+
+        if display_title.lower() in {"evaluation"}:
+            # evaluation results in a URL clash, especially with the update page.
+            raise ValidationError(
+                "Title not allowed, please select an alternative"
+            )
+
+        return display_title
+
 
 class PageUpdateForm(PageCreateForm):
     """Like the page update form but you can also move the page."""


Test patch:
Repository: comic/grand-challenge.org

Issue:
Cannot Delete Display Sets
![MicrosoftTeams-image (7)](https://github.com/comic/grand-challenge.org/assets/7871310/cdee1219-af7d-4c4d-9c02-873a34e3034e)

Likely this is because of the new strict in-line JS prevention.


Discussion:
That code was moved to a JS module in #2891, I guess that method needs to be included in the global scope.

Resolving patch:
diff --git a/app/grandchallenge/reader_studies/urls.py b/app/grandchallenge/reader_studies/urls.py
--- a/app/grandchallenge/reader_studies/urls.py
+++ b/app/grandchallenge/reader_studies/urls.py
@@ -23,7 +23,6 @@
     ReaderStudyDetail,
     ReaderStudyDisplaySetList,
     ReaderStudyExampleGroundTruth,
-    ReaderStudyImagesList,
     ReaderStudyLeaderBoard,
     ReaderStudyList,
     ReaderStudyPermissionRequestCreate,
@@ -58,7 +57,6 @@
         ReaderStudyLeaderBoard.as_view(),
         name="leaderboard",
     ),
-    path("<slug>/cases/", ReaderStudyImagesList.as_view(), name="images"),
     path(
         "<slug>/display-sets/",
         ReaderStudyDisplaySetList.as_view(),
diff --git a/app/grandchallenge/reader_studies/views.py b/app/grandchallenge/reader_studies/views.py
--- a/app/grandchallenge/reader_studies/views.py
+++ b/app/grandchallenge/reader_studies/views.py
@@ -445,50 +445,6 @@ def get_queryset(self):
         return qs
 
 
-class ReaderStudyImagesList(
-    LoginRequiredMixin, ObjectPermissionRequiredMixin, PaginatedTableListView
-):
-    model = Image
-    permission_required = (
-        f"{ReaderStudy._meta.app_label}.change_{ReaderStudy._meta.model_name}"
-    )
-    raise_exception = True
-    template_name = "reader_studies/readerstudy_images_list.html"
-    row_template = "reader_studies/readerstudy_images_row.html"
-    search_fields = ["pk", "name"]
-    columns = [
-        Column(title="Name", sort_field="name"),
-        Column(title="Created", sort_field="created"),
-        Column(title="Creator", sort_field="origin__creator__username"),
-        Column(title="View"),
-        Column(title="Download"),
-        Column(title="Remove from Study"),
-    ]
-
-    @cached_property
-    def reader_study(self):
-        return get_object_or_404(ReaderStudy, slug=self.kwargs["slug"])
-
-    def get_permission_object(self):
-        return self.reader_study
-
-    def get_context_data(self, **kwargs):
-        context = super().get_context_data(**kwargs)
-        context.update({"reader_study": self.reader_study})
-        return context
-
-    def get_queryset(self):
-        qs = super().get_queryset()
-        return (
-            qs.filter(readerstudies=self.reader_study)
-            .prefetch_related("files")
-            .select_related(
-                "origin__creator__user_profile",
-                "origin__creator__verification",
-            )
-        )
-
-
 class QuestionOptionMixin:
     def validate_options(self, form, _super):
         context = self.get_context_data()


Test patch:
Repository: comic/grand-challenge.org

Issue:
Code highlighting on the forum does not work
**Describe the bug**
A clear and concise description of what the bug is.

The forum posts uses markdown, which should highlight code snippets. The markdown preview widget shows the expected format, but the resulting post does not.

There is also a challenge post complaining about python formatting:
https://grand-challenge.org/forums/forum/automated-lesion-segmentation-in-petct-domain-generalization-687/topic/shm-limitation-trigers-bus-error-in-nnunetv2-1681/


**To Reproduce**
Steps to reproduce the behavior:

Go to the forum and create a post. 

Post text:
```python

def hello():
  print("Hello world!")

```

Switching to preview shows the correct highlighting, after submitting it looks like plain text:

https://github.com/comic/grand-challenge.org/assets/51329283/879ae751-94d9-4d30-a4e4-904a9f9980a6


**Expected behavior**
A clear and concise description of what you expected to happen.

I expect preview and final result to show the same highlighting

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS: [e.g. iOS]
 - Browser [e.g. chrome, safari] Chrome, Edge
 - Version [e.g. 22]

**Smartphone (please complete the following information):**
 - Device: [e.g. iPhone6]
 - OS: [e.g. iOS8.1]
 - Browser [e.g. stock browser, safari]
 - Version [e.g. 22]

**Additional context**
Add any other context about the problem here.



Discussion:

Resolving patch:
diff --git a/app/config/settings.py b/app/config/settings.py
--- a/app/config/settings.py
+++ b/app/config/settings.py
@@ -795,6 +795,10 @@
 MACHINA_PROFILE_AVATARS_ENABLED = False
 MACHINA_FORUM_NAME = "Grand Challenge Forums"
 MACHINA_MARKUP_WIDGET = "grandchallenge.core.widgets.MarkdownEditorWidget"
+MACHINA_MARKUP_LANGUAGE = (
+    "grandchallenge.core.templatetags.bleach.md2html",
+    {"link_blank_target": True},
+)
 
 AUTH_PASSWORD_VALIDATORS = [
     {


Test patch:
Repository: comic/grand-challenge.org

Issue:
Corrupt docker images leaves imports in started state
https://grand-challenge.sentry.io/issues/4329025848/?project=303639&query=%21logger%3Acsp+is%3Aunresolved&referrer=issue-stream&statsPeriod=30d&stream_index=0


Discussion:

Resolving patch:
diff --git a/app/grandchallenge/components/tasks.py b/app/grandchallenge/components/tasks.py
--- a/app/grandchallenge/components/tasks.py
+++ b/app/grandchallenge/components/tasks.py
@@ -7,6 +7,7 @@
 import uuid
 import zlib
 from base64 import b64encode
+from lzma import LZMAError
 from pathlib import Path
 from tempfile import NamedTemporaryFile, TemporaryDirectory
 
@@ -500,7 +501,7 @@ def _extract_docker_image_file(*, instance, filename: str):
             f"{filename} not found at the root of the container image "
             f"file. Was this created with docker save?"
         )
-    except (EOFError, zlib.error):
+    except (EOFError, zlib.error, LZMAError):
         raise ValidationError("Could not decompress the container image file.")
 
 


Test patch:
Repository: comic/grand-challenge.org

Issue:
Leaderboard active on general settings
<img width="1044" alt="Screenshot 2023-09-20 at 11 13 05" src="https://github.com/comic/grand-challenge.org/assets/12661555/73e78ab8-96a4-4246-ac15-4ada0ea5db49">

All tabs should be reviewed.


Discussion:

Resolving patch:
diff --git a/app/config/urls/challenge_subdomain.py b/app/config/urls/challenge_subdomain.py
--- a/app/config/urls/challenge_subdomain.py
+++ b/app/config/urls/challenge_subdomain.py
@@ -22,7 +22,7 @@
         include("grandchallenge.participants.urls", namespace="participants"),
     ),
     path("admins/", include("grandchallenge.admins.urls", namespace="admins")),
-    path("update/", ChallengeUpdate.as_view(), name="update"),
+    path("update/", ChallengeUpdate.as_view(), name="challenge-update"),
     path("summernote/", include("django_summernote.urls")),
     path(
         "workspaces/",
diff --git a/app/grandchallenge/challenges/views.py b/app/grandchallenge/challenges/views.py
--- a/app/grandchallenge/challenges/views.py
+++ b/app/grandchallenge/challenges/views.py
@@ -115,7 +115,7 @@ class ChallengeUpdate(
 
     def get_success_url(self):
         return reverse(
-            "update",
+            "challenge-update",
             kwargs={"challenge_short_name": self.request.challenge.short_name},
         )
 
diff --git a/app/grandchallenge/evaluation/views/__init__.py b/app/grandchallenge/evaluation/views/__init__.py
--- a/app/grandchallenge/evaluation/views/__init__.py
+++ b/app/grandchallenge/evaluation/views/__init__.py
@@ -959,7 +959,7 @@ def get_object(self, queryset=None):
 
     def get_success_url(self):
         return reverse(
-            "update",
+            "challenge-update",
             kwargs={
                 "challenge_short_name": self.request.challenge.short_name,
             },


Test patch:
diff --git a/app/tests/challenges_tests/test_permissions.py b/app/tests/challenges_tests/test_permissions.py
--- a/app/tests/challenges_tests/test_permissions.py
+++ b/app/tests/challenges_tests/test_permissions.py
@@ -21,7 +21,9 @@ def test_challenge_logged_in_permissions(view, client, challenge_set):
 @pytest.mark.django_db
 def test_challenge_update_permissions(client, two_challenge_sets):
     validate_admin_only_view(
-        two_challenge_set=two_challenge_sets, viewname="update", client=client
+        two_challenge_set=two_challenge_sets,
+        viewname="challenge-update",
+        client=client,
     )
 
 
diff --git a/app/tests/common_tests/test_link_visibility.py b/app/tests/common_tests/test_link_visibility.py
--- a/app/tests/common_tests/test_link_visibility.py
+++ b/app/tests/common_tests/test_link_visibility.py
@@ -7,7 +7,7 @@
 @pytest.mark.parametrize(
     "view",
     [
-        "update",
+        "challenge-update",
         "pages:list",
         "admins:list",
         "participants:list",