Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIXES #12766] API for timeseries settings #12767

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions geonode/geoserver/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1808,6 +1808,37 @@
gs_catalog.save(resource)


def get_time_info(layer):
"""
Get the time configuration for a layer
"""
time_info = {}
gs_layer = gs_catalog.get_layer(name=layer.name)

Check warning on line 1816 in geonode/geoserver/helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/helpers.py#L1815-L1816

Added lines #L1815 - L1816 were not covered by tests
if gs_layer is not None:
gs_time_info = gs_layer.resource.metadata.get("time")

Check warning on line 1818 in geonode/geoserver/helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/helpers.py#L1818

Added line #L1818 was not covered by tests
if gs_time_info.enabled:
_attr = layer.attributes.filter(attribute=gs_time_info.attribute).first()
time_info["attribute"] = _attr.pk if _attr else None

Check warning on line 1821 in geonode/geoserver/helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/helpers.py#L1820-L1821

Added lines #L1820 - L1821 were not covered by tests
if gs_time_info.end_attribute is not None:
end_attr = layer.attributes.filter(attribute=gs_time_info.end_attribute).first()
time_info["end_attribute"] = end_attr.pk if end_attr else None
time_info["presentation"] = gs_time_info.presentation

Check warning on line 1825 in geonode/geoserver/helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/helpers.py#L1823-L1825

Added lines #L1823 - L1825 were not covered by tests
lookup_value = sorted(list(gs_time_info._lookup), key=lambda x: x[1], reverse=True)
if gs_time_info.resolution is not None:
res = gs_time_info.resolution // 1000

Check warning on line 1828 in geonode/geoserver/helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/helpers.py#L1828

Added line #L1828 was not covered by tests
for el in lookup_value:
if res % el[1] == 0:
time_info["precision_value"] = res // el[1]
time_info["precision_step"] = el[0]
break

Check warning on line 1833 in geonode/geoserver/helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/helpers.py#L1831-L1833

Added lines #L1831 - L1833 were not covered by tests
else:
time_info["precision_value"] = gs_time_info.resolution
time_info["precision_step"] = "seconds"
return time_info

Check warning on line 1837 in geonode/geoserver/helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/helpers.py#L1835-L1837

Added lines #L1835 - L1837 were not covered by tests
else:
return None

Check warning on line 1839 in geonode/geoserver/helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/helpers.py#L1839

Added line #L1839 was not covered by tests


ogc_server_settings = OGC_Servers_Handler(settings.OGC_SERVER)["default"]

_wms = None
Expand Down
87 changes: 86 additions & 1 deletion geonode/geoserver/tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,21 @@
from geonode.layers.populate_datasets_data import create_dataset_data
from geonode.tests.base import GeoNodeBaseTestSupport
from geonode.geoserver.views import _response_callback
from geonode.layers.models import Dataset, Attribute
from uuid import uuid4
from django.contrib.auth import get_user_model

Check warning on line 36 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L34-L36

Added lines #L34 - L36 were not covered by tests

from geonode.geoserver.helpers import (
gs_catalog,
ows_endpoint_in_path,
get_dataset_storetype,
extract_name_from_sld,
get_dataset_capabilities_url,
get_layer_ows_url,
get_time_info,
)
from geonode.geoserver.ows import _wcs_link, _wfs_link, _wms_link
from unittest.mock import patch, Mock

Check warning on line 48 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L48

Added line #L48 was not covered by tests


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -267,11 +273,90 @@

@on_ogc_backend(geoserver.BACKEND_PACKAGE)
def test_layer_ows_url(self):
from geonode.layers.models import Dataset

ows_url = settings.GEOSERVER_PUBLIC_LOCATION
identifier = "geonode:CA"
dataset = Dataset.objects.get(alternate=identifier)
expected_url = f"{ows_url}geonode/CA/ows"
capabilities_url = get_layer_ows_url(dataset)
self.assertEqual(capabilities_url, expected_url, capabilities_url)

# Tests for geonode.geoserver.helpers.get_time_info
@patch("geonode.geoserver.helpers.gs_catalog")
def test_get_time_info_valid_layer(self, mock_gs_catalog):

Check warning on line 286 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L285-L286

Added lines #L285 - L286 were not covered by tests

mock_dataset = Dataset.objects.create(

Check warning on line 288 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L288

Added line #L288 was not covered by tests
uuid=str(uuid4()),
owner=get_user_model().objects.get(username=self.user),
name="geonode:states",
store="httpfooremoteservce",
subtype="remote",
alternate="geonode:states",
)

Attribute.objects.create(pk=5, attribute="begin", dataset_id=mock_dataset.pk)

Check warning on line 297 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L297

Added line #L297 was not covered by tests

Attribute.objects.create(pk=6, attribute="end", dataset_id=mock_dataset.pk)

Check warning on line 299 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L299

Added line #L299 was not covered by tests

# Build mock GeoServer's time info
mock_gs_time_info = Mock()
mock_gs_time_info.enabled = True
mock_gs_time_info.attribute = "begin"
mock_gs_time_info.end_attribute = "end"
mock_gs_time_info.presentation = "DISCRETE_INTERVAL"
mock_gs_time_info.resolution = 5000
mock_gs_time_info._lookup = [("seconds", 1), ("minutes", 60)]

Check warning on line 308 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L302-L308

Added lines #L302 - L308 were not covered by tests

mock_gs_layer = Mock()
mock_gs_layer.resource.metadata.get.return_value = mock_gs_time_info
mock_gs_catalog.get_layer.return_value = mock_gs_layer

Check warning on line 312 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L310-L312

Added lines #L310 - L312 were not covered by tests

result = get_time_info(mock_dataset)

Check warning on line 314 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L314

Added line #L314 was not covered by tests

self.assertEqual(result["attribute"], 5)
self.assertEqual(result["end_attribute"], 6)
self.assertEqual(result["presentation"], "DISCRETE_INTERVAL")
self.assertEqual(result["precision_value"], 5)
self.assertEqual(result["precision_step"], "seconds")

Check warning on line 320 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L316-L320

Added lines #L316 - L320 were not covered by tests

@patch("geonode.geoserver.helpers.gs_catalog")
def test_get_time_info_with_time_disabled(self, mock_gs_catalog):

Check warning on line 323 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L322-L323

Added lines #L322 - L323 were not covered by tests

mock_dataset = Dataset.objects.create(

Check warning on line 325 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L325

Added line #L325 was not covered by tests
uuid=str(uuid4()),
owner=get_user_model().objects.get(username=self.user),
name="geonode:states",
store="httpfooremoteservce",
subtype="remote",
alternate="geonode:states",
)

Attribute.objects.create(pk=5, attribute="begin", dataset_id=mock_dataset.pk)

Check warning on line 334 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L334

Added line #L334 was not covered by tests

Attribute.objects.create(pk=6, attribute="end", dataset_id=mock_dataset.pk)

Check warning on line 336 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L336

Added line #L336 was not covered by tests

mock_gs_time_info = Mock()
mock_gs_time_info.enabled = False
mock_gs_time_info.attribute = "begin"
mock_gs_time_info.end_attribute = "end"
mock_gs_time_info.presentation = "DISCRETE_INTERVAL"
mock_gs_time_info.resolution = 10000
mock_gs_time_info._lookup = [("seconds", 1), ("minutes", 60)]

Check warning on line 344 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L338-L344

Added lines #L338 - L344 were not covered by tests

mock_gs_layer = Mock()
mock_gs_layer.resource.metadata.get.return_value = mock_gs_time_info
mock_gs_catalog.get_layer.return_value = mock_gs_layer

Check warning on line 348 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L346-L348

Added lines #L346 - L348 were not covered by tests

result = get_time_info(mock_dataset)
self.assertEqual(result, {})

Check warning on line 351 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L350-L351

Added lines #L350 - L351 were not covered by tests

@patch("geonode.geoserver.helpers.gs_catalog")
def test_get_time_info_no_layer(self, mock_gs_catalog):

Check warning on line 354 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L353-L354

Added lines #L353 - L354 were not covered by tests

mock_gs_catalog.get_layer.return_value = None

Check warning on line 356 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L356

Added line #L356 was not covered by tests

mock_layer = Mock()
mock_layer.name = "nonexistent_layer"

Check warning on line 359 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L358-L359

Added lines #L358 - L359 were not covered by tests

result = get_time_info(mock_layer)
self.assertIsNone(result)

Check warning on line 362 in geonode/geoserver/tests/test_helpers.py

View check run for this annotation

Codecov / codecov/patch

geonode/geoserver/tests/test_helpers.py#L361-L362

Added lines #L361 - L362 were not covered by tests
37 changes: 37 additions & 0 deletions geonode/layers/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,40 @@

class Meta:
fields = "metadata_file"


class DatasetTimeSeriesSerializer(serializers.Serializer):

def __init__(self, *args, **kwargs):

super().__init__(*args, **kwargs)

Check warning on line 222 in geonode/layers/api/serializers.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/serializers.py#L222

Added line #L222 was not covered by tests

layer = self.context.get("layer")

Check warning on line 224 in geonode/layers/api/serializers.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/serializers.py#L224

Added line #L224 was not covered by tests

if layer:
# use the get_choices method of the Dataset model
choices = [(None, "-----")] + layer.get_choices
self.fields["attribute"].choices = choices
self.fields["end_attribute"].choices = choices

Check warning on line 230 in geonode/layers/api/serializers.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/serializers.py#L228-L230

Added lines #L228 - L230 were not covered by tests
else:
choices = [(None, "-----")]

Check warning on line 232 in geonode/layers/api/serializers.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/serializers.py#L232

Added line #L232 was not covered by tests

has_time = serializers.BooleanField(default=False)
attribute = serializers.ChoiceField(choices=[], required=False)
end_attribute = serializers.ChoiceField(choices=[], required=False)
presentation = serializers.ChoiceField(
required=False,
choices=[
("LIST", "List of all the distinct time values"),
("DISCRETE_INTERVAL", "Intervals defined by the resolution"),
(
"CONTINUOUS_INTERVAL",
"Continuous Intervals for data that is frequently updated, resolution describes the frequency of updates",
),
],
)
precision_value = serializers.IntegerField(required=False)
precision_step = serializers.ChoiceField(
required=False,
choices=[("years",) * 2, ("months",) * 2, ("days",) * 2, ("hours",) * 2, ("minutes",) * 2, ("seconds",) * 2],
)
106 changes: 106 additions & 0 deletions geonode/layers/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,25 @@
from geonode.resource.utils import update_resource
from geonode.resource.manager import resource_manager
from rest_framework.exceptions import NotFound
from django.shortcuts import get_object_or_404
from django.http import JsonResponse

from geonode.storage.manager import StorageManager

from .serializers import (
DatasetSerializer,
DatasetListSerializer,
DatasetMetadataSerializer,
DatasetTimeSeriesSerializer,
)
from .permissions import DatasetPermissionsFilter

from geonode import geoserver
from geonode.utils import check_ogc_backend

if check_ogc_backend(geoserver.BACKEND_PACKAGE):
from geonode.geoserver.helpers import get_time_info

import logging

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -80,6 +89,8 @@
def get_serializer_class(self):
if self.action == "list":
return DatasetListSerializer
if self.action == "timeseries_info":
return DatasetTimeSeriesSerializer

Check warning on line 93 in geonode/layers/api/views.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/views.py#L93

Added line #L93 was not covered by tests
return DatasetSerializer

def partial_update(self, request, *args, **kwargs):
Expand Down Expand Up @@ -187,3 +198,98 @@
dataset = self.get_object()
resources = dataset.maps
return Response(SimpleMapSerializer(many=True).to_representation(resources))

@action(
detail=True,
url_path="timeseries",
url_name="timeseries",
methods=["get", "put"],
permission_classes=[IsAuthenticated],
)
def timeseries_info(self, request, pk, *args, **kwards):
"""
Endpoint for timeseries information

url = "http://localhost:8080/api/v2/datasets/{dataset_id}/timeseries"

cURL examples:
GET method
curl -X GET http://localhost:8000/api/v2/datasets/1/timeseries -u <username>:<password>

PUT method
curl -X PUT http://localhost:8000/api/v2/datasets/1/timeseries -u <username>:<password>
-H "Content-Type: application/json" -d '{"has_time": true, "attribute": 4, "end_attribute": 5,
"presentation": "DISCRETE_INTERVAL", "precision_value": 2, "precision_step": "months"}'
"""

layer = get_object_or_404(Dataset, id=pk)

Check warning on line 225 in geonode/layers/api/views.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/views.py#L225

Added line #L225 was not covered by tests

if layer.supports_time is False:
return JsonResponse({"message": "The time dimension is not supported for this dataset."}, status=200)

Check warning on line 228 in geonode/layers/api/views.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/views.py#L228

Added line #L228 was not covered by tests

if request.method == "GET":

time_info = get_time_info(layer)
serializer = DatasetTimeSeriesSerializer(data=time_info, context={"layer": layer})
serializer.is_valid(raise_exception=True)
serialized_time_info = serializer.data

Check warning on line 235 in geonode/layers/api/views.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/views.py#L232-L235

Added lines #L232 - L235 were not covered by tests

if layer.has_time is True and time_info is not None:
serialized_time_info["has_time"] = layer.has_time
return JsonResponse(serialized_time_info, status=200)

Check warning on line 239 in geonode/layers/api/views.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/views.py#L238-L239

Added lines #L238 - L239 were not covered by tests
else:
return JsonResponse({"message": "No time information available."}, status=404)

Check warning on line 241 in geonode/layers/api/views.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/views.py#L241

Added line #L241 was not covered by tests

if request.method == "PUT":

serializer = DatasetTimeSeriesSerializer(data=request.data, context={"layer": layer})
serializer.is_valid(raise_exception=True)
serialized_time_info = serializer.validated_data

Check warning on line 247 in geonode/layers/api/views.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/views.py#L245-L247

Added lines #L245 - L247 were not covered by tests

if serialized_time_info.get("has_time") is True:

start_attr = (

Check warning on line 251 in geonode/layers/api/views.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/views.py#L251

Added line #L251 was not covered by tests
layer.attributes.get(pk=serialized_time_info.get("attribute")).attribute
if serialized_time_info.get("attribute")
else None
)
end_attr = (

Check warning on line 256 in geonode/layers/api/views.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/views.py#L256

Added line #L256 was not covered by tests
layer.attributes.get(pk=serialized_time_info.get("end_attribute")).attribute
if serialized_time_info.get("end_attribute")
else None
)

# Save the has_time value to the database
layer.has_time = True
layer.save()

Check warning on line 264 in geonode/layers/api/views.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/views.py#L263-L264

Added lines #L263 - L264 were not covered by tests

resource_manager.exec(

Check warning on line 266 in geonode/layers/api/views.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/views.py#L266

Added line #L266 was not covered by tests
"set_time_info",
None,
instance=layer,
time_info={
"attribute": start_attr,
"end_attribute": end_attr,
"presentation": serialized_time_info.get("presentation", None),
"precision_value": serialized_time_info.get("precision_value", None),
"precision_step": serialized_time_info.get("precision_step", None),
"enabled": serialized_time_info.get("has_time", False),
},
)

resource_manager.update(

Check warning on line 280 in geonode/layers/api/views.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/views.py#L280

Added line #L280 was not covered by tests
layer.uuid,
instance=layer,
notify=True,
)
return JsonResponse({"message": "the time information data was updated successfully"}, status=200)

Check warning on line 285 in geonode/layers/api/views.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/views.py#L285

Added line #L285 was not covered by tests
else:
# Save the has_time value to the database
layer.has_time = False
layer.save()

Check warning on line 289 in geonode/layers/api/views.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/views.py#L288-L289

Added lines #L288 - L289 were not covered by tests

return JsonResponse(

Check warning on line 291 in geonode/layers/api/views.py

View check run for this annotation

Codecov / codecov/patch

geonode/layers/api/views.py#L291

Added line #L291 was not covered by tests
{
"message": "The time information was not updated since the time dimension is disabled for this layer"
}
)
15 changes: 15 additions & 0 deletions geonode/layers/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,21 @@ def is_vector(self):
def is_raster(self):
return self.subtype == "raster"

@property
def supports_time(self):
valid_attributes = self.get_choices
# check if the layer object if a vector and
# includes valid_attributes
if self.is_vector() and valid_attributes:
return True
return False

@property
def get_choices(self):

attributes = Attribute.objects.filter(dataset_id=self.pk)
return [(_a.pk, _a.attribute) for _a in attributes if _a.attribute_type in ["xsd:dateTime", "xsd:date"]]

@property
def display_type(self):
if self.subtype in ["vector", "vector_time"]:
Expand Down
Loading
Loading