Skip to content
Merged
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
24 changes: 24 additions & 0 deletions src/dso_api/dynamic_api/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@
DatasetMVTView,
DatasetTileJSONView,
DatasetWFSView,
OGCRecordDetailView,
OGCRecordsCollectionDetailView,
OGCRecordsCollectionItemsView,
OGCRecordsCollectionView,
viewset_factory,
)
Expand Down Expand Up @@ -580,6 +582,28 @@ def _build_ogc_views(self, datasets: Iterable[Dataset]) -> list[URLPattern]:
},
)
)
results.append(
path(
f"/ogc/records/collections/{dataset.path}/items",
OGCRecordsCollectionItemsView.as_view(),
name="ogc-collections-items",
kwargs={
"dataset_name": dataset_id,
},
)
)
for version, _ in dataset.schema.versions.items():
results.append(
path(
f"/ogc/records/collections/{dataset.path}/items/{dataset_id}_{version}",
OGCRecordDetailView.as_view(),
name="ogc-record",
kwargs={
"dataset_name": dataset_id,
"dataset_version": version,
},
)
)

return results

Expand Down
4 changes: 4 additions & 0 deletions src/dso_api/dynamic_api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
from .oauth import oauth2_redirect
from .ogc import (
OGCIndexView,
OGCRecordDetailView,
OGCRecordsCollectionDetailView,
OGCRecordsCollectionItemsView,
OGCRecordsCollectionView,
OGCRecordsIndexView,
)
Expand All @@ -27,6 +29,8 @@
"OGCRecordsIndexView",
"OGCRecordsCollectionView",
"OGCRecordsCollectionDetailView",
"OGCRecordsCollectionItemsView",
"OGCRecordDetailView",
"DocsIndexView",
"oauth2_redirect",
"viewset_factory",
Expand Down
141 changes: 137 additions & 4 deletions src/dso_api/dynamic_api/views/ogc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging

from django.utils.functional import cached_property
from django.views.generic import TemplateView
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
Expand Down Expand Up @@ -48,6 +49,7 @@ class OGCRecordsCollectionView(APIView):
renderer_classes = [JSONRenderer]
name = "Record Collections"

@cached_property
def get_datasets(self):
return [
ds
Expand All @@ -57,7 +59,7 @@ def get_datasets(self):

def get(self, request, *args, **kwargs):
base = request.build_absolute_uri("/").rstrip("/")
datasets = self.get_datasets()
datasets = self.get_datasets

collections = []

Expand All @@ -81,11 +83,12 @@ def get(self, request, *args, **kwargs):


class OGCRecordsCollectionDetailView(APIView):
"""An JSON page for a single OGC record collection (dataset)."""
"""A JSON page for a single OGC record collection (dataset)."""

renderer_classes = [JSONRenderer]
name = "Records"

@cached_property
def get_datasets(self):
return [
ds
Expand All @@ -95,7 +98,7 @@ def get_datasets(self):

def get(self, request, *args, **kwargs):
base = request.build_absolute_uri("/").rstrip("/")
datasets = self.get_datasets()
datasets = self.get_datasets
dataset_name = self.kwargs.get("dataset_name")
collection_url = f"{base}/v1/ogc/records/collections/{dataset_name}"
records = []
Expand All @@ -110,13 +113,14 @@ def get(self, request, *args, **kwargs):
self.build_record_collection(
ds=ds,
version=version,
base=base,
collection_url=collection_url,
)
)

return Response({"records": records})

def build_record_collection(self, ds, version, collection_url):
def build_record_collection(self, ds, version, base, collection_url):
"""Handle missing attributes and ensure each OGC field has the expected datatype."""
metadata = {
"id": f"{ds.schema.id}_{version}",
Expand All @@ -137,6 +141,16 @@ def build_record_collection(self, ds, version, collection_url):
],
}

for version, _ in ds.schema.versions.items():
metadata["links"].append(
{
"rel": f"{version}",
"type": "application/json",
"href": f"{base}/v1/ogc/records/collections/{ds.schema.id}/items"
f"/{ds.schema.id}_{version}",
}
)

if ds.schema.get("dateCreated"):
metadata["created"] = ds.schema.get("dateCreated")

Expand All @@ -158,3 +172,122 @@ def build_record_collection(self, ds, version, collection_url):
metadata["license"] = ds.schema.license

return [metadata]


class OGCRecordsCollectionItemsView(APIView):
"""Shows a JSON page with an overview of record collection items."""

renderer_classes = [JSONRenderer]
name = "Collection Items"

@cached_property
def get_datasets(self):
return [
ds
for ds in get_active_datasets().db_enabled().order_by("name")
if ds.has_geometry_fields
]

def get(self, request, *args, **kwargs):
base = request.build_absolute_uri("/").rstrip("/")
datasets = self.get_datasets
dataset_name = self.kwargs.get("dataset_name")

ds = next(
(dataset for dataset in datasets if str(dataset.schema.id) == str(dataset_name)),
None,
)

items = []

for version, _ in ds.schema.versions.items():
items.append(
{
"id": f"{ds.schema.id}_{version}",
"title": ds.schema.title or ds.name,
"description": ds.schema.description or "",
"links": [
{
"rel": "self",
"type": "application/json",
"href": (f"{base}/v1/ogc/records/collections/{ds.schema.id}/items"),
},
{
"rel": f"{version}",
"type": "application/json",
"href": f"{base}/v1/ogc/records/collections/{ds.schema.id}/items"
f"/{ds.schema.id}_{version}",
},
],
}
)

return Response({"items": items})


class OGCRecordDetailView(APIView):
"""A JSON page for a single OGC record (datasetversion)."""

renderer_classes = [JSONRenderer]
name = "Record"

@cached_property
def get_datasets(self):
Comment thread
lotte-amsterdam marked this conversation as resolved.
Resolved
return [
ds
for ds in get_active_datasets().db_enabled().order_by("name")
if ds.has_geometry_fields
]

def get(self, request, *args, **kwargs):
base = request.build_absolute_uri("/").rstrip("/")
datasets = self.get_datasets
dataset_name = self.kwargs.get("dataset_name")
dataset_version = self.kwargs.get("dataset_version")
record_url = (
f"{base}/v1/ogc/records/collections/{dataset_name}/items"
f"/{dataset_name}_{dataset_version}"
)

wfs_url = (
f"{base}/v1/wfs/{dataset_name}/{dataset_version}?service=WFS&request=GetCapabilities"
)

ds = next(
(dataset for dataset in datasets if str(dataset.schema.id) == str(dataset_name)),
None,
)

record = self.build_record(
ds=ds,
version=dataset_version,
record_url=record_url,
wfs_url=wfs_url,
)

return Response({"record": record})

def build_record(self, ds, version, record_url, wfs_url):
"""Handle missing attributes and ensure each OGC field has the expected datatype."""
metadata = {
"id": f"{ds.schema.id}_{version}",
"conformsTo": [],
"language": {"code": "nl", "name": "Dutch (Netherlands)"},
"links": [
{
"rel": "self",
"type": "application/json",
"href": record_url,
},
{
"rel": "WFS",
"type": "application/json",
"href": wfs_url,
},
],
}

if ds.schema.get("dateCreated"):
metadata["created"] = ds.schema.get("dateCreated")

return metadata
2 changes: 1 addition & 1 deletion src/templates/dso_api/dynamic_api/ogc_records.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
op een gestandaardiseerde manier beschikbaar te stellen. Iedere DSO-dataset wordt ontsloten als een
record collection, met metadata over deze dataset uit het Amsterdam Schema en de beschikbare datasetversies.
Iedere datasetversie wordt vervolgens beschreven door een afzonderlijk OGC-record met de bijbehorende
metadata en links naar de relevante WFS- en WMS-services. Hierdoor kunnen kaartviewers zoals NL3D en
metadata en links naar de relevante WFS-services. Hierdoor kunnen kaartviewers zoals NL3D en
Geosquare deze datasets en datasetversies op een uniforme manier ontdekken en de bijbehorende geografische
gegevens raadplegen.
</p>
Expand Down
59 changes: 59 additions & 0 deletions src/tests/test_dynamic_api/views/test_ogc.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ def test_ogc_view(self, api_client, afval_container):
"type": "application/json",
"href": "http://testserver/v1/ogc/records/collections/afvalwegingen",
},
{
"rel": "v1",
"type": "application/json",
"href": "http://testserver/v1/ogc/records/collections/afvalwegingen/items/afvalwegingen_v1",
},
{
"rel": "v2",
"type": "application/json",
"href": "http://testserver/v1/ogc/records/collections/afvalwegingen/items/afvalwegingen_v2",
},
],
"contacts": [
{
Expand Down Expand Up @@ -90,6 +100,16 @@ def test_ogc_view(self, api_client, afval_container):
"type": "application/json",
"href": "http://testserver/v1/ogc/records/collections/afvalwegingen",
},
{
"rel": "v1",
"type": "application/json",
"href": "http://testserver/v1/ogc/records/collections/afvalwegingen/items/afvalwegingen_v1",
},
{
"rel": "v2",
"type": "application/json",
"href": "http://testserver/v1/ogc/records/collections/afvalwegingen/items/afvalwegingen_v2",
},
],
"contacts": [
{
Expand All @@ -104,3 +124,42 @@ def test_ogc_view(self, api_client, afval_container):
},
],
}


@pytest.mark.django_db
class TestOGCRecordsCollectionItemsView:
def test_ogc_items_view(self, api_client, afval_container):
ogc_url = "/v1/ogc/records/collections/afvalwegingen/items"
response = api_client.get(ogc_url)
json = response.json()
assert response.status_code == 200, response.content
assert len(json["items"]) == 2
assert json["items"][0]["id"] == "afvalwegingen_v1"
assert json["items"][1]["id"] == "afvalwegingen_v2"


@pytest.mark.django_db
class TestOGCRecordDetailView:
def test_ogc_record_view(self, api_client, afval_container):
ogc_url = "/v1/ogc/records/collections/afvalwegingen/items/afvalwegingen_v1"
response = api_client.get(ogc_url)
assert response.status_code == 200, response.content
assert response.data == {
"record": {
"id": "afvalwegingen_v1",
"conformsTo": [],
"language": {"code": "nl", "name": "Dutch (Netherlands)"},
"links": [
{
"rel": "self",
"type": "application/json",
"href": "http://testserver/v1/ogc/records/collections/afvalwegingen/items/afvalwegingen_v1",
},
{
"rel": "WFS",
"type": "application/json",
"href": "http://testserver/v1/wfs/afvalwegingen/v1?service=WFS&request=GetCapabilities",
},
],
},
}
Loading