From d3d420d028437b271af8279a3a0eeb61bb547262 Mon Sep 17 00:00:00 2001 From: Chris van Run Date: Mon, 13 Jan 2025 09:49:44 +0100 Subject: [PATCH] Apply ordering of CIVs to API serializations (#3768) Ensures the ordering of CIVs on the rendered views is now also applied to the API representations. This way, the order is the same when viewing them within a view and CIRRUS. --- app/grandchallenge/components/serializers.py | 13 +++++ .../components/templatetags/civ.py | 4 +- .../components_tests/test_serializers.py | 58 ++++++++++++++++++- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/app/grandchallenge/components/serializers.py b/app/grandchallenge/components/serializers.py index 730c546e87..4ecb5f6582 100644 --- a/app/grandchallenge/components/serializers.py +++ b/app/grandchallenge/components/serializers.py @@ -14,6 +14,7 @@ ComponentInterface, ComponentInterfaceValue, ) +from grandchallenge.components.templatetags.civ import sort_civs from grandchallenge.core.guardian import filter_by_permission from grandchallenge.uploads.models import UserUpload from grandchallenge.workstation_configs.serializers import ( @@ -162,6 +163,17 @@ def validate(self, attrs): return attrs +class SortedCIVSerializer(serializers.ListSerializer): + def to_representation(self, data): + iterable = ( + data.all() + if isinstance(data, serializers.models.manager.BaseManager) + else data + ) + sorted_data = sort_civs(iterable) + return super().to_representation(sorted_data) + + class ComponentInterfaceValueSerializer(serializers.ModelSerializer): # Serializes images in place rather than with hyperlinks for internal usage image = SimpleImageSerializer(required=False) @@ -170,6 +182,7 @@ class ComponentInterfaceValueSerializer(serializers.ModelSerializer): class Meta: model = ComponentInterfaceValue fields = ["interface", "value", "file", "image", "pk"] + list_serializer_class = SortedCIVSerializer class HyperlinkedComponentInterfaceValueSerializer( diff --git a/app/grandchallenge/components/templatetags/civ.py b/app/grandchallenge/components/templatetags/civ.py index faa6cdebb3..0e09883389 100644 --- a/app/grandchallenge/components/templatetags/civ.py +++ b/app/grandchallenge/components/templatetags/civ.py @@ -25,12 +25,12 @@ def sort_civs(civs: Iterable[ComponentInterfaceValue]): charts.append(v) else: values.append(v) - elif v.file: + elif v.interface.is_file_kind: if v.interface.is_thumbnail_kind: thumbnails.append(v) else: files.append(v) - elif v.image: + elif v.interface.is_image_kind: images.append(v) else: residual.append(v) diff --git a/app/tests/components_tests/test_serializers.py b/app/tests/components_tests/test_serializers.py index e7023a279f..0be43c0cc1 100644 --- a/app/tests/components_tests/test_serializers.py +++ b/app/tests/components_tests/test_serializers.py @@ -4,8 +4,12 @@ from grandchallenge.components.models import InterfaceKind from grandchallenge.components.serializers import ( ComponentInterfaceValuePostSerializer, + ComponentInterfaceValueSerializer, +) +from tests.components_tests.factories import ( + ComponentInterfaceFactory, + ComponentInterfaceValueFactory, ) -from tests.components_tests.factories import ComponentInterfaceFactory from tests.factories import ImageFactory, UploadSessionFactory, UserFactory TEST_DATA = { @@ -468,3 +472,55 @@ def test_civ_post_image_valid(kind, rf): # verify assert serializer.is_valid() + + +@pytest.mark.django_db +def test_civ_serializer_list_ordering(): + + civs = [ + ComponentInterfaceValueFactory( + interface=ComponentInterfaceFactory( + kind=InterfaceKind.InterfaceKindChoices.IMAGE, + store_in_database=False, + ) + ), + ComponentInterfaceValueFactory( + interface=ComponentInterfaceFactory( + kind=InterfaceKind.InterfaceKindChoices.THUMBNAIL_PNG, + store_in_database=False, + ) + ), + ComponentInterfaceValueFactory( + interface=ComponentInterfaceFactory( + kind=InterfaceKind.InterfaceKindChoices.ZIP, + store_in_database=False, + ) + ), + ComponentInterfaceValueFactory( + interface=ComponentInterfaceFactory( + kind=InterfaceKind.InterfaceKindChoices.STRING + ), + value="bar", + ), + ComponentInterfaceValueFactory( + interface=ComponentInterfaceFactory( + kind=InterfaceKind.InterfaceKindChoices.CHART + ), + value="foo", + ), + ] + + serializer = ComponentInterfaceValueSerializer(many=True) + + produced_order = [ + civ["interface"]["kind"] + for civ in serializer.to_representation(data=civs) + ] + + assert produced_order == [ + InterfaceKind.InterfaceKindChoices.STRING.label, + InterfaceKind.InterfaceKindChoices.THUMBNAIL_PNG.label, + InterfaceKind.InterfaceKindChoices.CHART.label, + InterfaceKind.InterfaceKindChoices.ZIP.label, + InterfaceKind.InterfaceKindChoices.IMAGE.label, + ]