Skip to content

Commit

Permalink
Merge branch 'metadata_manager' of https://github.com/GeoNode/geonode
Browse files Browse the repository at this point in the history
…into metadata_manager
  • Loading branch information
Gpetrak committed Dec 20, 2024
2 parents a7dcf0a + aef2940 commit 0aa4d4e
Show file tree
Hide file tree
Showing 12 changed files with 289 additions and 86 deletions.
6 changes: 4 additions & 2 deletions geonode/base/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1529,9 +1529,11 @@ def base_linked_resources_payload(instance, user, params={}):
lres = base_linked_resources_instances(instance, user, params)
ret = {
"linked_to": LinkedResourceSerializer(lres["linked_to"], embed=True, many=True).data,
"linked_by": LinkedResourceSerializer(instance=lres["linked_by"], serialize_source=True, embed=True, many=True).data
"linked_by": LinkedResourceSerializer(
instance=lres["linked_by"], serialize_source=True, embed=True, many=True
).data,
}
if lres.get("WARNINGS", None):
ret["WARNINGS"] = lres["WARNINGS"]

return ret
return ret
10 changes: 10 additions & 0 deletions geonode/metadata/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,15 @@
MetadataGroupAutocomplete.as_view(),
name="metadata_autocomplete_groups",
),
path(
r"metadata/autocomplete/categories",
views.categories_autocomplete,
name="metadata_autocomplete_categories",
),
path(
r"metadata/autocomplete/licenses",
views.licenses_autocomplete,
name="metadata_autocomplete_licenses",
),
# path(r"metadata/autocomplete/users", login_required(ProfileAutocomplete.as_view()), name="metadata_autocomplete_users"),
]
65 changes: 61 additions & 4 deletions geonode/metadata/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import logging

from dal import autocomplete
from oauth2_provider.contrib.rest_framework import OAuth2Authentication
from rest_framework.authentication import BasicAuthentication, SessionAuthentication
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.viewsets import ViewSet
from rest_framework.decorators import action
from rest_framework.response import Response
Expand All @@ -27,10 +30,11 @@
from django.core.handlers.wsgi import WSGIRequest
from django.http import JsonResponse
from django.utils.translation.trans_real import get_language_from_request
from django.utils.translation import get_language
from django.utils.translation import get_language, gettext as _
from django.db.models import Q

from geonode.base.models import ResourceBase, ThesaurusKeyword, ThesaurusKeywordLabel
from geonode.base.api.permissions import UserHasPerms
from geonode.base.models import ResourceBase, ThesaurusKeyword, ThesaurusKeywordLabel, TopicCategory, License
from geonode.base.utils import remove_country_from_languagecode
from geonode.base.views import LinkedResourcesAutocomplete, RegionAutocomplete, HierarchicalKeywordAutocomplete
from geonode.groups.models import GroupProfile
Expand All @@ -41,6 +45,9 @@


class MetadataViewSet(ViewSet):
authentication_classes = [SessionAuthentication, BasicAuthentication, OAuth2Authentication]
permission_classes = [IsAuthenticatedOrReadOnly, UserHasPerms]

"""
Simple viewset that return the metadata JSON schema
"""
Expand Down Expand Up @@ -71,8 +78,22 @@ def schema(self, request, pk=None):

# Get the JSON schema
@action(detail=False, methods=["get", "put", "patch"], url_path=r"instance/(?P<pk>\d+)", url_name="schema_instance")
@action(
detail=False,
methods=["get", "put", "patch"],
url_path=r"instance/(?P<pk>\d+)",
permission_classes=[
UserHasPerms(
perms_dict={
"default": {
"GET": ["base.view_resourcebase"],
"POST": ["change_resourcebase_metadata"],
}
}
)
],
)
def schema_instance(self, request, pk=None):

try:
resource = ResourceBase.objects.get(pk=pk)

Expand All @@ -83,7 +104,7 @@ def schema_instance(self, request, pk=None):
schema_instance, content_type="application/schema-instance+json", json_dumps_params={"indent": 3}
)

elif request.method in ("PUT", "PATCH"):
elif request.method in ("PUT"):
logger.debug(f"handling request {request.method}")
# try:
# logger.debug(f"handling content {json.dumps(request.data, indent=3)}")
Expand Down Expand Up @@ -146,6 +167,42 @@ def tkeywords_autocomplete(request: WSGIRequest, thesaurusid):
return JsonResponse({"results": ret})


def categories_autocomplete(request: WSGIRequest):
qs = TopicCategory.objects.order_by("gn_description")

if q := request.GET.get("q", None):
qs = qs.filter(gn_description__istartswith=q)

ret = []
for record in qs.all():
ret.append(
{
"id": record.identifier,
"label": _(record.gn_description),
}
)

return JsonResponse({"results": ret})


def licenses_autocomplete(request: WSGIRequest):
qs = License.objects.order_by("name")

if q := request.GET.get("q", None):
qs = qs.filter(name__istartswith=q)

ret = []
for record in qs.all():
ret.append(
{
"id": record.identifier,
"label": _(record.name),
}
)

return JsonResponse({"results": ret})


class ProfileAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if self.request and self.request.user:
Expand Down
14 changes: 13 additions & 1 deletion geonode/metadata/handlers/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def _add_after(self, jsonschema, after_what, property_name, subschema):

@staticmethod
def _set_error(errors: dict, path: list, msg: str):
logger.warning(f"Reported message: {'/'.join(path)}: {msg} ")
logger.info(f"Reported message: {'/'.join(path)}: {msg} ")
elem = errors
for step in path:
elem = elem.setdefault(step, {})
Expand All @@ -135,3 +135,15 @@ def _localize_label(context, lang: str, text: str):
def _localize_subschema_label(context, subschema: dict, lang: str, annotation_name: str):
if annotation_name in subschema:
subschema[annotation_name] = MetadataHandler._localize_label(context, lang, subschema[annotation_name])


class MetadataFieldException(Exception):
pass


class UnsetFieldException(MetadataFieldException):
pass


class UnparsableFieldException(MetadataFieldException):
pass
48 changes: 28 additions & 20 deletions geonode/metadata/handlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@
import logging
from datetime import datetime

from rest_framework.reverse import reverse
from django.utils.translation import gettext as _

from geonode.base.models import TopicCategory, License, RestrictionCodeType, SpatialRepresentationType
from geonode.metadata.handlers.abstract import MetadataHandler
from geonode.metadata.settings import JSONSCHEMA_BASE
from geonode.base.enumerations import ALL_LANGUAGES, UPDATE_FREQUENCIES
from django.utils.translation import gettext as _


logger = logging.getLogger(__name__)
Expand All @@ -48,21 +50,23 @@ def deserialize(cls, field_value):
class CategorySubHandler(SubHandler):
@classmethod
def update_subschema(cls, subschema, lang=None):
# subschema["title"] = _("topiccategory")
subschema["oneOf"] = [
{"const": tc.identifier, "title": _(tc.gn_description), "description": _(tc.description)}
for tc in TopicCategory.objects.order_by("gn_description")
]
subschema["ui:options"] = {
"geonode-ui:autocomplete": reverse("metadata_autocomplete_categories"),
}

@classmethod
def serialize(cls, db_value):
if isinstance(db_value, TopicCategory):
return db_value.identifier
return db_value
if db_value is None:
return None
elif isinstance(db_value, TopicCategory):
return {"id": db_value.identifier, "label": _(db_value.gn_description)}
else:
logger.warning(f"Category: can't decode <{type(db_value)}>'{db_value}'")
return None

@classmethod
def deserialize(cls, field_value):
return TopicCategory.objects.get(identifier=field_value)
return TopicCategory.objects.get(identifier=field_value["id"]) if field_value else None


class DateTypeSubHandler(SubHandler):
Expand Down Expand Up @@ -95,20 +99,23 @@ def update_subschema(cls, subschema, lang=None):
class LicenseSubHandler(SubHandler):
@classmethod
def update_subschema(cls, subschema, lang=None):
subschema["oneOf"] = [
{"const": tc.identifier, "title": tc.name, "description": tc.description}
for tc in License.objects.order_by("name")
]
subschema["ui:options"] = {
"geonode-ui:autocomplete": reverse("metadata_autocomplete_licenses"),
}

@classmethod
def serialize(cls, db_value):
if isinstance(db_value, License):
return db_value.identifier
return db_value
if db_value is None:
return None
elif isinstance(db_value, License):
return {"id": db_value.identifier, "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)
return License.objects.get(identifier=field_value["id"]) if field_value else None


class RestrictionsSubHandler(SubHandler):
Expand All @@ -127,7 +134,7 @@ def serialize(cls, db_value):

@classmethod
def deserialize(cls, field_value):
return RestrictionCodeType.objects.get(identifier=field_value)
return RestrictionCodeType.objects.get(identifier=field_value) if field_value else None


class SpatialRepresentationTypeSubHandler(SubHandler):
Expand All @@ -146,7 +153,7 @@ def serialize(cls, db_value):

@classmethod
def deserialize(cls, field_value):
return SpatialRepresentationType.objects.get(identifier=field_value)
return SpatialRepresentationType.objects.get(identifier=field_value) if field_value else None


SUBHANDLERS = {
Expand Down Expand Up @@ -215,3 +222,4 @@ def update_resource(self, resource, field_name, json_instance, context, errors,
setattr(resource, field_name, field_value)
except Exception as e:
logger.warning(f"Error setting field {field_name}={field_value}: {e}")
self._set_error(errors, [field_name], "Error while storing field. Contact your administrator")
34 changes: 26 additions & 8 deletions geonode/metadata/handlers/contact.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@

logger = logging.getLogger(__name__)

# contact roles names are spread in the code, let's map them here:
ROLE_NAMES_MAP = {
Roles.OWNER: "owner", # this is not saved as a contact
Roles.METADATA_AUTHOR: "author",
Roles.PROCESSOR: Roles.PROCESSOR.name,
Roles.PUBLISHER: Roles.PUBLISHER.name,
Roles.CUSTODIAN: Roles.CUSTODIAN.name,
Roles.POC: "pointOfContact",
Roles.DISTRIBUTOR: Roles.DISTRIBUTOR.name,
Roles.RESOURCE_USER: Roles.RESOURCE_USER.name,
Roles.RESOURCE_PROVIDER: Roles.RESOURCE_PROVIDER.name,
Roles.ORIGINATOR: Roles.ORIGINATOR.name,
Roles.PRINCIPAL_INVESTIGATOR: Roles.PRINCIPAL_INVESTIGATOR.name,
}

NAMES_ROLE_MAP = {v: k for k, v in ROLE_NAMES_MAP.items()}


class ContactHandler(MetadataHandler):
"""
Expand All @@ -38,10 +55,11 @@ def update_schema(self, jsonschema, context, lang=None):
contacts = {}
required = []
for role in Roles:
rolename = ROLE_NAMES_MAP[role]
minitems = 1 if role.is_required else 0
card = f'[{minitems}..{"N" if role.is_multivalue else "1"}]'
if role.is_required:
required.append(role.name)
required.append(rolename)

if role.is_multivalue:
contact = {
Expand Down Expand Up @@ -82,7 +100,7 @@ def update_schema(self, jsonschema, context, lang=None):
"required": ["id"] if role.is_required else [],
}

contacts[role.name] = contact
contacts[rolename] = contact

jsonschema["properties"]["contacts"] = {
"type": "object",
Expand All @@ -103,30 +121,30 @@ def __create_user_entry(user):

contacts = {}
for role in Roles:
rolename = ROLE_NAMES_MAP[role]
if role.is_multivalue:
content = [__create_user_entry(user) for user in resource.__get_contact_role_elements__(role) or []]
content = [__create_user_entry(user) for user in resource.__get_contact_role_elements__(rolename) or []]
else:
users = resource.__get_contact_role_elements__(role)
users = resource.__get_contact_role_elements__(rolename)
if not users and role == Roles.OWNER:
users = [resource.owner]
content = __create_user_entry(users[0]) if users else None

contacts[role.name] = content
contacts[rolename] = content

return contacts

def update_resource(self, resource, field_name, json_instance, context, errors, **kwargs):
data = json_instance[field_name]
logger.debug(f"CONTACTS {data}")
for rolename, users in data.items():
if rolename == Roles.OWNER.OWNER.name:
if rolename == Roles.OWNER.name:
if not users:
logger.warning(f"User not specified for role '{rolename}'")
self._set_error(errors, ["contacts", rolename], f"User not specified for role '{rolename}'")
else:
resource.owner = get_user_model().objects.get(pk=users["id"])
else:
role = Roles.get_role_by_name(rolename)
ids = [u["id"] for u in users]
profiles = get_user_model().objects.filter(pk__in=ids)
resource.__set_contact_role_element__(profiles, role)
resource.__set_contact_role_element__(profiles, rolename)
2 changes: 1 addition & 1 deletion geonode/metadata/handlers/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def get_jsonschema_instance(self, resource, field_name, context, errors, lang=No
)

def update_resource(self, resource, field_name, json_instance, context, errors, **kwargs):
data = json_instance[field_name]
data = json_instance.get(field_name, None)
id = data.get("id", None) if data else None
if id is not None:
gp = GroupProfile.objects.get(pk=id)
Expand Down
Loading

0 comments on commit 0aa4d4e

Please sign in to comment.