From 95c9482910d05252df93650e10cdd53c234c35b4 Mon Sep 17 00:00:00 2001 From: Valentin Rigal <rigal@teklia.com> Date: Tue, 27 Aug 2024 08:27:33 +0000 Subject: [PATCH] Allow updating element image or polygon to null --- arkindex/documents/serializers/elements.py | 57 +++++++++---------- .../documents/tests/test_patch_elements.py | 35 ++++++++++++ arkindex/documents/tests/test_put_elements.py | 19 +++++++ 3 files changed, 80 insertions(+), 31 deletions(-) diff --git a/arkindex/documents/serializers/elements.py b/arkindex/documents/serializers/elements.py index 1d599b5da7..7e08129b4b 100644 --- a/arkindex/documents/serializers/elements.py +++ b/arkindex/documents/serializers/elements.py @@ -549,6 +549,7 @@ class ElementSerializer(ElementTinySerializer): queryset=Image.objects.all().select_related("server"), required=False, write_only=True, + allow_null=True, help_text="Link this element to an image by UUID via a polygon. " "When the image is updated, if there was an image before and the polygon is not updated, " "the previous polygon is reused. Otherwise, a polygon filling the new image is used.", @@ -557,6 +558,7 @@ class ElementSerializer(ElementTinySerializer): polygon = LinearRingField( required=False, write_only=True, + allow_null=True, help_text="Set the polygon linking this element to the image. " "`image` must be set when this field is set and there was no image or polygon defined before.", ) @@ -627,39 +629,32 @@ class ElementSerializer(ElementTinySerializer): return element.thumbnail.s3_put_url def update(self, instance, validated_data): - image = validated_data.pop("image", None) - polygon = validated_data.pop("polygon", None) - if polygon or image: - if not image: - if not instance.image_id: - # A polygon was set but there is no image - raise ValidationError({ - "image": ["Image is required when defining a polygon on an element without an existing zone"], - }) - image = instance.image - - if image.width == 0 or image.height == 0: - raise ValidationError({"image": ["This image does not have valid dimensions."]}) - - if not polygon: - if instance.polygon: - polygon = instance.polygon - else: - polygon = LinearRing( - (0, 0), - (0, image.height), - (image.width, image.height), - (image.width, 0), - (0, 0) - ) - if polygon and image: - if polygon_outside_image(image, polygon): - raise ValidationError({ - "polygon": ["An element's polygon must not exceed its image's bounds."] - }) + image = validated_data.pop("image", instance.image) + polygon = validated_data.pop("polygon", instance.polygon) + if polygon and not image: + raise ValidationError({ + "image": ["Image is required when defining a polygon on an element without an existing zone"], + }) + + if image and (image.width == 0 or image.height == 0): + raise ValidationError({"image": ["This image does not have valid dimensions."]}) + + if image and polygon and polygon_outside_image(image, polygon): + raise ValidationError({ + "polygon": ["An element's polygon must not exceed its image's bounds."] + }) - validated_data.update(image=image, polygon=polygon) + if image and not polygon: + # Automatically set a polygon corresponding to the full image + polygon = LinearRing( + (0, 0), + (0, image.height), + (image.width, image.height), + (image.width, 0), + (0, 0) + ) + validated_data.update(image=image, polygon=polygon) return super().update(instance, validated_data) diff --git a/arkindex/documents/tests/test_patch_elements.py b/arkindex/documents/tests/test_patch_elements.py index ce5d1f008b..18354726d1 100644 --- a/arkindex/documents/tests/test_patch_elements.py +++ b/arkindex/documents/tests/test_patch_elements.py @@ -267,3 +267,38 @@ class TestPatchElements(FixtureAPITestCase): self.assertDictEqual(response.json(), { "polygon": ["An element's polygon must not exceed its image's bounds."] }) + + def test_patch_element_null_polygon_null_image(self): + self.assertIsNotNone(self.element.image) + self.assertIsNotNone(self.element.polygon) + self.client.force_login(self.user) + response = self.client.patch( + reverse("api:element-retrieve", kwargs={"pk": str(self.element.id)}), + data={ + "polygon": None, + "image": None, + }, + format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.element.refresh_from_db() + self.assertIsNone(self.element.image) + self.assertIsNone(self.element.polygon) + + def test_patch_element_image_only(self): + self.element.image = None + self.element.polygon = None + self.element.save() + self.client.force_login(self.user) + response = self.client.patch( + reverse("api:element-retrieve", kwargs={"pk": str(self.element.id)}), + data={"image": str(self.image.id)}, + format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.element.refresh_from_db() + self.assertEqual(self.element.image_id, self.image.id) + self.assertTupleEqual( + self.element.polygon.coords, + ((0, 0), (0, 42), (42, 42), (42, 0), (0, 0)), + ) diff --git a/arkindex/documents/tests/test_put_elements.py b/arkindex/documents/tests/test_put_elements.py index 22cb9fce4c..babf90c779 100644 --- a/arkindex/documents/tests/test_put_elements.py +++ b/arkindex/documents/tests/test_put_elements.py @@ -278,3 +278,22 @@ class TestPutElements(FixtureAPITestCase): self.assertDictEqual(response.json(), { "polygon": ["An element's polygon must not exceed its image's bounds."] }) + + def test_put_element_null_polygon_null_image(self): + self.assertIsNotNone(self.element.image) + self.assertIsNotNone(self.element.polygon) + self.client.force_login(self.user) + response = self.client.put( + reverse("api:element-retrieve", kwargs={"pk": str(self.element.id)}), + data={ + "name": self.element.name, + "type": self.element.type.slug, + "polygon": None, + "image": None, + }, + format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.element.refresh_from_db() + self.assertIsNone(self.element.polygon) + self.assertIsNone(self.element.image) -- GitLab