Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
edbf8f0
remove deprecated code - replaced by service module which handles tab…
jh-RLI Jul 30, 2025
f0ea251
#1971: Implement datasets:
jh-RLI Aug 1, 2025
7dbe988
#1971: add api paths for new dataset functionalities
jh-RLI Aug 1, 2025
244f3b1
#1971: extend api test suite with tests for all new dataset related f…
jh-RLI Aug 1, 2025
4b38d37
#1971: fix missing reuse information
jh-RLI Aug 1, 2025
6ee6101
#1971: avoid changes to oemetadata v2 template
jh-RLI Aug 1, 2025
4515a18
#1971: Add important note on how to handle resource (table) metadata
jh-RLI Aug 4, 2025
1bbf66f
#1971 Enhance table creation:
jh-RLI Aug 4, 2025
62cc52e
#1971: Fix Dataset resource update method to correctly handle table r…
jh-RLI Aug 4, 2025
55c52a0
Add notice about legacy code which is not part of the dataview response
jh-RLI Aug 4, 2025
e7c24b6
#1971: Update tests to match intended oemetadata handling
jh-RLI Aug 4, 2025
0d487bc
#1971: fix return metadata
jh-RLI Aug 4, 2025
2011ad0
#1971: update changelog
jh-RLI Aug 4, 2025
8c61535
fix typo
jh-RLI Aug 4, 2025
65be88e
Merge branch 'develop' into feature-1971-add-oep-datasets
jh-RLI Oct 16, 2025
49a588f
Merge branch 'develop' into feature-1971-add-oep-datasets
jh-RLI Feb 10, 2026
4fef660
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 10, 2026
8d5603d
Merge branch 'develop' into feature-1971-add-oep-datasets
jh-RLI Mar 12, 2026
a3f0510
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 12, 2026
ee18440
Merge branch 'develop' into feature-1971-add-oep-datasets
jh-RLI Apr 7, 2026
c70116a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 7, 2026
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
37 changes: 36 additions & 1 deletion api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.urls import reverse
from rest_framework import serializers

from dataedit.models import Table
from dataedit.models import Dataset, Table
from modelview.models import Energyframework, Energymodel
from oeplatform.settings import URL

Expand Down Expand Up @@ -164,3 +164,38 @@ def validate_dataset(self, value):
raise serializers.ValidationError("Dataset names must be unique.")

return value


class DatasetReadSerializer(serializers.ModelSerializer):
class Meta:
model = Dataset
fields = ["uuid", "name", "metadata", "created_at"]


class DatasetCreateSerializer(serializers.Serializer):
name = serializers.CharField()
title = serializers.CharField()
description = serializers.CharField()
at_id = serializers.URLField(required=False)


class DatasetAssignTablesSerializer(serializers.Serializer):
tables = serializers.ListField(
child=serializers.DictField(child=serializers.CharField()), min_length=1
)

def validate_tables(self, value):
for item in value:
if "schema" not in item or "name" not in item:
raise serializers.ValidationError(
"Each table must have 'schema' and 'name'."
)
return value


class DatasetResourceSerializer(serializers.ModelSerializer):
schema = serializers.StringRelatedField()

class Meta:
model = Table
fields = ["id", "schema", "name", "oemetadata", "human_readable_name"]
25 changes: 25 additions & 0 deletions api/services/dataset_creation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# SPDX-FileCopyrightText: 2025 Jonas Huber <https://github.com/jh-RLI> © Reiner Lemoine Institut # noqa: E501
#
# SPDX-License-Identifier: AGPL-3.0-or-later

from copy import deepcopy
from typing import Any

from oemetadata.v2.v20.example import OEMETADATA_V20_EXAMPLE
from oemetadata.v2.v20.template import OEMETADATA_V20_TEMPLATE


def assemble_dataset_metadata(
validated_data: dict[str, Any], oemetadata: dict = OEMETADATA_V20_TEMPLATE
) -> dict[str, Any]:
# set the context
oemetadata = deepcopy(oemetadata)
oemetadata["@context"] = OEMETADATA_V20_EXAMPLE["@context"]
oemetadata["resources"] = [] # Remove resources

oemetadata["@id"] = validated_data.get("at_id")
oemetadata["name"] = validated_data["name"]
oemetadata["title"] = validated_data["title"]
oemetadata["description"] = validated_data["description"]

return oemetadata
176 changes: 176 additions & 0 deletions api/tests/test_datasets_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# SPDX-FileCopyrightText: 2025 Jonas Huber <https://github.com/jh-RLI> © Reiner Lemoine Institut # noqa: E501
#
# SPDX-License-Identifier: AGPL-3.0-or-later

from copy import deepcopy

from oemetadata.latest.template import OEMETADATA_LATEST_TEMPLATE
from rest_framework import status
from rest_framework.test import APITestCase

from dataedit.models import Dataset, Schema, Table


class DatasetAPITests(APITestCase):
def setUpDatasetMetadata(self, dataset_name: str):
metadata = deepcopy(OEMETADATA_LATEST_TEMPLATE)

metadata["name"] = dataset_name
metadata["resources"] = []

return metadata

def setUpResourceMetadata(self, table_name: str):
metadata = deepcopy(OEMETADATA_LATEST_TEMPLATE)

metadata["resources"][0]["name"] = table_name

return metadata

def test_create_dataset(self):
payload = {
"name": "test_dataset",
"title": "Test Dataset",
"description": "This is a test dataset",
}
response = self.client.post(
"/api/v0/datasets/", payload, format="json"
) # fixed
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertIn("metadata", response.data)
self.assertIn("resources", response.data["metadata"])
self.assertEqual(response.data["metadata"]["name"], "test_dataset")

def test_list_datasets(self):
Dataset.objects.create(name="ds1", metadata=self.setUpDatasetMetadata("ds1"))
Dataset.objects.create(name="ds2", metadata=self.setUpDatasetMetadata("ds2"))
response = self.client.get("/api/v0/datasets/") # fixed
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 2)

def test_assign_tables_to_dataset(self):
schema = Schema.objects.create(name="test_schema")
Table.objects.create(
name="t1", schema=schema, oemetadata=self.setUpResourceMetadata("t1")
)
Table.objects.create(
name="t2", schema=schema, oemetadata=self.setUpResourceMetadata("t2")
)
dataset = Dataset.objects.create(
name="test_dataset", metadata={"name": "test_dataset"}
)

payload = {
"dataset_name": "test_dataset",
"tables": [
{"schema": "test_schema", "name": "t1"},
{"schema": "test_schema", "name": "t2"},
],
}

response = self.client.post(
"/api/v0/datasets/test_dataset/assign-tables/", payload, format="json"
)
self.assertEqual(response.status_code, 200)
dataset.refresh_from_db()
self.assertEqual(len(dataset.tables.all()), 2)
self.assertEqual(len(dataset.metadata["resources"]), 2)

def test_list_resources_for_dataset(self):
schema = Schema.objects.create(name="test_schema")
table = Table.objects.create(
name="t1", schema=schema, oemetadata=self.setUpResourceMetadata("t1")
)
dataset = Dataset.objects.create(
name="test_dataset", metadata=self.setUpDatasetMetadata("test_dataset")
)
dataset.tables.add(table)
dataset.update_resources_from_tables()

response = self.client.get(
f"/api/v0/datasets/{dataset.name}/resources/"
) # fixed
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 1)
self.assertEqual(response.data[0]["name"], "t1")

def test_assign_missing_table(self):
Dataset.objects.create(
name="ds_missing", metadata=self.setUpDatasetMetadata("ds_missing")
)

payload = {
"dataset_name": "ds_missing",
"tables": [{"schema": "nonexistent", "name": "missing"}],
}

response = self.client.post(
"/api/v0/datasets/ds_missing/assign-tables/", payload, format="json"
)
self.assertEqual(response.status_code, 200)
self.assertIn("missing", response.data)
self.assertEqual(len(response.data["missing"]), 1)

def test_list_resources_dataset_not_found(self):
response = self.client.get("/api/v0/datasets/nonexistent/resources/") # fixed
self.assertEqual(response.status_code, 404)


class DatasetManagerAPITests(APITestCase):
def setUp(self):
self.dataset = Dataset.objects.create(
name="test_dataset",
metadata={
"name": "test_dataset",
"title": "Test Title",
"description": "Test Description",
"resources": [],
},
)
self.detail_url = f"/api/v0/datasets/{self.dataset.name}/"

def test_get_dataset(self):
response = self.client.get(self.detail_url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["name"], "test_dataset")

def test_update_dataset(self):
updated_data = {
"name": "test_dataset", # must match existing name
"title": "Updated Title",
"description": "Updated Description",
"at_id": "https://example.org/dataset/test_dataset",
}

response = self.client.put(self.detail_url, updated_data, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.dataset.refresh_from_db()
self.assertEqual(self.dataset.metadata["title"], "Updated Title")
self.assertEqual(self.dataset.metadata["description"], "Updated Description")
self.assertEqual(
self.dataset.metadata["@id"], "https://example.org/dataset/test_dataset"
)

def test_delete_dataset(self):
response = self.client.delete(self.detail_url)
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertFalse(Dataset.objects.filter(name="test_dataset").exists())

def test_get_nonexistent_dataset(self):
response = self.client.get("/api/v0/datasets/nonexistent_dataset/")
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

def test_put_nonexistent_dataset(self):
payload = {
"name": "nonexistent_dataset",
"title": "Does Not Exist",
"description": "Should return 404",
}
response = self.client.put(
"/api/v0/datasets/nonexistent_dataset/", payload, format="json"
)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

def test_delete_nonexistent_dataset(self):
response = self.client.delete("/api/v0/datasets/nonexistent_dataset/")
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
20 changes: 20 additions & 0 deletions api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,26 @@
ManageOekgScenarioDatasetsAPIView.as_view(),
name="add-scenario-datasets",
),
path(
"v0/datasets/",
views.DatasetsListCreate.as_view(),
name="dataset-list-create",
),
path(
"v0/datasets/<str:dataset_name>/assign-tables/",
views.AssignDatasetTables.as_view(),
name="dataset-assign-tables",
),
path(
"v0/datasets/<str:dataset_name>/",
views.DatasetManager.as_view(),
name="dataset",
),
path(
"v0/datasets/<str:dataset_name>/resources/",
views.DatasetsListResources.as_view(),
name="dataset-resources",
),
path("db/table-sizes/", AllTableSizesAPIView.as_view(), name="table-sizes"),
]

Expand Down
Loading
Loading