diff --git a/geonode/assets/serializers.py b/geonode/assets/serializers.py index 5fb7fd9149e..ac84187614d 100644 --- a/geonode/assets/serializers.py +++ b/geonode/assets/serializers.py @@ -27,6 +27,8 @@ Asset, LocalAsset, ) +from rest_framework import serializers +from geonode.assets.utils import is_asset_deletable logger = logging.getLogger(__name__) @@ -64,12 +66,16 @@ class AssetSerializer(DynamicModelSerializer): owner = SimpleUserSerializer(embed=False) asset_type = ClassTypeField() subinfo = AssetSubclassField() + deletable = serializers.SerializerMethodField() class Meta: model = Asset name = "asset" # fields = ("pk", "title", "description", "type", "owner", "created") - fields = ("pk", "title", "description", "type", "owner", "created", "asset_type", "subinfo") + fields = ("pk", "title", "description", "type", "owner", "created", "asset_type", "subinfo", "deletable") + + def get_deletable(self, obj): + return is_asset_deletable(obj) class LocalAssetSerializer(AssetSerializer): diff --git a/geonode/assets/utils.py b/geonode/assets/utils.py index 2d350770b33..b8432e50095 100644 --- a/geonode/assets/utils.py +++ b/geonode/assets/utils.py @@ -201,6 +201,17 @@ def rollback_asset_and_link(asset, link): logger.error(f"Could not rollback asset[{asset}] and link[{link}]", exc_info=e) +def is_asset_deletable(asset: Asset): + """ + Checks if an asset can be deleted. + currently Assets with titles "Original" or "Files" are protected and cannot be deleted. + Further logics can be added here. + """ + if asset.title in ["Original", "Files"]: + return False + return True + + def unlink_asset(resource: ResourceBase, asset: Asset, remove_asset: bool = True): """ Unlinks an asset from a resource. By default, the asset is deleted. @@ -214,8 +225,7 @@ def unlink_asset(resource: ResourceBase, asset: Asset, remove_asset: bool = True * If the asset is only linked to the provided resource, the asset itself is deleted, which in turn triggers the deletion of the associated files. """ - if asset.title in ["Original", "Files"]: - logger.info(f"Asset '{asset.title}' is protected and will not be unlinked or deleted.") + if not is_asset_deletable(asset): return False, "Asset is protected and will not be unlinked or deleted." link = Link.objects.filter(resource=resource, asset=asset).first() diff --git a/geonode/base/api/serializers.py b/geonode/base/api/serializers.py index 1e5b5964ed3..1fe5db49ecd 100644 --- a/geonode/base/api/serializers.py +++ b/geonode/base/api/serializers.py @@ -28,7 +28,7 @@ from django.forms.models import model_to_dict from django.contrib.auth import get_user_model from django.db.models.query import QuerySet -from geonode.assets.utils import get_default_asset +from geonode.assets.utils import get_default_asset, is_asset_deletable from geonode.people import Roles from django.http import QueryDict from deprecated import deprecated @@ -580,8 +580,10 @@ def to_representation(self, instance): formatted_link = model_to_dict(lnk, fields=link_fields) ret.append(formatted_link) if lnk.asset: + deletable = is_asset_deletable(lnk.asset) extras = { "type": "asset", + "deletable": deletable, "content": model_to_dict(lnk.asset, ["title", "description", "type", "created"]), } if request and permissions_registry.user_has_perm( diff --git a/geonode/base/tests.py b/geonode/base/tests.py index 1d988445706..3216b65d10b 100644 --- a/geonode/base/tests.py +++ b/geonode/base/tests.py @@ -65,7 +65,10 @@ Thesaurus, ThesaurusKeyword, generate_thesaurus_reference, + Link, ) +from geonode.assets.tests import ONE_JSON +from geonode.assets.utils import create_asset from geonode.base.middleware import ReadOnlyMiddleware, MaintenanceMiddleware from geonode.base.templatetags.base_tags import get_visibile_resources, facets from geonode.base.templatetags.thesaurus import ( @@ -80,6 +83,7 @@ from geonode import geoserver from geonode.decorators import on_ogc_backend from geonode.resource.manager import resource_manager +from geonode.base.api.serializers import ResourceBaseSerializer test_image = Image.new("RGBA", size=(50, 50), color=(155, 0, 0)) @@ -1324,3 +1328,37 @@ def test_fix_otherrestrictions_codetype(self): fixed_obj = RestrictionCodeType.objects.get(identifier="otherRestrictions") self.assertEqual(fixed_obj.description, "limitation not listed") self.assertEqual(fixed_obj.gn_description, "limitation not listed") + + +class TestDeletableAssetKey(GeoNodeBaseTestSupport): + def setUp(self): + super().setUp() + self.user = get_user_model().objects.get(username="admin") + self.resource = create_single_dataset("test_resource") + self.asset1 = create_asset(self.user, asset_type="document", title="Test Asset for Deletion", files=[ONE_JSON]) + self.asset2 = create_asset(self.user, asset_type="document", title="Original", files=[ONE_JSON]) + self.link = Link.objects.create(resource=self.resource, asset=self.asset1, name="test_link") + self.link2 = Link.objects.create(resource=self.resource, asset=self.asset2, name="test_link_2") + + def test_deletable_extra_property(self): + serializer = ResourceBaseSerializer(instance=self.resource) + data = serializer.data + links = data.get("links", []) # get value from LinksSerializer + + # Create a mapping from asset title to the link's deletable status + deletable_status_by_title = { + link.get("extras", {}).get("content", {}).get("title"): link.get("extras", {}).get("deletable") + for link in links + if link.get("extras", {}).get("content", {}).get("title") + } + # Assertions for specific asset titles + self.assertIn("Test Asset for Deletion", deletable_status_by_title) + self.assertTrue( + deletable_status_by_title["Test Asset for Deletion"], + "Link with title 'Test Asset for Deletion' should have deletable=True", + ) + self.assertIn("Original", deletable_status_by_title) + self.assertFalse( + deletable_status_by_title["Original"], + "Link with title 'Original' should have deletable=False", + )