From 1355a3f393aa302c4cd3d989ea5c2545915b5bdb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 10:16:57 +0000 Subject: [PATCH 1/3] Initial plan From 186616211b40f0c33835545019ee624581edadf9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 10:32:14 +0000 Subject: [PATCH 2/3] Refactor ExtraMetadata: migrate to SparseFields and remove ExtraMetadata model Co-authored-by: etj <717359+etj@users.noreply.github.com> --- geonode/api/resourcebase_api.py | 8 -- geonode/api/tests.py | 32 -------- geonode/base/api/serializers.py | 20 ----- geonode/base/api/tests.py | 72 ----------------- geonode/base/api/views.py | 81 +------------------ ...8_migrate_extrametadata_to_sparsefields.py | 52 ++++++++++++ geonode/base/models.py | 11 +-- geonode/base/utils.py | 31 ------- geonode/documents/api/tests.py | 11 --- geonode/geoapps/api/tests.py | 11 --- geonode/layers/api/tests.py | 11 --- geonode/maps/api/tests.py | 11 --- geonode/resource/manager.py | 2 - geonode/resource/utils.py | 8 -- geonode/settings.py | 34 -------- 15 files changed, 54 insertions(+), 341 deletions(-) create mode 100644 geonode/base/migrations/0098_migrate_extrametadata_to_sparsefields.py diff --git a/geonode/api/resourcebase_api.py b/geonode/api/resourcebase_api.py index cf98190ac8e..e3a94505aec 100644 --- a/geonode/api/resourcebase_api.py +++ b/geonode/api/resourcebase_api.py @@ -142,10 +142,6 @@ def build_filters(self, filters=None, ignore_bad_filters=False, **kwargs): if "app_type__in" in filters: orm_filters.update({"resource_type": filters["app_type__in"].lower()}) - _metadata = {f"metadata__{_k}": _v for _k, _v in filters.items() if _k.startswith("metadata__")} - if _metadata: - orm_filters.update({"metadata_filters": _metadata}) - if "extent" in filters: orm_filters.update({"extent": filters["extent"]}) orm_filters["f_method"] = filters["f_method"] if "f_method" in filters else "and" @@ -165,7 +161,6 @@ def apply_filters(self, request, applicable_filters): keywords = applicable_filters.pop("keywords__slug__in", None) metadata_only = applicable_filters.pop("metadata_only", False) filtering_method = applicable_filters.pop("f_method", "and") - metadata_filters = applicable_filters.pop("metadata_filters", None) if filtering_method == "or": filters = Q() for f in applicable_filters.items(): @@ -207,9 +202,6 @@ def apply_filters(self, request, applicable_filters): if keywords: filtered = self.filter_h_keywords(filtered, keywords) - if metadata_filters: - filtered = filtered.filter(**metadata_filters) - # return filtered return get_visible_resources( filtered, diff --git a/geonode/api/tests.py b/geonode/api/tests.py index 71559cbe835..9cafd084939 100644 --- a/geonode/api/tests.py +++ b/geonode/api/tests.py @@ -37,7 +37,6 @@ from geonode.layers.models import Dataset from geonode.documents.models import Document from geonode.base.models import ( - ExtraMetadata, Thesaurus, ThesaurusLabel, ThesaurusKeyword, @@ -534,37 +533,6 @@ def test_category_filters(self): self.assertValidJSONResponse(resp) self.assertEqual(len(self.deserialize(resp)["objects"]), 5) - def test_metadata_filters(self): - """Test category filtering""" - _r = Dataset.objects.first() - _m = ExtraMetadata.objects.create( - resource=_r, - metadata={ - "name": "metadata-updated", - "slug": "metadata-slug-updated", - "help_text": "this is the help text-updated", - "field_type": "str-updated", - "value": "my value-updated", - "category": "category", - }, - ) - - list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) - _r.metadata.add(_m) - # check we get the correct layers number returnered filtering on one - # and then two different categories - filter_url = f"{list_url}?metadata__category=category" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 1) - - filter_url = f"{list_url}?metadata__category=not-existing-category" - - resp = self.api_client.get(filter_url) - self.assertValidJSONResponse(resp) - self.assertEqual(len(self.deserialize(resp)["objects"]), 0) - def test_tag_filters(self): """Test keywords filtering""" list_url = reverse("api_dispatch_list", kwargs={"api_name": "api", "resource_name": "datasets"}) diff --git a/geonode/base/api/serializers.py b/geonode/base/api/serializers.py index fa1add81264..522164c70cc 100644 --- a/geonode/base/api/serializers.py +++ b/geonode/base/api/serializers.py @@ -57,7 +57,6 @@ SpatialRepresentationType, ThesaurusKeyword, ThesaurusKeywordLabel, - ExtraMetadata, LinkedResource, ) from geonode.documents.models import Document @@ -282,23 +281,6 @@ def get_attribute(self, instance): return build_absolute_uri(instance.detail_url) -class ExtraMetadataSerializer(DynamicModelSerializer): - class Meta: - model = ExtraMetadata - name = "ExtraMetadata" - fields = ("pk", "metadata") - - def to_representation(self, obj): - if isinstance(obj, QuerySet): - out = [] - for el in obj: - out.append({**{"id": el.id}, **el.metadata}) - return out - elif isinstance(obj, list): - return obj - return {**{"id": obj.id}, **obj.metadata} - - class ThumbnailUrlField(DynamicComputedField): def __init__(self, **kwargs): super().__init__(**kwargs) @@ -672,7 +654,6 @@ class ResourceBaseSerializer(MultiLangOutputMixin, DynamicModelSerializer): links = DynamicRelationField(LinksSerializer, source="id", read_only=True) # Deferred fields - metadata = ComplexDynamicRelationField(ExtraMetadataSerializer, many=True, deferred=True) data = DataBlobField(DataBlobSerializer, source="id", deferred=True, required=False) executions = DynamicRelationField( ResourceExecutionRequestSerializer, source="id", deferred=True, required=False, read_only=True @@ -754,7 +735,6 @@ class Meta: "sourcetype", "is_copyable", "blob", - "metadata", "executions", "linked_resources", "download_url", diff --git a/geonode/base/api/tests.py b/geonode/base/api/tests.py index 2e4ab46f127..aa6ab6600a2 100644 --- a/geonode/base/api/tests.py +++ b/geonode/base/api/tests.py @@ -71,7 +71,6 @@ ResourceBase, TopicCategory, ThesaurusKeyword, - ExtraMetadata, RestrictionCodeType, License, Group, @@ -3084,77 +3083,6 @@ def test_metadata_uploaded_preserve_can_be_updated(self): self.assertTrue(response.json()["resource"]["metadata_uploaded_preserve"]) -class TestExtraMetadataBaseApi(GeoNodeBaseTestSupport): - def setUp(self): - self.layer = create_single_dataset("single_layer") - self.metadata = { - "filter_header": "Foo Filter header", - "field_name": "metadata-name", - "field_label": "this is the help text", - "field_value": "foo", - } - m = ExtraMetadata.objects.create(resource=self.layer, metadata=self.metadata) - self.layer.metadata.add(m) - self.mdata = ExtraMetadata.objects.first() - - def test_get_will_return_the_list_of_extra_metadata(self): - self.client.login(username="admin", password="admin") - url = reverse("base-resources-extra-metadata", args=[self.layer.id]) - response = self.client.get(url, content_type="application/json") - self.assertTrue(200, response.status_code) - expected = [{**{"id": self.mdata.id}, **self.metadata}] - self.assertEqual(expected, response.json()) - - def test_put_will_update_the_whole_metadata(self): - self.client.login(username="admin", password="admin") - url = reverse("base-resources-extra-metadata", args=[self.layer.id]) - input_metadata = { - "id": self.mdata.id, - "filter_header": "Foo Filter header", - "field_name": "metadata-updated", - "field_label": "this is the help text", - "field_value": "foo", - } - response = self.client.put(url, data=[input_metadata], content_type="application/json") - self.assertTrue(200, response.status_code) - self.assertEqual([input_metadata], response.json()) - - def test_post_will_add_new_metadata(self): - self.client.login(username="admin", password="admin") - url = reverse("base-resources-extra-metadata", args=[self.layer.id]) - input_metadata = { - "filter_header": "Foo Filter header", - "field_name": "metadata-updated", - "field_label": "this is the help text", - "field_value": "foo", - } - response = self.client.post(url, data=[input_metadata], content_type="application/json") - self.assertTrue(201, response.status_code) - self.assertEqual(2, len(response.json())) - - def test_delete_will_delete_single_metadata(self): - self.client.login(username="admin", password="admin") - url = reverse("base-resources-extra-metadata", args=[self.layer.id]) - response = self.client.delete(url, data=[self.mdata.id], content_type="application/json") - self.assertTrue(200, response.status_code) - self.assertEqual([], response.json()) - - def test_user_without_view_perms_cannot_see_the_endpoint(self): - from geonode.resource.manager import resource_manager - - self.client.login(username="bobby", password="bob") - resource_manager.remove_permissions(self.layer.uuid, instance=self.layer.get_self_resource()) - url = reverse("base-resources-extra-metadata", args=[self.layer.id]) - response = self.client.get(url, content_type="application/json") - self.assertTrue(401, response.status_code) - - perm_spec = {"users": {"bobby": ["view_resourcebase"]}, "groups": {}} - self.layer.set_permissions(perm_spec) - url = reverse("base-resources-extra-metadata", args=[self.layer.id]) - response = self.client.get(url, content_type="application/json") - self.assertTrue(200, response.status_code) - - class TestApiLinkedResources(GeoNodeBaseTestSupport): @classmethod def setUpClass(cls) -> None: diff --git a/geonode/base/api/views.py b/geonode/base/api/views.py index cda55692ddc..0f03ae69871 100644 --- a/geonode/base/api/views.py +++ b/geonode/base/api/views.py @@ -65,7 +65,6 @@ TopicCategory, ThesaurusKeyword, Configuration, - ExtraMetadata, LinkedResource, ) from geonode.base.api.filters import ( @@ -107,12 +106,11 @@ TopicCategorySerializer, RegionSerializer, ThesaurusKeywordSerializer, - ExtraMetadataSerializer, LinkedResourceSerializer, ) from geonode.people.api.serializers import UserSerializer from .pagination import GeoNodeApiPagination -from geonode.base.utils import validate_extra_metadata, patch_perms +from geonode.base.utils import patch_perms from geonode.assets.models import Asset from geonode.assets.utils import create_asset_and_link, unlink_asset from geonode.assets.handlers import asset_handler_registry @@ -1282,83 +1280,6 @@ def set_thumbnail(self, request, pk, *args, **kwargs): return Response({"thumbnail_url": resource.thumbnail_url}) return Response("Unable to set thumbnail", status=status.HTTP_400_BAD_REQUEST) - @extend_schema( - methods=["get", "put", "delete", "post"], description="Get/Update/Delete/Add extra metadata for resource" - ) - @action( - detail=True, - methods=["get", "put", "delete", "post"], - permission_classes=[IsOwnerOrAdmin, UserHasPerms(perms_dict={"default": {"POST": ["base.add_resourcebase"]}})], - url_path=r"extra_metadata", # noqa - url_name="extra-metadata", - ) - def extra_metadata(self, request, pk, *args, **kwargs): - _obj = get_object_or_404(ResourceBase, pk=pk) - - if request.method == "GET": - # get list of available metadata - queryset = _obj.metadata.all() - _filters = [{f"metadata__{key}": value} for key, value in request.query_params.items()] - if _filters: - queryset = queryset.filter(**_filters[0]) - return Response(ExtraMetadataSerializer().to_representation(queryset)) - if not request.method == "DELETE": - try: - extra_metadata = validate_extra_metadata(request.data, _obj) - except Exception as e: - return Response(status=500, data=e.args[0]) - - if request.method == "PUT": - """ - update specific metadata. The ID of the metadata is required to perform the update - [ - { - "id": 1, - "name": "foo_name", - "slug": "foo_sug", - "help_text": "object", - "field_type": "int", - "value": "object", - "category": "object" - } - ] - """ - for _m in extra_metadata: - _id = _m.pop("id") - ResourceBase.objects.filter(id=_obj.id).first().metadata.filter(id=_id).update(metadata=_m) - logger.info("metadata updated for the selected resource") - _obj.refresh_from_db() - return Response(ExtraMetadataSerializer().to_representation(_obj.metadata.all())) - elif request.method == "DELETE": - # delete single metadata - """ - Expect a payload with the IDs of the metadata that should be deleted. Payload be like: - [4, 3] - """ - ResourceBase.objects.filter(id=_obj.id).first().metadata.filter(id__in=request.data).delete() - _obj.refresh_from_db() - return Response(ExtraMetadataSerializer().to_representation(_obj.metadata.all())) - elif request.method == "POST": - # add new metadata - """ - [ - { - "name": "foo_name", - "slug": "foo_sug", - "help_text": "object", - "field_type": "int", - "value": "object", - "category": "object" - } - ] - """ - for _m in extra_metadata: - new_m = ExtraMetadata.objects.create(resource=_obj, metadata=_m) - new_m.save() - _obj.metadata.add(new_m) - _obj.refresh_from_db() - return Response(ExtraMetadataSerializer().to_representation(_obj.metadata.all()), status=201) - @action( detail=True, methods=["get"], diff --git a/geonode/base/migrations/0098_migrate_extrametadata_to_sparsefields.py b/geonode/base/migrations/0098_migrate_extrametadata_to_sparsefields.py new file mode 100644 index 00000000000..234d4b9644c --- /dev/null +++ b/geonode/base/migrations/0098_migrate_extrametadata_to_sparsefields.py @@ -0,0 +1,52 @@ +import json +import logging + +from django.db import migrations + +logger = logging.getLogger(__name__) + + +def migrate_extrametadata_to_sparsefields(apps, schema_editor): + """ + Migrate ExtraMetadata entries to SparseField entries. + + Each ExtraMetadata object (with its JSON dict) is stored as a single + SparseField entry with name 'extra_metadata_' and value as JSON string. + Entries whose serialized JSON exceeds 1024 characters are skipped with a warning. + """ + ExtraMetadata = apps.get_model("base", "ExtraMetadata") + SparseField = apps.get_model("metadata", "SparseField") + + for extra_meta in ExtraMetadata.objects.select_related("resource").iterator(): + name = f"extra_metadata_{extra_meta.pk}" + value = json.dumps(extra_meta.metadata) + if len(value) > 1024: + logger.warning( + f"ExtraMetadata pk={extra_meta.pk} skipped during migration to SparseField: " + f"serialized value exceeds 1024 characters" + ) + continue + SparseField.objects.get_or_create( + resource=extra_meta.resource, + name=name, + defaults={"value": value}, + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ("base", "0097_alter_link_asset"), + ("metadata", "0001_initial"), + ] + + operations = [ + migrations.RunPython(migrate_extrametadata_to_sparsefields, migrations.RunPython.noop), + migrations.RemoveField( + model_name="resourcebase", + name="metadata", + ), + migrations.DeleteModel( + name="ExtraMetadata", + ), + ] diff --git a/geonode/base/models.py b/geonode/base/models.py index 6ead4558734..3d4fc801d0e 100644 --- a/geonode/base/models.py +++ b/geonode/base/models.py @@ -679,9 +679,6 @@ class ResourceBase(PolymorphicModel, PermissionLevelMixin, ItemBase): data_quality_statement_help_text = _( "general explanation of the data producer's knowledge about the lineage of a" " dataset" ) - extra_metadata_help_text = _( - 'Additional metadata, must be in format [ {"metadata_key": "metadata_value"}, {"metadata_key": "metadata_value"} ]' - ) # internal fields uuid = models.CharField(max_length=36, unique=True, default=uuid.uuid4) title = models.CharField(_("title"), max_length=255, help_text=_("name by which the cited resource is known")) @@ -887,10 +884,6 @@ class ResourceBase(PolymorphicModel, PermissionLevelMixin, ItemBase): subtype = models.CharField(max_length=128, null=True, blank=True) - metadata = models.ManyToManyField( - "ExtraMetadata", verbose_name=_("Extra Metadata"), null=True, blank=True, help_text=extra_metadata_help_text - ) - objects = ResourceBaseManager() class Meta: @@ -2137,6 +2130,4 @@ class GroupGeoLimit(models.Model): wkt = models.TextField(db_column="wkt", blank=True) -class ExtraMetadata(models.Model): - resource = models.ForeignKey(ResourceBase, null=False, blank=False, on_delete=models.CASCADE) - metadata = JSONField(null=True, default=dict, blank=True) + diff --git a/geonode/base/utils.py b/geonode/base/utils.py index f5b543b5066..d421230341e 100644 --- a/geonode/base/utils.py +++ b/geonode/base/utils.py @@ -22,16 +22,12 @@ # Standard Modules import re -import json import logging -from schema import Schema from dateutil.parser import isoparse from datetime import datetime, timedelta # Django functionality -from django.conf import settings from django.contrib.auth import get_user_model -from django.core.exceptions import ValidationError # Geonode functionality from geonode.layers.models import Dataset @@ -177,33 +173,6 @@ def get_resource(resource_base): return resource_base.get_real_instance() -def validate_extra_metadata(data, instance): - if not data: - return data - - # starting validation of extra metadata passed via JSON - # if schema for metadata validation is not defined, an error is raised - resource_type = instance.polymorphic_ctype.model if instance.polymorphic_ctype else instance.class_name.lower() - extra_metadata_validation_schema = settings.EXTRA_METADATA_SCHEMA.get(resource_type, None) - if not extra_metadata_validation_schema: - raise ValidationError(f"EXTRA_METADATA_SCHEMA validation schema is not available for resource {resource_type}") - # starting json structure validation. The Field can contain multiple metadata - try: - if isinstance(data, str): - data = json.loads(data) - except Exception: - raise ValidationError("The value provided for the Extra metadata field is not a valid JSON") - - # looping on all the single metadata provided. If it doen't match the schema an error is raised - for _index, _metadata in enumerate(data): - try: - Schema(extra_metadata_validation_schema).validate(_metadata) - except Exception as e: - raise ValidationError(f"{e} at index {_index} for input json: {json.dumps(_metadata)}") - # conerted because in this case, we can store a well formated json instead of the user input - return data - - def remove_country_from_languagecode(language: str): """Remove country code (us) from language name (en-us) >>> remove_country_from_lanugecode("en-us") diff --git a/geonode/documents/api/tests.py b/geonode/documents/api/tests.py index 42f677a189e..c699d7221aa 100644 --- a/geonode/documents/api/tests.py +++ b/geonode/documents/api/tests.py @@ -83,17 +83,6 @@ def test_documents(self): # import json # logger.error(f"{json.dumps(layers_data)}") - def test_extra_metadata_included_with_param(self): - resource = Document.objects.first() - url = urljoin(f"{reverse('documents-list')}/", f"{resource.pk}") - data = {"include[]": "metadata"} - - response = self.client.get(url, format="json", data=data) - self.assertIsNotNone(response.data["document"].get("metadata")) - - response = self.client.get(url, format="json") - self.assertNotIn("metadata", response.data["document"]) - def test_creation_return_error_if_file_is_not_passed(self): """ If file_path is not available, should raise error diff --git a/geonode/geoapps/api/tests.py b/geonode/geoapps/api/tests.py index 7fb78d4160e..11a86393c03 100644 --- a/geonode/geoapps/api/tests.py +++ b/geonode/geoapps/api/tests.py @@ -114,17 +114,6 @@ def test_geoapp_listing_advertised(self): GeoApp.objects.update(advertised=True) - def test_extra_metadata_included_with_param(self): - _app = GeoApp.objects.first() - url = urljoin(f"{reverse('geoapps-list')}/", f"{_app.pk}") - data = {"include[]": "metadata"} - - response = self.client.get(url, format="json", data=data) - self.assertIsNotNone(response.data["geoapp"].get("metadata")) - - response = self.client.get(url, format="json") - self.assertNotIn("metadata", response.data["geoapp"]) - def test_geoapps_crud(self): """ Ensure we can create/update GeoApps. diff --git a/geonode/layers/api/tests.py b/geonode/layers/api/tests.py index a68e61c68ec..27c6992c3bf 100644 --- a/geonode/layers/api/tests.py +++ b/geonode/layers/api/tests.py @@ -209,17 +209,6 @@ def test_dataset_listing_advertised(self): Dataset.objects.update(advertised=True) - def test_extra_metadata_included_with_param(self): - _dataset = Dataset.objects.first() - url = urljoin(f"{reverse('datasets-list')}/", f"{_dataset.pk}") - data = {"include[]": "metadata"} - - response = self.client.get(url, format="json", data=data) - self.assertIsNotNone(response.data["dataset"].get("metadata")) - - response = self.client.get(url, format="json") - self.assertNotIn("metadata", response.data["dataset"]) - def test_get_dataset_related_maps_and_maplayers(self): dataset = Dataset.objects.first() assign_perm("base.view_resourcebase", get_anonymous_user(), dataset.get_self_resource()) diff --git a/geonode/maps/api/tests.py b/geonode/maps/api/tests.py index 24c154d5bf1..24781461654 100644 --- a/geonode/maps/api/tests.py +++ b/geonode/maps/api/tests.py @@ -129,17 +129,6 @@ def test_maps(self): self.assertEqual(response.data["map"]["maplayers"][0]["order"], 0) self.assertEqual(response.data["map"]["maplayers"][0]["opacity"], 1.0) - def test_extra_metadata_included_with_param(self): - resource = Map.objects.first() - url = urljoin(f"{reverse('maps-list')}/", f"{resource.pk}") - data = {"include[]": "metadata"} - - response = self.client.get(url, format="json", data=data) - self.assertIsNotNone(response.data["map"].get("metadata")) - - response = self.client.get(url, format="json") - self.assertNotIn("map", response.data["map"]) - def test_patch_map(self): """ Patch to maps// diff --git a/geonode/resource/manager.py b/geonode/resource/manager.py index 528f3d6628b..9d60f2579c4 100644 --- a/geonode/resource/manager.py +++ b/geonode/resource/manager.py @@ -397,7 +397,6 @@ def update( keywords: list = [], custom: dict = {}, notify: bool = True, - extra_metadata: list = [], *args, **kwargs, ) -> ResourceBase: @@ -439,7 +438,6 @@ def update( regions=regions, keywords=keywords, vals=vals, - extra_metadata=extra_metadata, ) ji = custom.get("jsoninstance", None) diff --git a/geonode/resource/utils.py b/geonode/resource/utils.py index 2e8edb6173a..546a49d9455 100644 --- a/geonode/resource/utils.py +++ b/geonode/resource/utils.py @@ -37,7 +37,6 @@ from ..base import enumerations from ..base.models import ( - ExtraMetadata, Link, License, ResourceBase, @@ -142,7 +141,6 @@ def update_resource( regions: list = [], keywords: list = [], vals: dict = {}, - extra_metadata: list = [], ): if xml_file: instance.metadata_xml = open(xml_file).read() @@ -277,12 +275,6 @@ def update_resource( # Refresh from DB instance.refresh_from_db() - if extra_metadata: - instance.metadata.all().delete() - for _m in extra_metadata: - new_m = ExtraMetadata.objects.create(resource=instance, metadata=_m) - instance.metadata.add(new_m) - return instance diff --git a/geonode/settings.py b/geonode/settings.py index 1150c4247d7..794a39e8cdc 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -25,7 +25,6 @@ import logging import subprocess import dj_database_url -from schema import Optional from urllib.parse import urlparse, urljoin # @@ -2069,39 +2068,6 @@ def get_geonode_catalogue_service(): DEFAULT_AUTO_FIELD = "django.db.models.AutoField" -""" -Default schema used to store extra and dynamic metadata for the resource -""" - -DEFAULT_EXTRA_METADATA_SCHEMA = { - Optional("id"): int, - "filter_header": object, - "field_name": object, - "field_label": object, - "field_value": object, -} - -""" -If present, will extend the available metadata schema used for store -new value for each resource. By default overrided the existing one. -The expected schema is the same as the default -""" -CUSTOM_METADATA_SCHEMA = os.getenv("CUSTOM_METADATA_SCHEMA ", {}) - -""" -Variable used to actually get the expected metadata schema for each resource_type. -In this way, each resource type can have a different metadata schema -""" -EXTRA_METADATA_SCHEMA = { - **{ - "map": os.getenv("MAP_EXTRA_METADATA_SCHEMA", DEFAULT_EXTRA_METADATA_SCHEMA), - "dataset": os.getenv("DATASET_EXTRA_METADATA_SCHEMA", DEFAULT_EXTRA_METADATA_SCHEMA), - "document": os.getenv("DOCUMENT_EXTRA_METADATA_SCHEMA", DEFAULT_EXTRA_METADATA_SCHEMA), - "geoapp": os.getenv("GEOAPP_EXTRA_METADATA_SCHEMA", DEFAULT_EXTRA_METADATA_SCHEMA), - }, - **CUSTOM_METADATA_SCHEMA, -} - """ List of modules that implement custom metadata storers that will be called when the metadata of a resource is saved """ From cfc914c27efc9c7745d695ce83f68536d40ffc7f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:09:02 +0000 Subject: [PATCH 3/3] Fix flake8 errors: remove unused imports and trailing blank line Co-authored-by: etj <717359+etj@users.noreply.github.com> --- geonode/base/api/serializers.py | 2 +- geonode/base/api/views.py | 1 - geonode/base/models.py | 3 --- geonode/geoapps/api/tests.py | 2 -- 4 files changed, 1 insertion(+), 7 deletions(-) diff --git a/geonode/base/api/serializers.py b/geonode/base/api/serializers.py index 522164c70cc..812b7e98ac3 100644 --- a/geonode/base/api/serializers.py +++ b/geonode/base/api/serializers.py @@ -27,7 +27,7 @@ from django.contrib.auth.models import Group 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, is_asset_deletable from geonode.metadata.multilang.serializers import MultiLangOutputMixin from geonode.people import Roles diff --git a/geonode/base/api/views.py b/geonode/base/api/views.py index 0f03ae69871..1ad9fa915de 100644 --- a/geonode/base/api/views.py +++ b/geonode/base/api/views.py @@ -89,7 +89,6 @@ from geonode.resource.manager import resource_manager from .permissions import ( - IsOwnerOrAdmin, IsManagerEditOrAdmin, ResourceBasePermissionsFilter, UserHasPerms, diff --git a/geonode/base/models.py b/geonode/base/models.py index 3d4fc801d0e..c6b356d0352 100644 --- a/geonode/base/models.py +++ b/geonode/base/models.py @@ -2128,6 +2128,3 @@ class GroupGeoLimit(models.Model): group = models.ForeignKey(GroupProfile, null=False, blank=False, on_delete=models.CASCADE) resource = models.ForeignKey(ResourceBase, null=False, blank=False, on_delete=models.CASCADE) wkt = models.TextField(db_column="wkt", blank=True) - - - diff --git a/geonode/geoapps/api/tests.py b/geonode/geoapps/api/tests.py index 11a86393c03..a6198e47283 100644 --- a/geonode/geoapps/api/tests.py +++ b/geonode/geoapps/api/tests.py @@ -19,8 +19,6 @@ import json import logging from unittest.mock import MagicMock -from urllib.parse import urljoin - from django.contrib.auth import get_user_model from django.urls import reverse from django.test import override_settings