diff --git a/geonode/base/api/tests.py b/geonode/base/api/tests.py index f6c452e11cb..0f9597d1e08 100644 --- a/geonode/base/api/tests.py +++ b/geonode/base/api/tests.py @@ -1998,6 +1998,56 @@ def test_set_resource_thumbnail(self): ) self.assertEqual(response.status_code, 200) + @patch("geonode.base.api.views.remove_thumb") + def test_delete_thumbnail(self, mock_remove_thumb): + + resource = Dataset.objects.first() + + # Set a thumbnail url and path + resource.thumbnail_url = "http://example.com/thumb/test.png" + resource.thumbnail_path = "thumb/test.png" + resource.save() + + url = reverse("base-resources-delete-thumbnail", kwargs={"resource_id": resource.id}) + + # Anonymous user + response = self.client.post(url) + self.assertEqual(response.status_code, 403) + + # Authenticated user (admin) + self.assertTrue(self.client.login(username="admin", password="admin")) + + # Valid thumbnail removal + response = self.client.post(url) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["message"], "Thumbnail deleted successfully.") + resource.refresh_from_db() + self.assertIsNone(resource.thumbnail_url) + self.assertIsNone(resource.thumbnail_path) + mock_remove_thumb.assert_called_once_with("test.png") + + # Thumbnail already deleted + response = self.client.post(url) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["message"], "The thumbnail URL field is already empty.") + self.assertFalse(response.data["success"]) + + # Invalid image format + resource.thumbnail_url = "http://example.com/media/thumb/test.txt" + resource.thumbnail_path = "thumb/test.txt" + resource.save() + + response = self.client.post(url) + self.assertEqual(response.status_code, 400) + self.assertIn("not a valid image", response.data) + + # Resource does not exist + invalid_url = reverse("base-resources-delete-thumbnail", kwargs={"resource_id": 9999}) + response = self.client.post(invalid_url) + self.assertEqual(response.status_code, 404) + self.assertEqual(response.data["message"], "Resource not found.") + self.assertFalse(response.data["success"]) + def test_set_thumbnail_from_bbox_from_Anonymous_user_raise_permission_error(self): """ Given a request with Anonymous user, should raise an authentication error. diff --git a/geonode/base/api/views.py b/geonode/base/api/views.py index a7ad7df6ddb..12fed6d4f38 100644 --- a/geonode/base/api/views.py +++ b/geonode/base/api/views.py @@ -57,7 +57,7 @@ from geonode.base.models import Configuration, ExtraMetadata, LinkedResource from geonode.thumbs.exceptions import ThumbnailError from geonode.thumbs.thumbnails import create_thumbnail -from geonode.thumbs.utils import _decode_base64, BASE64_PATTERN +from geonode.thumbs.utils import _decode_base64, BASE64_PATTERN, remove_thumb from geonode.groups.conf import settings as groups_settings from geonode.base.models import HierarchicalKeyword, Region, ResourceBase, TopicCategory, ThesaurusKeyword from geonode.base.api.filters import ( @@ -721,6 +721,57 @@ def set_thumbnail_from_bbox(self, request, resource_id, *args, **kwargs): logger.error(e) return Response(data={"message": e.args[0], "success": False}, status=500, exception=True) + @extend_schema( + methods=["post"], + responses={200}, + description="API endpoint allowing to delete a thumbnail for an existing dataset.", + ) + @action( + detail=False, + url_path="(?P\d+)/delete_thumbnail", # noqa + url_name="delete-thumbnail", + methods=["post"], + permission_classes=[IsAuthenticated, UserHasPerms(perms_dict={"default": {"POST": ["base.add_resourcebase"]}})], + ) + def delete_thumbnail(self, request, resource_id, *args, **kwargs): + + try: + resource = ResourceBase.objects.get(id=int(resource_id)) + + # Check if thumbnail exists + if not resource.thumbnail_url: + return Response( + {"message": "The thumbnail URL field is already empty.", "success": False}, + status=status.HTTP_200_OK, + ) + + thumb_parsed_url = urlparse(resource.thumbnail_url) + thumb_filename = thumb_parsed_url.path.split("/")[-1] + if thumb_filename.rsplit(".")[-1] not in ["png", "jpeg", "jpg"]: + return Response( + "The file name is not a valid image with a format png, jpeg or jpg", + status=status.HTTP_400_BAD_REQUEST, + ) + + # remove_thumb will call the thumb_path function and then the storage_manager which will delete it + remove_thumb(thumb_filename) + + # Clear the related fields in the database + resource.thumbnail_url = None + resource.thumbnail_path = None + resource.save(update_fields=["thumbnail_url", "thumbnail_path"]) + + return Response({"message": "Thumbnail deleted successfully.", "success": True}, status=status.HTTP_200_OK) + + except ResourceBase.DoesNotExist: + return Response({"message": "Resource not found.", "success": False}, status=status.HTTP_404_NOT_FOUND) + except Exception as e: + logger.error(e) + return Response( + {"message": "Unexpected error occurred.", "success": False}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + @extend_schema( methods=["post"], responses={200}, description="Instructs the Async dispatcher to execute a 'INGEST' operation." )