From 1d886ea070b495b06eda6c36d259334d5735e31b Mon Sep 17 00:00:00 2001 From: Julian Date: Wed, 2 Aug 2023 10:23:34 -0400 Subject: [PATCH 1/8] Add dats action to retrieve DATS file for a specific dataset --- chord_metadata_service/chord/api_views.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/chord_metadata_service/chord/api_views.py b/chord_metadata_service/chord/api_views.py index 25a9aa758..9f6e1c63e 100644 --- a/chord_metadata_service/chord/api_views.py +++ b/chord_metadata_service/chord/api_views.py @@ -1,9 +1,11 @@ import logging +import json from rest_framework import status, viewsets from rest_framework.permissions import BasePermission, SAFE_METHODS from rest_framework.response import Response from rest_framework.settings import api_settings +from rest_framework.decorators import action from django_filters.rest_framework import DjangoFilterBackend @@ -72,6 +74,18 @@ class DatasetViewSet(CHORDPublicModelViewSet): renderer_classes = tuple(CHORDModelViewSet.renderer_classes) + (JSONLDDatasetRenderer, RDFDatasetRenderer,) queryset = Dataset.objects.all().order_by("title") + @action(detail=True, methods=['get']) + def dats(self, request, pk=None): + """ + Retrieve a specific DATS file for a given dataset. + + Return the DATS file as a JSON response or an error if not found. + """ + dataset = self.get_object() + if not dataset.dats_file: + return Response({'error': 'No DATS file found'}, status=status.HTTP_404_NOT_FOUND) + return Response(json.loads(dataset.dats_file)) + class TableOwnershipViewSet(CHORDPublicModelViewSet): """ From 8cdb8dc5fb2d28e0c794c7bf3c3bbcdd412df434 Mon Sep 17 00:00:00 2001 From: Julian Date: Wed, 2 Aug 2023 10:48:37 -0400 Subject: [PATCH 2/8] Add "identifier" property to datasets provenance metadata --- chord_metadata_service/restapi/api_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chord_metadata_service/restapi/api_views.py b/chord_metadata_service/restapi/api_views.py index c325a6253..01d1ebec3 100644 --- a/chord_metadata_service/restapi/api_views.py +++ b/chord_metadata_service/restapi/api_views.py @@ -429,7 +429,7 @@ def public_dataset(_request): "dimensions", "primary_publications", "citations", "produced_by", "creators", "licenses", "acknowledges", "keywords", "version", "dats_file", - "extra_properties" + "extra_properties", "identifier" ) # convert dats_file json content to dict From ca32d1ca59aec51176c1ce9428639b2af2ec9345 Mon Sep 17 00:00:00 2001 From: Julian Date: Thu, 3 Aug 2023 12:56:19 -0400 Subject: [PATCH 3/8] Add test for project dats retrieval --- chord_metadata_service/chord/tests/test_api.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/chord_metadata_service/chord/tests/test_api.py b/chord_metadata_service/chord/tests/test_api.py index 4dcd97a57..e7f85a72e 100644 --- a/chord_metadata_service/chord/tests/test_api.py +++ b/chord_metadata_service/chord/tests/test_api.py @@ -95,6 +95,7 @@ def test_create_dataset(self): self.assertEqual(Dataset.objects.count(), len(self.valid_payloads)) def test_dats(self): + self.dats_valid_payload['dats_file'] = json.dumps({}) r = self.client.post('/api/datasets', data=json.dumps(self.dats_valid_payload), content_type="application/json") r_invalid = self.client.post('/api/datasets', data=json.dumps(self.dats_invalid_payload), @@ -103,6 +104,12 @@ def test_dats(self): self.assertEqual(r_invalid.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(Dataset.objects.count(), 1) + dataset_id = Dataset.objects.first().identifier + + url = f'/api/datasets/{dataset_id}/dats' + response = self.client.get(url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data, json.loads(self.dats_valid_payload['dats_file'])) # TODO: Update Dataset # TODO: Delete Dataset From 8aa29a6dbe7a9f1bd97d0e5de608c26e50662db8 Mon Sep 17 00:00:00 2001 From: Julian Date: Thu, 3 Aug 2023 13:47:38 -0400 Subject: [PATCH 4/8] Refactor action for project dats retrieval --- chord_metadata_service/chord/api_views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/chord_metadata_service/chord/api_views.py b/chord_metadata_service/chord/api_views.py index 9f6e1c63e..573fc8267 100644 --- a/chord_metadata_service/chord/api_views.py +++ b/chord_metadata_service/chord/api_views.py @@ -82,8 +82,6 @@ def dats(self, request, pk=None): Return the DATS file as a JSON response or an error if not found. """ dataset = self.get_object() - if not dataset.dats_file: - return Response({'error': 'No DATS file found'}, status=status.HTTP_404_NOT_FOUND) return Response(json.loads(dataset.dats_file)) From 7b2f9d4648f8f5a03fe195b54e42d13601da2343 Mon Sep 17 00:00:00 2001 From: Julian Date: Thu, 3 Aug 2023 16:06:27 -0400 Subject: [PATCH 5/8] Refactor tuple check with isinstance in authorization logic --- chord_metadata_service/restapi/candig_authz_middleware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chord_metadata_service/restapi/candig_authz_middleware.py b/chord_metadata_service/restapi/candig_authz_middleware.py index fb95941df..e5696c1f8 100644 --- a/chord_metadata_service/restapi/candig_authz_middleware.py +++ b/chord_metadata_service/restapi/candig_authz_middleware.py @@ -39,7 +39,7 @@ def __call__(self, request): opa_res_datasets = self.get_opa_datasets(token, request.path, request.method) if len(opa_res_datasets) == 0: self.authorize_datasets = 'NO_DATASETS_AUTHORIZED' - elif type(opa_res_datasets) == tuple and opa_res_datasets[0] == "error": # error response + elif isinstance(opa_res_datasets, tuple) and opa_res_datasets[0] == "error": # error response return opa_res_datasets[1] else: self.authorize_datasets = ",".join(opa_res_datasets) From eab79dac0ce41326f3af4bce2ad2a67420d03012 Mon Sep 17 00:00:00 2001 From: Julian Date: Mon, 7 Aug 2023 10:10:48 -0400 Subject: [PATCH 6/8] revert Refactor tuple check with isinstance in authorization logic --- chord_metadata_service/restapi/candig_authz_middleware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chord_metadata_service/restapi/candig_authz_middleware.py b/chord_metadata_service/restapi/candig_authz_middleware.py index e5696c1f8..fb95941df 100644 --- a/chord_metadata_service/restapi/candig_authz_middleware.py +++ b/chord_metadata_service/restapi/candig_authz_middleware.py @@ -39,7 +39,7 @@ def __call__(self, request): opa_res_datasets = self.get_opa_datasets(token, request.path, request.method) if len(opa_res_datasets) == 0: self.authorize_datasets = 'NO_DATASETS_AUTHORIZED' - elif isinstance(opa_res_datasets, tuple) and opa_res_datasets[0] == "error": # error response + elif type(opa_res_datasets) == tuple and opa_res_datasets[0] == "error": # error response return opa_res_datasets[1] else: self.authorize_datasets = ",".join(opa_res_datasets) From f0031422974565774bb964895f8ee1f495a960c6 Mon Sep 17 00:00:00 2001 From: Julian Date: Mon, 7 Aug 2023 10:31:29 -0400 Subject: [PATCH 7/8] Refactor tuple check with isinstance in authorization logic --- chord_metadata_service/restapi/candig_authz_middleware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chord_metadata_service/restapi/candig_authz_middleware.py b/chord_metadata_service/restapi/candig_authz_middleware.py index fb95941df..e5696c1f8 100644 --- a/chord_metadata_service/restapi/candig_authz_middleware.py +++ b/chord_metadata_service/restapi/candig_authz_middleware.py @@ -39,7 +39,7 @@ def __call__(self, request): opa_res_datasets = self.get_opa_datasets(token, request.path, request.method) if len(opa_res_datasets) == 0: self.authorize_datasets = 'NO_DATASETS_AUTHORIZED' - elif type(opa_res_datasets) == tuple and opa_res_datasets[0] == "error": # error response + elif isinstance(opa_res_datasets, tuple) and opa_res_datasets[0] == "error": # error response return opa_res_datasets[1] else: self.authorize_datasets = ",".join(opa_res_datasets) From 602fba272c4b61059332bd2c5df0376f51ddb6e1 Mon Sep 17 00:00:00 2001 From: Julian Date: Mon, 7 Aug 2023 13:07:08 -0400 Subject: [PATCH 8/8] Refactor test for project dats retrieval --- chord_metadata_service/chord/tests/test_api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chord_metadata_service/chord/tests/test_api.py b/chord_metadata_service/chord/tests/test_api.py index e7f85a72e..a5c455fa6 100644 --- a/chord_metadata_service/chord/tests/test_api.py +++ b/chord_metadata_service/chord/tests/test_api.py @@ -95,8 +95,8 @@ def test_create_dataset(self): self.assertEqual(Dataset.objects.count(), len(self.valid_payloads)) def test_dats(self): - self.dats_valid_payload['dats_file'] = json.dumps({}) - r = self.client.post('/api/datasets', data=json.dumps(self.dats_valid_payload), + payload = {**self.dats_valid_payload, 'dats_file': json.dumps({})} + r = self.client.post('/api/datasets', data=json.dumps(payload), content_type="application/json") r_invalid = self.client.post('/api/datasets', data=json.dumps(self.dats_invalid_payload), content_type="application/json") @@ -109,7 +109,7 @@ def test_dats(self): url = f'/api/datasets/{dataset_id}/dats' response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data, json.loads(self.dats_valid_payload['dats_file'])) + self.assertDictEqual(response.data, json.loads(payload['dats_file'])) # TODO: Update Dataset # TODO: Delete Dataset