diff --git a/geonode/api/resourcebase_api.py b/geonode/api/resourcebase_api.py index 44ecf59f6e1..ba72c21689f 100644 --- a/geonode/api/resourcebase_api.py +++ b/geonode/api/resourcebase_api.py @@ -95,7 +95,8 @@ class CommonMetaApi: 'date': ALL, 'purpose': ALL, 'uuid': ALL_WITH_RELATIONS, - 'abstract': ALL + 'abstract': ALL, + 'metadata': ALL_WITH_RELATIONS } ordering = ['date', 'title', 'popular_count'] max_limit = None @@ -168,6 +169,9 @@ def build_filters(self, filters=None, ignore_bad_filters=False, **kwargs): orm_filters.update({'polymorphic_ctype__model__in': [filt.lower() for filt in filters.getlist('app_type__in')]}) if 'extent' in filters: orm_filters.update({'extent': filters['extent']}) + _metadata = {f"metadata__{_k}": _v for _k, _v in filters.items() if _k.startswith('metadata__')} + if _metadata: + orm_filters.update({"metadata_filters": _metadata}) orm_filters['f_method'] = filters['f_method'] if 'f_method' in filters else 'and' if not settings.SEARCH_RESOURCES_EXTENDED: return self._remove_additional_filters(orm_filters) @@ -186,6 +190,9 @@ def apply_filters(self, request, applicable_filters): metadata_only = applicable_filters.pop('metadata_only', False) filtering_method = applicable_filters.pop('f_method', 'and') polyphormic_model = applicable_filters.pop('polymorphic_ctype__model__in', None) + + metadata_filters = applicable_filters.pop('metadata_filters', None) + if filtering_method == 'or': filters = Q() for f in applicable_filters.items(): @@ -234,6 +241,9 @@ 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, @@ -589,6 +599,9 @@ def format_objects(self, objects): except Exception as e: logger.exception(e) + if formatted_obj.get('metadata', None): + formatted_obj['metadata'] = [model_to_dict(_m) for _m in formatted_obj['metadata']] + formatted_objects.append(formatted_obj) return formatted_objects diff --git a/geonode/api/tests.py b/geonode/api/tests.py index 8c85a2fa373..d748bc826e3 100644 --- a/geonode/api/tests.py +++ b/geonode/api/tests.py @@ -30,6 +30,7 @@ from guardian.shortcuts import get_anonymous_user from geonode import geoserver +from geonode.base.models import ExtraMetadata from geonode.maps.models import Map from geonode.layers.models import Layer from geonode.utils import check_ogc_backend @@ -360,6 +361,35 @@ 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 = Layer.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" + } + ) + _r.metadata.add(_m) + # check we get the correct layers number returnered filtering on one + # and then two different categories + filter_url = f"{self.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"{self.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""" diff --git a/geonode/base/api/serializers.py b/geonode/base/api/serializers.py index 581ff8c66f1..4ed9121a9a9 100644 --- a/geonode/base/api/serializers.py +++ b/geonode/base/api/serializers.py @@ -16,6 +16,7 @@ # along with this program. If not, see . # ######################################################################### +from django.db.models.query import QuerySet from slugify import slugify from urllib.parse import urljoin @@ -35,15 +36,16 @@ from geonode.favorite.models import Favorite from geonode.base.models import ( - ResourceBase, + ExtraMetadata, HierarchicalKeyword, + License, Region, + ResourceBase, RestrictionCodeType, - License, - TopicCategory, SpatialRepresentationType, ThesaurusKeyword, - ThesaurusKeywordLabel + ThesaurusKeywordLabel, + TopicCategory, ) from geonode.groups.models import ( GroupCategory, @@ -263,6 +265,24 @@ 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): @@ -381,6 +401,8 @@ def __init__(self, *args, **kwargs): self.fields['spatial_representation_type'] = DynamicRelationField( SpatialRepresentationTypeSerializer, embed=True, many=False) + metadata = DynamicRelationField(ExtraMetadataSerializer, embed=False, many=True, deferred=True) + class Meta: model = ResourceBase name = 'resource' @@ -397,7 +419,7 @@ class Meta: 'popular_count', 'share_count', 'rating', 'featured', 'is_published', 'is_approved', 'detail_url', 'embed_url', 'created', 'last_updated', 'raw_abstract', 'raw_purpose', 'raw_constraints_other', - 'raw_supplemental_information', 'raw_data_quality_statement', 'metadata_only', 'processed' + 'raw_supplemental_information', 'raw_data_quality_statement', 'metadata_only', 'processed', "metadata" # TODO # csw_typename, csw_schema, csw_mdsource, csw_insert_date, csw_type, csw_anytext, csw_wkt_geometry, # metadata_uploaded, metadata_uploaded_preserve, metadata_xml, diff --git a/geonode/base/api/tests.py b/geonode/base/api/tests.py index 99f0b86d6d0..207dfaa755a 100644 --- a/geonode/base/api/tests.py +++ b/geonode/base/api/tests.py @@ -36,16 +36,18 @@ from geonode import geoserver from geonode.layers.models import Layer +from geonode.tests.base import GeoNodeBaseTestSupport from geonode.utils import check_ogc_backend, set_resource_default_links from geonode.favorite.models import Favorite from geonode.documents.models import Document from geonode.base.utils import build_absolute_uri from geonode.thumbs.exceptions import ThumbnailError -from geonode.base.populate_test_data import create_models +from geonode.base.populate_test_data import create_models, create_single_layer from geonode.security.utils import get_resources_with_perms from geonode.base.models import ( CuratedThumbnail, + ExtraMetadata, HierarchicalKeyword, Region, ResourceBase, @@ -1021,3 +1023,64 @@ def test_set_thumbnail_from_bbox_from_logged_user_for_existing_dataset_raise_exp } self.assertEqual(response.status_code, 500) self.assertEqual(expected, response.json()) + + +class TestExtraMetadataBaseApi(GeoNodeBaseTestSupport): + def setUp(self): + self.layer = create_single_layer('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()) diff --git a/geonode/base/api/views.py b/geonode/base/api/views.py index c59767ed61f..691bdd3a65d 100644 --- a/geonode/base/api/views.py +++ b/geonode/base/api/views.py @@ -38,7 +38,7 @@ from geonode.favorite.models import Favorite from geonode.thumbs.exceptions import ThumbnailError from geonode.thumbs.thumbnails import create_thumbnail -from geonode.base.models import HierarchicalKeyword, Region, ResourceBase, TopicCategory, ThesaurusKeyword +from geonode.base.models import ExtraMetadata, HierarchicalKeyword, Region, ResourceBase, TopicCategory, ThesaurusKeyword from geonode.base.api.filters import DynamicSearchFilter, ExtentFilter, FavoriteFilter from geonode.groups.models import GroupProfile, GroupMember from geonode.layers.models import Layer @@ -70,6 +70,8 @@ ThesaurusKeywordSerializer, ) from .pagination import GeoNodeApiPagination +from geonode.base.api.serializers import ExtraMetadataSerializer +from geonode.base.utils import validate_extra_metadata import logging @@ -466,3 +468,84 @@ def set_thumbnail_from_bbox(self, request, resource_id): traceback.print_exc() logger.error(e) return Response(data={"message": e.args[0], "success": False}, status=500, exception=True) + + @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, + ], + url_path=r"extra_metadata", # noqa + url_name="extra-metadata", + ) + def extra_metadata(self, request, pk=None): + _obj = self.get_object() + 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) diff --git a/geonode/base/forms.py b/geonode/base/forms.py index 939e61c9998..ab220ceef9a 100644 --- a/geonode/base/forms.py +++ b/geonode/base/forms.py @@ -16,6 +16,7 @@ # along with this program. If not, see . # ######################################################################### +import json import re import html import logging @@ -44,6 +45,7 @@ License, Region, ResourceBase, Thesaurus, ThesaurusKeyword, ThesaurusKeywordLabel, ThesaurusLabel, TopicCategory) +from geonode.base.utils import validate_extra_metadata from geonode.base.widgets import TaggitSelect2Custom from geonode.documents.models import Document from geonode.layers.models import Layer @@ -477,8 +479,19 @@ class ResourceBaseForm(TranslationModelForm): regions.widget.attrs = {"size": 20} + extra_metadata = forms.CharField( + required=False, + widget=forms.Textarea, + help_text=_('Additional metadata, must be in format [\ + {"metadata_key": "metadata_value"},\ + {"metadata_key": "metadata_value"} \ + ]') +) + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + if self.instance and self.instance.id and self.instance.metadata.exists(): + self.fields['extra_metadata'].initial = [x.metadata for x in self.instance.metadata.all()] for field in self.fields: help_text = self.fields[field].help_text if help_text != '': @@ -524,6 +537,10 @@ def clean_title(self): title = title.replace(",", "_") return title + def clean_extra_metadata(self): + cleaned_data = self.cleaned_data.get('extra_metadata', []) + return json.dumps(validate_extra_metadata(cleaned_data, self.instance), indent=4) + class Meta: exclude = ( 'contacts', diff --git a/geonode/base/migrations/0062_resourcebase_extra_metadata.py b/geonode/base/migrations/0062_resourcebase_extra_metadata.py new file mode 100644 index 00000000000..0f48abef8c3 --- /dev/null +++ b/geonode/base/migrations/0062_resourcebase_extra_metadata.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.24 on 2022-01-26 14:04 + +from django.db import migrations +import django_jsonfield_backport.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0061_auto_20211117_1238'), + ] + + operations = [ + migrations.AddField( + model_name='resourcebase', + name='extra_metadata', + field=django_jsonfield_backport.models.JSONField(blank=True, default=list, null=True), + ), + ] diff --git a/geonode/base/migrations/0063_auto_20220128_1042.py b/geonode/base/migrations/0063_auto_20220128_1042.py new file mode 100644 index 00000000000..ee53e668f85 --- /dev/null +++ b/geonode/base/migrations/0063_auto_20220128_1042.py @@ -0,0 +1,27 @@ +# Generated by Django 2.2.24 on 2022-01-28 10:42 + +from django.db import migrations, models +import django.db.models.deletion +import django_jsonfield_backport.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0062_resourcebase_extra_metadata'), + ] + + operations = [ + migrations.RemoveField( + model_name='resourcebase', + name='extra_metadata', + ), + migrations.CreateModel( + name='ExtraMetadata', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('metadata', django_jsonfield_backport.models.JSONField(blank=True, default=dict, null=True)), + ('resource', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='base.ResourceBase')), + ], + ), + ] diff --git a/geonode/base/migrations/0064_resourcebase_metadata.py b/geonode/base/migrations/0064_resourcebase_metadata.py new file mode 100644 index 00000000000..5e12788dd25 --- /dev/null +++ b/geonode/base/migrations/0064_resourcebase_metadata.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.24 on 2022-01-28 10:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0063_auto_20220128_1042'), + ] + + operations = [ + migrations.AddField( + model_name='resourcebase', + name='metadata', + field=models.ManyToManyField(blank=True, null=True, to='base.ExtraMetadata', verbose_name='Extra Metadata'), + ), + ] diff --git a/geonode/base/models.py b/geonode/base/models.py index cf0d92b1cb8..1e80b419ef3 100644 --- a/geonode/base/models.py +++ b/geonode/base/models.py @@ -43,6 +43,7 @@ from django.core.files.storage import default_storage as storage from django.utils.html import strip_tags from mptt.models import MPTTModel, TreeForeignKey +from django_jsonfield_backport.models import JSONField from PIL import Image, ImageOps @@ -701,6 +702,8 @@ 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) title = models.CharField(_('title'), max_length=255, help_text=_( @@ -961,6 +964,13 @@ class ResourceBase(PolymorphicModel, PermissionLevelMixin, ItemBase): default=False, help_text=_('If true, will be excluded from search')) + metadata = models.ManyToManyField( + "ExtraMetadata", + verbose_name=_('Extra Metadata'), + null=True, + blank=True, + help_text=extra_metadata_help_text) + objects = ResourceBaseManager() class Meta: @@ -2085,3 +2095,12 @@ def rating_post_save(instance, *args, **kwargs): signals.post_save.connect(rating_post_save, sender=OverallRating) + + +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/populate_test_data.py b/geonode/base/populate_test_data.py index aa1862cee93..cca8d8a6548 100644 --- a/geonode/base/populate_test_data.py +++ b/geonode/base/populate_test_data.py @@ -355,7 +355,7 @@ def create_single_layer(name, keywords=None, owner=None, group=None): return layer -def create_single_map(name, keywords=None): +def create_single_map(name, keywords=None, owner=None): admin, created = get_user_model().objects.get_or_create(username='admin') if created: admin.is_superuser = True @@ -374,7 +374,7 @@ def create_single_map(name, keywords=None): projection='EPSG:4326', center_x=42, center_y=-73, - owner=user, + owner=owner or user, bbox_polygon=Polygon.from_bbox((bbox_x0, bbox_y0, bbox_x1, bbox_y1)), ll_bbox_polygon=Polygon.from_bbox((bbox_x0, bbox_y0, bbox_x1, bbox_y1)), srid='EPSG:4326', @@ -390,7 +390,7 @@ def create_single_map(name, keywords=None): return m -def create_single_doc(name, keywords=None): +def create_single_doc(name, keywords=None, owner=None): admin, created = get_user_model().objects.get_or_create(username='admin') if created: admin.is_superuser = True @@ -406,7 +406,7 @@ def create_single_doc(name, keywords=None): m = Document( title=title, abstract=abstract, - owner=user, + owner=owner or user, bbox_polygon=Polygon.from_bbox((bbox_x0, bbox_y0, bbox_x1, bbox_y1)), ll_bbox_polygon=Polygon.from_bbox((bbox_x0, bbox_y0, bbox_x1, bbox_y1)), srid='EPSG:4326', diff --git a/geonode/base/utils.py b/geonode/base/utils.py index 11d7f877588..829b335a66d 100644 --- a/geonode/base/utils.py +++ b/geonode/base/utils.py @@ -21,6 +21,7 @@ """ # Standard Modules +import json import re import logging from urllib.parse import urljoin @@ -31,7 +32,7 @@ # Django functionality from django.conf import settings from django.contrib.auth import get_user_model - +from django.core.exceptions import ValidationError # Geonode functionality from guardian.shortcuts import get_perms, remove_perm, assign_perm @@ -41,6 +42,7 @@ get_thumbs, remove_thumb) from geonode.utils import get_legend_url +from schema import Schema logger = logging.getLogger('geonode.base.utils') @@ -207,3 +209,36 @@ def _restore_owner_permissions(self): assign_perm(perm, self.resource.owner, self.resource) elif perm not in {'change_resourcebase_permissions', 'publish_resourcebase'}: assign_perm(perm, self.resource.owner, self.resource) + + +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 diff --git a/geonode/documents/templates/layouts/doc_panels.html b/geonode/documents/templates/layouts/doc_panels.html index f3dcc8cdf6c..7a9f9d7c0ab 100644 --- a/geonode/documents/templates/layouts/doc_panels.html +++ b/geonode/documents/templates/layouts/doc_panels.html @@ -539,7 +539,13 @@ {{ document_form.spatial_representation_type }} - {% endblock doc_spatial_representation_type %} + {% endblock doc_spatial_representation_type %} + {% block doc_extra_metadata %} +
+ + {{ document_form.extra_metadata }} +
+ {% endblock doc_extra_metadata %}
diff --git a/geonode/documents/tests.py b/geonode/documents/tests.py index 1ad4b404797..4bdd83cc5af 100644 --- a/geonode/documents/tests.py +++ b/geonode/documents/tests.py @@ -807,9 +807,17 @@ def test_that_non_admin_user_can_create_write_to_map_without_keyword(self): self.client.login(username=self.not_admin.username, password='very-secret') url = reverse('document_metadata', args=(self.test_doc.pk,)) with self.settings(FREETEXT_KEYWORDS_READONLY=True): - response = self.client.post(url) + response = self.client.post(url, data={ + "resource-owner": self.not_admin.id, + "resource-title": "doc", + "resource-date": "2022-01-24 16:38 pm", + "resource-date_type": "creation", + "resource-language": "eng", + }) self.assertFalse(self.not_admin.is_superuser) self.assertEqual(response.status_code, 200) + self.test_doc.refresh_from_db() + self.assertEqual('doc', self.test_doc.title) def test_that_non_admin_user_cannot_create_edit_keyword(self): """ @@ -843,9 +851,18 @@ def test_that_non_admin_user_can_create_edit_keyword_when_freetext_keywords_read self.client.login(username=self.not_admin.username, password='very-secret') url = reverse('document_metadata', args=(self.test_doc.pk,)) with self.settings(FREETEXT_KEYWORDS_READONLY=False): - response = self.client.post(url, data={'resource-keywords': 'wonderful-keyword'}) + response = self.client.post(url, data={ + "resource-owner": self.not_admin.id, + "resource-title": "doc", + "resource-date": "2022-01-24 16:38 pm", + "resource-date_type": "creation", + "resource-language": "eng", + 'resource-keywords': 'wonderful-keyword' + }) self.assertFalse(self.not_admin.is_superuser) self.assertEqual(response.status_code, 200) + self.test_doc.refresh_from_db() + self.assertEqual("doc", self.test_doc.title) def test_document_link_with_permissions(self): self.test_doc.set_permissions(self.perm_spec) diff --git a/geonode/documents/views.py b/geonode/documents/views.py index 6b74f7df48a..2f7cfa2d89e 100644 --- a/geonode/documents/views.py +++ b/geonode/documents/views.py @@ -45,6 +45,7 @@ from geonode.base.bbox_utils import BBOXHelper from geonode.base.forms import CategoryForm, TKeywordForm, ThesaurusAvailableForm from geonode.base.models import ( + ExtraMetadata, Thesaurus, TopicCategory) from geonode.documents.enumerations import DOCUMENT_TYPE_MAP, DOCUMENT_MIMETYPE_MAP @@ -463,6 +464,17 @@ def document_metadata( document.regions.clear() document.regions.add(*new_regions) document.category = new_category + + # deleting old metadata from the resource + document.metadata.all().delete() + # creating new metadata for the resource + for _m in json.loads(document_form.cleaned_data['extra_metadata']): + new_m = ExtraMetadata.objects.create( + resource=document, + metadata=_m + ) + document.metadata.add(new_m) + document.save(notify=True) document_form.save_many2many() @@ -498,7 +510,18 @@ def document_metadata( logger.error(tb) return HttpResponse(json.dumps({'message': message})) - + elif request.method == "POST" and (not document_form.is_valid( + ) or not category_form.is_valid() or not tkeywords_form.is_valid()): + errors_list = {**document_form.errors.as_data(), **category_form.errors.as_data(), **tkeywords_form.errors.as_data()} + logger.error(f"GeoApp Metadata form is not valid: {errors_list}") + out = { + 'success': False, + "errors": [f"{x}: {y[0].messages[0]}" for x, y in errors_list.items()] + } + return HttpResponse( + json.dumps(out), + content_type='application/json', + status=400) # - POST Request Ends here - # Request.GET diff --git a/geonode/geoapps/templates/layouts/app_panels.html b/geonode/geoapps/templates/layouts/app_panels.html index 5decca27fde..40800356fbc 100644 --- a/geonode/geoapps/templates/layouts/app_panels.html +++ b/geonode/geoapps/templates/layouts/app_panels.html @@ -470,6 +470,12 @@ {{ geoapp_form.spatial_representation_type }}
+ {% block geoapp_extra_metadata %} +
+ + {{ geoapp_form.extra_metadata }} +
+ {% endblock geoapp_extra_metadata %}
diff --git a/geonode/geoapps/tests.py b/geonode/geoapps/tests.py index 1dc371bddd4..971f7a0d7da 100644 --- a/geonode/geoapps/tests.py +++ b/geonode/geoapps/tests.py @@ -16,8 +16,10 @@ # along with this program. If not, see . # ######################################################################### +from django.test import override_settings from django.urls import reverse from django.contrib.auth import get_user_model +from geonode.geoapps.forms import GeoAppForm from geonode.geoapps.models import GeoApp from geonode.tests.base import GeoNodeBaseTestSupport @@ -25,8 +27,18 @@ class TestGeoAppViews(GeoNodeBaseTestSupport): + def setUp(self) -> None: + self.user = get_user_model().objects.get(username='admin') + self.geoapp = GeoApp.objects.create( + name="name", + title="geoapp_titlte", + thumbnail_url='initial', + owner=self.user + ) + self.sut = GeoAppForm + def test_update_geoapp_metadata(self): - bobby = get_user_model().objects.get(username='bobby') + bobby = get_user_model().objects.get(username='admin') gep_app = GeoApp.objects.create( title="App", thumbnail_url='initial', @@ -41,10 +53,73 @@ def test_update_geoapp_metadata(self): "resource-date_type": 'publication', 'resource-language': gep_app.language } - self.client.login(username=bobby.username, password='bob') + self.client.login(username=bobby.username, password='admin') response = self.client.post(url, data=data, format='json') self.assertEqual(response.status_code, 200) self.assertEqual(gep_app.thumbnail_url, GeoApp.objects.get(id=gep_app.id).thumbnail_url) self.assertEqual(GeoApp.objects.get(id=gep_app.id).title, 'New title') # Check uuid is populate self.assertTrue(GeoApp.objects.get(id=gep_app.id).uuid) + + def test_resource_form_is_invalid_extra_metadata_not_json_format(self): + self.client.login(username="admin", password="admin") + url = reverse("geoapp_metadata", args=(self.geoapp.id,)) + response = self.client.post(url, data={ + "resource-owner": self.geoapp.owner.id, + "resource-title": "geoapp_title", + "resource-date": "2022-01-24 16:38 pm", + "resource-date_type": "creation", + "resource-language": "eng", + "resource-extra_metadata": "not-a-json" + }) + expected = {"success": False, "errors": ["extra_metadata: The value provided for the Extra metadata field is not a valid JSON"]} + self.assertDictEqual(expected, response.json()) + + @override_settings(EXTRA_METADATA_SCHEMA={"key": "value"}) + def test_resource_form_is_invalid_extra_metadata_not_schema_in_settings(self): + self.client.login(username="admin", password="admin") + url = reverse("geoapp_metadata", args=(self.geoapp.id,)) + response = self.client.post(url, data={ + "resource-owner": self.geoapp.owner.id, + "resource-title": "geoapp_title", + "resource-date": "2022-01-24 16:38 pm", + "resource-date_type": "creation", + "resource-language": "eng", + "resource-extra_metadata": "[{'key': 'value'}]" + }) + expected = {"success": False, "errors": ["extra_metadata: EXTRA_METADATA_SCHEMA validation schema is not available for resource geoapp"]} + self.assertDictEqual(expected, response.json()) + + def test_resource_form_is_invalid_extra_metadata_invalids_schema_entry(self): + self.client.login(username="admin", password="admin") + url = reverse("geoapp_metadata", args=(self.geoapp.id,)) + response = self.client.post(url, data={ + "resource-owner": self.geoapp.owner.id, + "resource-title": "geoapp_title", + "resource-date": "2022-01-24 16:38 pm", + "resource-date_type": "creation", + "resource-language": "eng", + "resource-extra_metadata": '[{"key": "value"},{"id": "int", "filter_header": "object", "field_name": "object", "field_label": "object", "field_value": "object"}]' + }) + expected = "extra_metadata: Missing keys: \'field_label\', \'field_name\', \'field_value\', \'filter_header\' at index 0 " + self.assertIn(expected, response.json()['errors'][0]) + + @override_settings(EXTRA_METADATA_SCHEMA={ + "geoapp": { + "id": int, + "filter_header": object, + "field_name": object, + "field_label": object, + "field_value": object + } + }) + def test_resource_form_is_valid_extra_metadata(self): + form = self.sut(data={ + "owner": self.geoapp.owner.id, + "title": "geoapp_title", + "date": "2022-01-24 16:38 pm", + "date_type": "creation", + "language": "eng", + "extra_metadata": '[{"id": 1, "filter_header": "object", "field_name": "object", "field_label": "object", "field_value": "object"}]' + }) + self.assertTrue(form.is_valid()) diff --git a/geonode/geoapps/views.py b/geonode/geoapps/views.py index 23b07709bc7..95c0809a733 100644 --- a/geonode/geoapps/views.py +++ b/geonode/geoapps/views.py @@ -44,10 +44,7 @@ from geonode.people.forms import ProfileForm from geonode.base.forms import CategoryForm, TKeywordForm, ThesaurusAvailableForm -from geonode.base.models import ( - Thesaurus, - TopicCategory -) +from geonode.base.models import ExtraMetadata, Thesaurus, TopicCategory from geonode.utils import ( resolve_object, @@ -350,6 +347,7 @@ def geoapp_metadata(request, geoappid, template='apps/app_metadata.html', ajax=T else: geoapp_form = GeoAppForm(instance=geoapp_obj, prefix="resource") + geoapp_form.disable_keywords_widget_for_non_superuser(request.user) category_form = CategoryForm( prefix="category_choice_field", @@ -451,7 +449,17 @@ def geoapp_metadata(request, geoappid, template='apps/app_metadata.html', ajax=T geoapp_obj.regions.clear() geoapp_obj.regions.add(*new_regions) geoapp_obj.category = new_category + geoapp_obj.save(notify=True) + # clearing old metadata from the resource + geoapp_obj.metadata.all().delete() + # creating new metadata for the resource + for _m in json.loads(geoapp_form.cleaned_data['extra_metadata']): + new_m = ExtraMetadata.objects.create( + resource=geoapp_obj, + metadata=_m + ) + geoapp_obj.metadata.add(new_m) register_event(request, EventType.EVENT_CHANGE_METADATA, geoapp_obj) if not ajax: @@ -486,7 +494,18 @@ def geoapp_metadata(request, geoappid, template='apps/app_metadata.html', ajax=T logger.error(tb) return HttpResponse(json.dumps({'message': message})) - + elif request.method == "POST" and (not geoapp_form.is_valid( + ) or not category_form.is_valid() or not tkeywords_form.is_valid()): + errors_list = {**geoapp_form.errors.as_data(), **category_form.errors.as_data(), **tkeywords_form.errors.as_data()} + logger.error(f"GeoApp Metadata form is not valid: {errors_list}") + out = { + 'success': False, + "errors": [f"{x}: {y[0].messages[0]}" for x, y in errors_list.items()] + } + return HttpResponse( + json.dumps(out), + content_type='application/json', + status=400) # - POST Request Ends here - # Request.GET diff --git a/geonode/layers/api/views.py b/geonode/layers/api/views.py index 9cf70249387..8751af1df6f 100644 --- a/geonode/layers/api/views.py +++ b/geonode/layers/api/views.py @@ -22,7 +22,6 @@ from rest_framework.permissions import IsAuthenticatedOrReadOnly from rest_framework.authentication import SessionAuthentication, BasicAuthentication from oauth2_provider.contrib.rest_framework import OAuth2Authentication - from geonode.base.api.filters import DynamicSearchFilter, ExtentFilter from geonode.base.api.permissions import IsOwnerOrReadOnly from geonode.base.api.pagination import GeoNodeApiPagination diff --git a/geonode/layers/templates/layouts/panels.html b/geonode/layers/templates/layouts/panels.html index df5073d8503..4d2d6b6cbe3 100644 --- a/geonode/layers/templates/layouts/panels.html +++ b/geonode/layers/templates/layouts/panels.html @@ -544,6 +544,12 @@ {{ layer_form.spatial_representation_type }}
{% endblock layer_spatial_representation_type %} + {% block layer_extra_metadata %} +
+ + {{ layer_form.extra_metadata }} +
+ {% endblock layer_extra_metadata %}
diff --git a/geonode/layers/tests.py b/geonode/layers/tests.py index ac690b14deb..6ce7afbca35 100644 --- a/geonode/layers/tests.py +++ b/geonode/layers/tests.py @@ -65,7 +65,7 @@ from geonode.people.utils import get_valid_user from geonode.base.populate_test_data import all_public, create_single_layer from geonode.base.models import TopicCategory, License, Region, Link -from geonode.layers.forms import JSONField, LayerUploadForm +from geonode.layers.forms import JSONField, LayerForm, LayerUploadForm from geonode.utils import check_ogc_backend, set_resource_default_links from geonode.layers import LayersAppConfig from geonode.tests.utils import NotificationsTestsHelper @@ -1945,3 +1945,64 @@ def test_give_single_file_should_return_False(self): request.FILES['base_file'] = f actual = is_sld_upload_only(request) self.assertFalse(actual) + + +class TestLayerForm(GeoNodeBaseTestSupport): + def setUp(self) -> None: + self.user = get_user_model().objects.get(username='admin') + self.layer = create_single_layer("my_single_layer", owner=self.user) + self.sut = LayerForm + + def test_resource_form_is_invalid_extra_metadata_not_json_format(self): + self.client.login(username="admin", password="admin") + url = reverse("layer_metadata", args=(self.layer.alternate,)) + response = self.client.post(url, data={ + "resource-owner": self.layer.owner.id, + "resource-title": "layer_title", + "resource-date": "2022-01-24 16:38 pm", + "resource-date_type": "creation", + "resource-language": "eng", + "resource-extra_metadata": "not-a-json" + }) + expected = {"success": False, "errors": ["extra_metadata: The value provided for the Extra metadata field is not a valid JSON"]} + self.assertDictEqual(expected, response.json()) + + @override_settings(EXTRA_METADATA_SCHEMA={"key": "value"}) + def test_resource_form_is_invalid_extra_metadata_not_schema_in_settings(self): + self.client.login(username="admin", password="admin") + url = reverse("layer_metadata", args=(self.layer.alternate,)) + response = self.client.post(url, data={ + "resource-owner": self.layer.owner.id, + "resource-title": "layer_title", + "resource-date": "2022-01-24 16:38 pm", + "resource-date_type": "creation", + "resource-language": "eng", + "resource-extra_metadata": "[{'key': 'value'}]" + }) + expected = {"success": False, "errors": ["extra_metadata: EXTRA_METADATA_SCHEMA validation schema is not available for resource layer"]} + self.assertDictEqual(expected, response.json()) + + def test_resource_form_is_invalid_extra_metadata_invalids_schema_entry(self): + self.client.login(username="admin", password="admin") + url = reverse("layer_metadata", args=(self.layer.alternate,)) + response = self.client.post(url, data={ + "resource-owner": self.layer.owner.id, + "resource-title": "layer_title", + "resource-date": "2022-01-24 16:38 pm", + "resource-date_type": "creation", + "resource-language": "eng", + "resource-extra_metadata": '[{"key": "value"},{"id": "int", "filter_header": "object", "field_name": "object", "field_label": "object", "field_value": "object"}]' + }) + expected = "extra_metadata: Missing keys: \'field_label\', \'field_name\', \'field_value\', \'filter_header\' at index 0 " + self.assertIn(expected, response.json()['errors'][0]) + + def test_resource_form_is_valid_extra_metadata(self): + form = self.sut(instance=self.layer, data={ + "owner": self.layer.owner.id, + "title": "layer_title", + "date": "2022-01-24 16:38 pm", + "date_type": "creation", + "language": "eng", + "extra_metadata": '[{"id": 1, "filter_header": "object", "field_name": "object", "field_label": "object", "field_value": "object"}]' + }) + self.assertTrue(form.is_valid()) diff --git a/geonode/layers/views.py b/geonode/layers/views.py index fba87c58cf0..598e73feb7d 100644 --- a/geonode/layers/views.py +++ b/geonode/layers/views.py @@ -60,6 +60,7 @@ from geonode.base.forms import CategoryForm, TKeywordForm, BatchPermissionsForm, ThesaurusAvailableForm from geonode.base.views import batch_modify, get_url_for_model from geonode.base.models import ( + ExtraMetadata, Thesaurus, TopicCategory) from geonode.base.enumerations import CHARSETS @@ -853,8 +854,7 @@ def layer_metadata( logger.error(f"Layer Metadata form is not valid: {layer_form.errors}") out = { 'success': False, - 'errors': [ - re.sub(re.compile('<.*?>'), '', str(err)) for err in layer_form.errors] + "errors": [f"{x}: {y[0].messages[0]}" for x, y in layer_form.errors.as_data().items()] } return HttpResponse( json.dumps(out), @@ -1004,6 +1004,7 @@ def layer_metadata( la.visible = form["visible"] la.display_order = form["display_order"] la.featureinfo_type = form["featureinfo_type"] + la.save() if new_poc is not None or new_author is not None: @@ -1023,6 +1024,16 @@ def layer_metadata( layer.regions.add(*new_regions) layer.category = new_category + # clearing old metadata from the resource + layer.metadata.all().delete() + # creating new metadata for the resource + for _m in json.loads(layer_form.cleaned_data['extra_metadata']): + new_m = ExtraMetadata.objects.create( + resource=layer, + metadata=_m + ) + layer.metadata.add(new_m) + up_sessions = UploadSession.objects.filter(layer=layer) if up_sessions.count() > 0 and up_sessions[0].user != layer.owner: up_sessions.update(user=layer.owner) diff --git a/geonode/maps/templates/layouts/map_panels.html b/geonode/maps/templates/layouts/map_panels.html index 122ed38fd93..4cf5516befd 100644 --- a/geonode/maps/templates/layouts/map_panels.html +++ b/geonode/maps/templates/layouts/map_panels.html @@ -528,6 +528,12 @@ {{ map_form.spatial_representation_type }}
+ {% block map_extra_metadata %} +
+ + {{ map_form.extra_metadata }} +
+ {% endblock map_extra_metadata %}
diff --git a/geonode/maps/tests.py b/geonode/maps/tests.py index b987e7f362b..b7529b86fd7 100644 --- a/geonode/maps/tests.py +++ b/geonode/maps/tests.py @@ -30,6 +30,8 @@ from django.contrib.auth.models import Group from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType +from geonode.base.populate_test_data import create_single_map +from geonode.maps.forms import MapForm from geonode.maps.models import Map, MapLayer from geonode.settings import on_travis @@ -476,9 +478,17 @@ def test_that_non_admin_user_can_create_write_to_map_without_keyword(self): url = reverse('map_metadata', args=(test_map.pk,)) with self.settings(FREETEXT_KEYWORDS_READONLY=True): - response = self.client.post(url) + response = self.client.post(url, data={ + "resource-owner": self.not_admin.id, + "resource-title": "doc", + "resource-date": "2022-01-24 16:38 pm", + "resource-date_type": "creation", + "resource-language": "eng" + }) self.assertFalse(self.not_admin.is_superuser) self.assertEqual(response.status_code, 200) + test_map.refresh_from_db() + self.assertEqual("doc", test_map.title) def test_that_keyword_multiselect_is_enabled_for_non_admin_users_when_freetext_keywords_readonly_istrue(self): """ @@ -505,9 +515,18 @@ def test_that_non_admin_user_can_create_edit_keyword_when_freetext_keywords_read url = reverse('map_metadata', args=(test_map.pk,)) with self.settings(FREETEXT_KEYWORDS_READONLY=False): - response = self.client.post(url, data={'resource-keywords': 'wonderful-keyword'}) + response = self.client.post(url, data={ + "resource-owner": self.not_admin.id, + "resource-title": "map", + "resource-date": "2022-01-24 16:38 pm", + "resource-date_type": "creation", + "resource-language": "eng", + 'resource-keywords': 'wonderful-keyword' + }) self.assertFalse(self.not_admin.is_superuser) self.assertEqual(response.status_code, 200) + test_map.refresh_from_db() + self.assertEqual("map", test_map.title) @patch('geonode.thumbs.thumbnails.create_thumbnail') def test_map_metadata(self, thumbnail_mock): @@ -547,8 +566,15 @@ def test_map_metadata(self, thumbnail_mock): self.assertEqual(response.status_code, 200) # Now test with a valid user using POST method + user = get_user_model().objects.filter(username='admin').first() self.client.login(username=self.user, password=self.passwd) - response = self.client.post(url) + response = self.client.post(url, data={ + "resource-owner": user.id, + "resource-title": "map_title", + "resource-date": "2022-01-24 16:38 pm", + "resource-date_type": "creation", + "resource-language": "eng", + }) self.assertEqual(response.status_code, 200) # TODO: only invalid mapform is tested @@ -1098,3 +1124,64 @@ def testMapsNotifications(self): rating=5) rating.save() self.assertTrue(self.check_notification_out('map_rated', self.u)) + + +class TestMapForm(GeoNodeBaseTestSupport): + def setUp(self) -> None: + self.user = get_user_model().objects.get(username='admin') + self.map = create_single_map("single_map", owner=self.user) + self.sut = MapForm + + def test_resource_form_is_invalid_extra_metadata_not_json_format(self): + self.client.login(username="admin", password="admin") + url = reverse("map_metadata", args=(self.map.id,)) + response = self.client.post(url, data={ + "resource-owner": self.map.owner.id, + "resource-title": "map_title", + "resource-date": "2022-01-24 16:38 pm", + "resource-date_type": "creation", + "resource-language": "eng", + "resource-extra_metadata": "not-a-json" + }) + expected = {"success": False, "errors": ["extra_metadata: The value provided for the Extra metadata field is not a valid JSON"]} + self.assertDictEqual(expected, response.json()) + + @override_settings(EXTRA_METADATA_SCHEMA={"key": "value"}) + def test_resource_form_is_invalid_extra_metadata_not_schema_in_settings(self): + self.client.login(username="admin", password="admin") + url = reverse("map_metadata", args=(self.map.id,)) + response = self.client.post(url, data={ + "resource-owner": self.map.owner.id, + "resource-title": "map_title", + "resource-date": "2022-01-24 16:38 pm", + "resource-date_type": "creation", + "resource-language": "eng", + "resource-extra_metadata": "[{'key': 'value'}]" + }) + expected = {"success": False, "errors": ["extra_metadata: EXTRA_METADATA_SCHEMA validation schema is not available for resource map"]} + self.assertDictEqual(expected, response.json()) + + def test_resource_form_is_invalid_extra_metadata_invalids_schema_entry(self): + self.client.login(username="admin", password="admin") + url = reverse("map_metadata", args=(self.map.id,)) + response = self.client.post(url, data={ + "resource-owner": self.map.owner.id, + "resource-title": "map_title", + "resource-date": "2022-01-24 16:38 pm", + "resource-date_type": "creation", + "resource-language": "eng", + "resource-extra_metadata": '[{"key": "value"},{"id": "int", "filter_header": "object", "field_name": "object", "field_label": "object", "field_value": "object"}]' + }) + expected = "extra_metadata: Missing keys: \'field_label\', \'field_name\', \'field_value\', \'filter_header\' at index 0 " + self.assertIn(expected, response.json()['errors'][0]) + + def test_resource_form_is_valid_extra_metadata(self): + form = self.sut(data={ + "owner": self.map.owner.id, + "title": "map_title", + "date": "2022-01-24 16:38 pm", + "date_type": "creation", + "language": "eng", + "extra_metadata": '[{"id": 1, "filter_header": "object", "field_name": "object", "field_label": "object", "field_value": "object"}]' + }) + self.assertTrue(form.is_valid()) diff --git a/geonode/maps/views.py b/geonode/maps/views.py index 70c5650c2eb..9edcbef4520 100644 --- a/geonode/maps/views.py +++ b/geonode/maps/views.py @@ -59,6 +59,7 @@ from geonode.security.views import _perms_info_json from geonode.base.forms import CategoryForm, TKeywordForm, ThesaurusAvailableForm from geonode.base.models import ( + ExtraMetadata, Thesaurus, TopicCategory) from geonode import geoserver @@ -334,6 +335,16 @@ def map_metadata( map_obj.regions.add(*new_regions) map_obj.category = new_category + # clearing old metadata from the resource + map_obj.metadata.all().delete() + # creating new metadata for the resource + for _m in json.loads(map_form.cleaned_data['extra_metadata']): + new_m = ExtraMetadata.objects.create( + resource=map_obj, + metadata=_m + ) + map_obj.metadata.add(new_m) + register_event(request, EventType.EVENT_CHANGE_METADATA, map_obj) if not ajax: return HttpResponseRedirect( @@ -369,7 +380,18 @@ def map_metadata( map_obj.save(notify=True) return HttpResponse(json.dumps({'message': message})) - + elif request.method == "POST" and (not map_form.is_valid( + ) or not category_form.is_valid() or not tkeywords_form.is_valid()): + errors_list = {**map_form.errors.as_data(), **category_form.errors.as_data(), **tkeywords_form.errors.as_data()} + logger.error(f"GeoApp Metadata form is not valid: {errors_list}") + out = { + 'success': False, + "errors": [f"{x}: {y[0].messages[0]}" for x, y in errors_list.items()] + } + return HttpResponse( + json.dumps(out), + content_type='application/json', + status=400) # - POST Request Ends here - # Request.GET diff --git a/geonode/settings.py b/geonode/settings.py index 8853e062db6..63ff4c37f53 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -37,6 +37,7 @@ from kombu import Queue, Exchange from kombu.serialization import register +from schema import Optional from . import serializer SILENCED_SYSTEM_CHECKS = [ @@ -2163,3 +2164,34 @@ def get_geonode_catalogue_service(): DEFAULT_MAX_UPLOAD_SIZE = int(os.getenv('DEFAULT_MAX_UPLOAD_SIZE', 104857600)) # 100 MB DEFAULT_MAX_BEFORE_UPLOAD_SIZE = int(os.getenv('DEFAULT_MAX_BEFORE_UPLOAD_SIZE', 524288000)) # 500 MB + +''' +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), + "layer": 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} diff --git a/geonode/templates/metadata_form_js.html b/geonode/templates/metadata_form_js.html index 5e66f174d78..b4557a2c45b 100644 --- a/geonode/templates/metadata_form_js.html +++ b/geonode/templates/metadata_form_js.html @@ -16,6 +16,12 @@ $('#poc_form').modal(); } }); + var _area = $("#id_resource-extra_metadata").val() + if (_area) { + var areaAsObj = (0, eval)('(' + _area + ')'); + var _value = JSON.stringify(areaAsObj,null,2); + $("#id_resource-extra_metadata").val(_value); + } var metadata_uri = $(location).attr('pathname').split('/').pop(); var metadata_update_done = false; diff --git a/requirements.txt b/requirements.txt index 6b20bce60d5..f2b70a36d19 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,6 +29,7 @@ jsonfield==3.1.0 jsonschema==3.2.0 pyrsistent==0.17.3 zipstream-new==1.1.8 +schema==0.7.5 # Django Apps django-allauth==0.44.0 diff --git a/setup.cfg b/setup.cfg index 9ee168d3c23..1c41251e132 100644 --- a/setup.cfg +++ b/setup.cfg @@ -56,6 +56,7 @@ install_requires = jsonschema==3.2.0 pyrsistent==0.17.3 zipstream-new==1.1.8 + schema==0.7.5 # Django Apps django-allauth==0.44.0