Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 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
ae6d978
Merge branch 'develop' into feature-1971-add-oep-datasets
jh-RLI May 11, 2026
0f500d0
Clean up metadata handling in views.py
jh-RLI May 11, 2026
052e9dd
Merge branch 'develop' into feature-1971-add-oep-datasets
jh-RLI Jun 10, 2026
ac0aaca
delete import _assert_valid_identifier_name in views.py
tomi-rli Jun 10, 2026
b0dc5fd
fix after bad merge
jh-RLI Jun 10, 2026
0068bf5
#1971 fix missing imports
jh-RLI Jun 10, 2026
d8775fe
#1917: merge migrations
jh-RLI Jun 10, 2026
929be12
#1971: Refactor schema to topic
jh-RLI Jun 10, 2026
61e5960
Fix spdx for REUSE
jh-RLI Jun 10, 2026
b295d30
#1971: Remove deprecated schema from test
jh-RLI Jun 10, 2026
9dd4c2b
#1971: Remove deprecated schema from test
jh-RLI Jun 10, 2026
4e01e7d
#1971: Remove deprecated schema from test
jh-RLI Jun 10, 2026
fcddee6
#1971 Fix broken api paths
jh-RLI Jun 10, 2026
bebdf63
#1971 Remove deprecated schema form dataset
jh-RLI Jun 10, 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
1 change: 0 additions & 1 deletion api/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1522,7 +1522,6 @@ def set_table_metadata(table: str, metadata):
Args:
table(str): name of table
metadata: OEPMetadata or metadata object (dict) or metadata str
cursor: sql alchemy connection cursor
"""

# ---------------------------------------
Expand Down
35 changes: 34 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,36 @@ 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 "name" not in item:
raise serializers.ValidationError("Each table must have '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
173 changes: 173 additions & 0 deletions api/tests/test_datasets_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# 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, Table, Topic


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 = Topic.objects.create(name="test_schema")
Table.objects.create(name="t1", oemetadata=self.setUpResourceMetadata("t1"))
Table.objects.create(name="t2", oemetadata=self.setUpResourceMetadata("t2"))
dataset = Dataset.objects.create(
name="test_dataset", metadata={"name": "test_dataset"}
)

payload = {
"dataset_name": "test_dataset",
"tables": [
{"name": "t1"},
{"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 = Topic.objects.create(name="test_schema")
table = Table.objects.create(
name="t1", oemetadata=self.setUpResourceMetadata("t1")
)
table.topics.add(schema)
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": [{"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)
24 changes: 24 additions & 0 deletions api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
AdvancedSetIsolationLevelAPIView,
AdvancedUpdateAPIView,
AllTableSizesAPIView,
AssignDatasetTables,
DatasetManager,
DatasetsListCreate,
DatasetsListResources,
EnergyframeworkFactsheetListAPIView,
EnergymodelFactsheetListAPIView,
ManageOekgScenarioDatasetsAPIView,
Expand Down Expand Up @@ -287,6 +291,26 @@
ManageOekgScenarioDatasetsAPIView.as_view(),
name="add-scenario-datasets",
),
path(
"datasets/",
DatasetsListCreate.as_view(),
name="dataset-list-create",
),
path(
"datasets/<str:dataset_name>/assign-tables/",
AssignDatasetTables.as_view(),
name="dataset-assign-tables",
),
path(
"datasets/<str:dataset_name>/",
DatasetManager.as_view(),
name="dataset",
),
path(
"datasets/<str:dataset_name>/resources/",
DatasetsListResources.as_view(),
name="dataset-resources",
),
path("db/table-sizes/", AllTableSizesAPIView.as_view(), name="table-sizes"),
]

Expand Down
Loading
Loading