From c04230487636d5d37c59dbbede1e560f476e7c91 Mon Sep 17 00:00:00 2001 From: sijandh35 Date: Fri, 3 Apr 2026 10:22:22 +0000 Subject: [PATCH 1/6] [Fixes #13997] Fixes Custom license saved without identifier --- geonode/base/models.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/geonode/base/models.py b/geonode/base/models.py index 4d7bd0f005b..0d0f69336a6 100644 --- a/geonode/base/models.py +++ b/geonode/base/models.py @@ -235,6 +235,11 @@ class License(models.Model): def __str__(self): return str(self.name) + def save(self, *args, **kwargs): + if not self.identifier: + self.identifier = uuid.uuid4().hex + super().save(*args, **kwargs) + @property def name_long(self): if self.abbreviation is None or len(self.abbreviation) == 0: From d47de0054f543cea99d4f101535cb41784ba4624 Mon Sep 17 00:00:00 2001 From: sijandh35 Date: Fri, 3 Apr 2026 10:45:12 +0000 Subject: [PATCH 2/6] [Fixes #13997] correct review suggestion --- geonode/base/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geonode/base/models.py b/geonode/base/models.py index 0d0f69336a6..455f2f821d0 100644 --- a/geonode/base/models.py +++ b/geonode/base/models.py @@ -237,7 +237,7 @@ def __str__(self): def save(self, *args, **kwargs): if not self.identifier: - self.identifier = uuid.uuid4().hex + self.identifier = str(uuid.uuid4()) super().save(*args, **kwargs) @property From 98929437c717c8f2a03828ab908e62842ccfcd11 Mon Sep 17 00:00:00 2001 From: sijandh35 Date: Wed, 8 Apr 2026 07:28:14 +0000 Subject: [PATCH 3/6] [Fixes #13997] make identifier editable and populate existing --- .../0098_alter_license_identifier.py | 27 +++++++++++++++++++ geonode/base/models.py | 7 +---- 2 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 geonode/base/migrations/0098_alter_license_identifier.py diff --git a/geonode/base/migrations/0098_alter_license_identifier.py b/geonode/base/migrations/0098_alter_license_identifier.py new file mode 100644 index 00000000000..3831e55a7d9 --- /dev/null +++ b/geonode/base/migrations/0098_alter_license_identifier.py @@ -0,0 +1,27 @@ +# Generated by Django 5.2.12 on 2026-04-08 06:35 + +from django.db import migrations, models +from django.db.models import Q +import uuid + +def populate_license_identifiers(apps, schema_editor): + License = apps.get_model("base", "License") + for lic in License.objects.filter(Q(identifier="") | Q(identifier__isnull=True)): + lic.identifier = str(uuid.uuid4()) + lic.save(update_fields=["identifier"]) + + +class Migration(migrations.Migration): + + dependencies = [ + ("base", "0097_alter_link_asset"), + ] + + operations = [ + migrations.RunPython(populate_license_identifiers, migrations.RunPython.noop), + migrations.AlterField( + model_name="license", + name="identifier", + field=models.CharField(max_length=255, unique=True), + ), + ] diff --git a/geonode/base/models.py b/geonode/base/models.py index 455f2f821d0..9469871ae26 100644 --- a/geonode/base/models.py +++ b/geonode/base/models.py @@ -225,7 +225,7 @@ class Meta: class License(models.Model): - identifier = models.CharField(max_length=255, editable=False) + identifier = models.CharField(max_length=255, unique=True) name = models.CharField(max_length=255) abbreviation = models.CharField(max_length=20, null=True, blank=True) description = models.TextField(null=True, blank=True) @@ -235,11 +235,6 @@ class License(models.Model): def __str__(self): return str(self.name) - def save(self, *args, **kwargs): - if not self.identifier: - self.identifier = str(uuid.uuid4()) - super().save(*args, **kwargs) - @property def name_long(self): if self.abbreviation is None or len(self.abbreviation) == 0: From c2515f8330e054da20d2d400cb6fdcf5f2e6a251 Mon Sep 17 00:00:00 2001 From: sijandh35 Date: Wed, 8 Apr 2026 08:59:58 +0000 Subject: [PATCH 4/6] [Fixes #13997] change the metadata editor procedures to use the id instead of the identifier for license --- geonode/metadata/api/views.py | 2 +- geonode/metadata/handlers/base.py | 4 ++-- geonode/metadata/schemas/base.json | 2 +- geonode/metadata/tests/test_handlers.py | 10 +++++----- geonode/metadata/tests/tests.py | 14 +++++++------- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/geonode/metadata/api/views.py b/geonode/metadata/api/views.py index 6009893d0fc..2c5ae6e8bd0 100644 --- a/geonode/metadata/api/views.py +++ b/geonode/metadata/api/views.py @@ -296,7 +296,7 @@ def licenses_autocomplete(request: WSGIRequest): for record in qs.all(): ret.append( { - "id": record.identifier, + "id": record.id, "label": _(record.name), } ) diff --git a/geonode/metadata/handlers/base.py b/geonode/metadata/handlers/base.py index c560f631d13..589b9aad828 100644 --- a/geonode/metadata/handlers/base.py +++ b/geonode/metadata/handlers/base.py @@ -115,14 +115,14 @@ def serialize(cls, db_value): if db_value is None: return None elif isinstance(db_value, License): - return {"id": db_value.identifier, "label": _(db_value.name)} + return {"id": db_value.id, "label": _(db_value.name)} else: logger.warning(f"License: can't decode <{type(db_value)}>'{db_value}'") return None @classmethod def deserialize(cls, field_value): - return License.objects.get(identifier=field_value["id"]) if field_value else None + return License.objects.get(id=field_value["id"]) if field_value else None class RestrictionsSubHandler(SubHandler): diff --git a/geonode/metadata/schemas/base.json b/geonode/metadata/schemas/base.json index f268ae69cfa..f022662ecfb 100644 --- a/geonode/metadata/schemas/base.json +++ b/geonode/metadata/schemas/base.json @@ -67,7 +67,7 @@ "maxLength": 255, "properties": { "id": { - "type": "string" + "type": "integer" }, "label": { "type": "string" diff --git a/geonode/metadata/tests/test_handlers.py b/geonode/metadata/tests/test_handlers.py index 2a492f82406..7ce59cd8289 100644 --- a/geonode/metadata/tests/test_handlers.py +++ b/geonode/metadata/tests/test_handlers.py @@ -457,12 +457,12 @@ def test_license_subhandler_serialize_with_existed_db_value(self): # Test the case where the db_value is a model instance serialized_value = LicenseSubHandler.serialize(self.license) - expected_value = {"id": self.license.identifier, "label": _(self.license.name)} + expected_value = {"id": self.license.id, "label": _(self.license.name)} self.assertEqual(serialized_value, expected_value) - # Assert that the serialize method returns the identifier - self.assertEqual(serialized_value["id"], self.license.identifier) + # Assert that the serialize method returns the id + self.assertEqual(serialized_value["id"], self.license.id) def test_license_subhandler_serialize_invalid_data(self): """ @@ -486,14 +486,14 @@ def test_license_subhandler_deserialize(self): An instance of this model has been created initial setup """ - field_value = {"id": "fake_license"} + field_value = {"id": self.license.id} # Call the method using the "fake_category" identifier from the created instance deserialized_value = LicenseSubHandler.deserialize(field_value) # Assert that the deserialized value is the correct model instance self.assertEqual(deserialized_value, self.license) - self.assertEqual(deserialized_value.identifier, field_value["id"]) + self.assertEqual(deserialized_value.id, field_value["id"]) def test_license_subhandler_deserialize_with_invalid_data(self): """ diff --git a/geonode/metadata/tests/tests.py b/geonode/metadata/tests/tests.py index 42d3fcd43e4..4f82dd55bf1 100644 --- a/geonode/metadata/tests/tests.py +++ b/geonode/metadata/tests/tests.py @@ -94,8 +94,8 @@ def setUp(self): # Setup the database TopicCategory.objects.create(identifier="cat1", gn_description="fake category 1") TopicCategory.objects.create(identifier="cat2", gn_description="fake category 2") - License.objects.create(identifier="license1", name="fake license 1") - License.objects.create(identifier="license2", name="fake license 2") + self.license1 = License.objects.create(identifier="license1", name="fake license 1") + self.license2 = License.objects.create(identifier="license2", name="fake license 2") Region.objects.create(code="fake_code_1", name="fake name 1") Region.objects.create(code="fake_code_2", name="fake name 2") HierarchicalKeyword.objects.create(name="fake_keyword_1", slug="fake keyword 1") @@ -355,8 +355,8 @@ def test_license_autocomplete_no_query(self): self.assertEqual(response.status_code, 200) results = response.json()["results"] self.assertEqual(len(results), 2) - self.assertIn({"id": "license1", "label": _("fake license 1")}, results) - self.assertIn({"id": "license2", "label": _("fake license 2")}, results) + self.assertIn({"id": self.license1.id, "label": _("fake license 1")}, results) + self.assertIn({"id": self.license2.id, "label": _("fake license 2")}, results) def test_license_autocomplete_with_query(self): """ @@ -369,8 +369,8 @@ def test_license_autocomplete_with_query(self): self.assertEqual(response.status_code, 200) results = response.json()["results"] self.assertEqual(len(results), 2) - self.assertIn({"id": "license1", "label": _("fake license 1")}, results) - self.assertIn({"id": "license2", "label": _("fake license 2")}, results) + self.assertIn({"id": self.license1.id, "label": _("fake license 1")}, results) + self.assertIn({"id": self.license2.id, "label": _("fake license 2")}, results) def test_license_autocomplete_with_query_one_match(self): """ @@ -383,7 +383,7 @@ def test_license_autocomplete_with_query_one_match(self): self.assertEqual(response.status_code, 200) results = response.json()["results"] self.assertEqual(len(results), 1) - self.assertIn({"id": "license2", "label": _("fake license 2")}, results) + self.assertIn({"id": self.license2.id, "label": _("fake license 2")}, results) def test_license_autocomplete_with_query_no_match(self): """ From b4df12f64470513c3340f31db7651dbe85a43f3c Mon Sep 17 00:00:00 2001 From: sijandh35 Date: Wed, 8 Apr 2026 09:37:30 +0000 Subject: [PATCH 5/6] [Fixes #13997] fix test case failure due to change --- geonode/api/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/geonode/api/tests.py b/geonode/api/tests.py index 71559cbe835..c7160fdd637 100644 --- a/geonode/api/tests.py +++ b/geonode/api/tests.py @@ -43,6 +43,7 @@ ThesaurusKeyword, ThesaurusKeywordLabel, ResourceBase, + License, ) from geonode.utils import check_ogc_backend from geonode.decorators import on_ogc_backend @@ -1048,7 +1049,7 @@ def _get_updated_metadata(self, resource, old_owner, new_owner): return { "date_type": "publication", "hkeywords": [], - "license": {"id": "not_specified", "label": "Not Specified"}, + "license": {"id": License.objects.get(identifier="not_specified").id, "label": "Not Specified"}, "regions": [], "supplemental_information": "No information provided", "linkedresources": [], From f3c253e8fe08ca4b22e5e414edb39246a4f627b2 Mon Sep 17 00:00:00 2001 From: sijandh35 Date: Thu, 9 Apr 2026 10:24:44 +0000 Subject: [PATCH 6/6] [Fixes #13997] fixes for duplicates identifier also --- .../migrations/0098_alter_license_identifier.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/geonode/base/migrations/0098_alter_license_identifier.py b/geonode/base/migrations/0098_alter_license_identifier.py index 3831e55a7d9..5f2dcd3b9ac 100644 --- a/geonode/base/migrations/0098_alter_license_identifier.py +++ b/geonode/base/migrations/0098_alter_license_identifier.py @@ -6,10 +6,15 @@ def populate_license_identifiers(apps, schema_editor): License = apps.get_model("base", "License") - for lic in License.objects.filter(Q(identifier="") | Q(identifier__isnull=True)): - lic.identifier = str(uuid.uuid4()) - lic.save(update_fields=["identifier"]) - + seen = set() + for lic in License.objects.order_by("id"): + identifier = lic.identifier + if not identifier or identifier in seen: + lic.identifier = str(uuid.uuid4()) + lic.save(update_fields=["identifier"]) + seen.add(lic.identifier) + else: + seen.add(identifier) class Migration(migrations.Migration):