Skip to content

Commit

Permalink
Publish draft (#308)
Browse files Browse the repository at this point in the history
* Add DraftsModifyApi

Changes to be committed:
	modified:   biocompute/apis.py
	modified:   biocompute/selectors.py
	modified:   biocompute/services.py
	modified:   biocompute/urls.py
	modified:   prefix/selectors.py
	deleted:    tests/test_apis/test_biocompute/objects_drafts_create.py
	deleted:    tests/test_apis/test_biocompute/test_objects_drafts_create.py

* Add IEEE schema
Changes to be committed:
	new file:   .secrets
	new file:   config/IEEE/2791object.json
	new file:   config/IEEE/description_domain.json
	new file:   config/IEEE/error_domain.json
	new file:   config/IEEE/execution_domain.json
	new file:   config/IEEE/io_domain.json
	new file:   config/IEEE/parametric_domain.json
	new file:   config/IEEE/provenance_domain.json
	new file:   config/IEEE/usability_domain.json

* test_objects_drafts_create
Changes to be committed:
	new file:   tests/test_apis/test_biocompute/test_objects_drafts_create.py

* Fix test
Changes to be committed:
	modified:   tests/test_apis/test_biocompute/test_objects_drafts_create.py

* Enable publish bco endpoint

This included a great many supporting functions
and permission checks to accomplish. Also added
the validate functions to the codebase

Changes to be committed:
	modified:   biocompute/apis.py
	modified:   biocompute/selectors.py
	modified:   biocompute/services.py
	modified:   biocompute/urls.py
	modified:   config/services.py
	modified:   docs/refactor.md
	modified:   prefix/selectors.py
	new file:   tests/test_apis/test_biocompute/test_objects_drafts_publish.py
  • Loading branch information
HadleyKing authored Apr 11, 2024
1 parent 407b18c commit 8ee1039
Show file tree
Hide file tree
Showing 18 changed files with 1,590 additions and 42 deletions.
Empty file added .secrets
Empty file.
322 changes: 299 additions & 23 deletions biocompute/apis.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,34 @@
"""BioCompute Object APIs
"""

from biocompute.services import (
BcoDraftSerializer,
BcoValidator,
ModifyBcoDraftSerializer,
publish_draft,
bco_counter_increment
)
from biocompute.selectors import (
retrieve_bco,
user_can_modify_bco,
user_can_publish_bco,
object_id_deconstructor,
)
from config.services import (
legacy_api_converter,
response_constructor,
response_status,
)
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from django.conf import settings
from django.db import utils
from rest_framework.views import APIView
from prefix.selectors import user_can_draft_prefix
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.response import Response
from tests.fixtures.example_bco import BCO_000001
from config.services import legacy_api_converter, response_constructor
from biocompute.services import BcoDraftSerializer, bco_counter_increment, ModifyBcoDraftSerializer
from biocompute.selectors import retrieve_bco, user_can_modify_bco
from prefix.selectors import user_can_draft

hostname = settings.PUBLIC_HOSTNAME

Expand Down Expand Up @@ -93,7 +108,7 @@ def post(self, request) -> Response:
for index, object in enumerate(data):
response_id = object.get("object_id", index)
bco_prefix = object.get("prefix", index)
prefix_permitted = user_can_draft(owner, bco_prefix)
prefix_permitted = user_can_draft_prefix(owner, bco_prefix)

if prefix_permitted is None:
response_data.append(response_constructor(
Expand Down Expand Up @@ -147,23 +162,131 @@ def post(self, request) -> Response:
))
rejected_requests = True

if accepted_requests is False and rejected_requests == True:
return Response(
status=status.HTTP_400_BAD_REQUEST,
data=response_data
)
status_code = response_status(accepted_requests, rejected_requests)
return Response(status=status_code, data=response_data)

class DraftsModifyApi(APIView):
"""Modify BCO Draft [Bulk Enabled]
API endpoint for modifying BioCompute Object (BCO) drafts, with support
for bulk operations.
This endpoint allows authenticated users to modify existing BCO drafts
individually or in bulk by submitting a list of BCO drafts. The operation
can be performed for one or more drafts in a single request. Each draft is
validated and processed independently, allowing for mixed response
statuses (HTTP_207_MULTI_STATUS) in the case of bulk submissions.
NOTE: If a list of `authorized_users` is provided, this method replaces
the current list of authorized users with the new list, allowing for
dynamic access control to the BCO. Users not included in the new list will
lose their access unless they are the owner or have other permissions.
"""

permission_classes = [IsAuthenticated,]

@swagger_auto_schema(
operation_id="api_objects_drafts_modify",
request_body=openapi.Schema(
type=openapi.TYPE_ARRAY,
title="Modify BCO Draft Schema",
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=[],
properties={
"authorized_users": openapi.Schema(
type=openapi.TYPE_ARRAY,
description="Users which can access the BCO draft.",
items=openapi.Schema(type=openapi.TYPE_STRING,
example="tester")
),
"contents": openapi.Schema(
type=openapi.TYPE_OBJECT,
description="Contents of the BCO.",
example=BCO_000001
),
},
),
description="Modify BCO draft [Bulk Enabled].",
),
responses={
200: "All requests were accepted.",
207: "Some requests failed and some succeeded. Each object submitted"
" will have it's own response object with it's own status"
" code and message.\n",
400: "All requests were rejected.",
403: "Invalid token.",
},
tags=["BCO Management"],
)

def post(self, request) -> Response:
response_data = []
requester = request.user
data = request.data
rejected_requests = False
accepted_requests = False
if 'POST_api_objects_drafts_modify' in request.data:
data = legacy_api_converter(request.data)

if accepted_requests is True and rejected_requests is True:
return Response(
status=status.HTTP_207_MULTI_STATUS,
data=response_data
)
for index, object in enumerate(data):
response_id = object.get("object_id", index)
modify_permitted = user_can_modify_bco(response_id, requester)

if modify_permitted is None:
response_data.append(response_constructor(
identifier=response_id,
status = "NOT FOUND",
code= 404,
message= f"Invalid BCO: {response_id}.",
))
rejected_requests = True
continue

if accepted_requests is True and rejected_requests is False:
return Response(
status=status.HTTP_200_OK,
data=response_data
)
if modify_permitted is False:
response_data.append(response_constructor(
identifier=response_id,
status = "FORBIDDEN",
code= 400,
message= f"User, {requester}, does not have draft permissions"\
+ f" for BCO {response_id}.",
))
rejected_requests = True
continue

bco = ModifyBcoDraftSerializer(data=object)

if bco.is_valid():
try:
bco.update(bco.validated_data)
response_data.append(response_constructor(
identifier=response_id,
status = "SUCCESS",
code= 200,
message= f"BCO {response_id} updated",
))
accepted_requests = True

except Exception as err:
response_data.append(response_constructor(
identifier=response_id,
status = "SERVER ERROR",
code= 500,
message= f"BCO {response_id} failed",
))

else:
response_data.append(response_constructor(
identifier=response_id,
status = "REJECTED",
code= 400,
message= f"BCO {response_id} rejected",
data=bco.errors
))
rejected_requests = True

status_code = response_status(accepted_requests, rejected_requests)
return Response(status=status_code, data=response_data)

class DraftsModifyApi(APIView):
"""Modify BCO Draft [Bulk Enabled]
Expand Down Expand Up @@ -355,6 +478,156 @@ def get(self, request, bco_accession):
bco_counter_increment(bco_instance)
return Response(status=status.HTTP_200_OK, data=bco_instance.contents)

class DraftsPublishApi(APIView):
"""Publish Draft BCO [Bulk Enabled]
API endpoint for publishing BioCompute Object (BCO) drafts, with support
for bulk operations.
This endpoint allows authenticated users to publish existing BCO drafts
individually or in bulk by submitting a list of BCO drafts. The operation
can be performed for one or more drafts in a single request. Each draft is
validated and processed independently, allowing for mixed response
statuses (HTTP_207_MULTI_STATUS) in the case of bulk submissions.
"""

permission_classes = [IsAuthenticated]

@swagger_auto_schema(
operation_id="api_objects_drafts_publish",
request_body=openapi.Schema(
type=openapi.TYPE_ARRAY,
title="Publish BCO Draft Schema",
description="Publish draft BCO [Bulk Enabled]",
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=["object_id"],
properties={
"published_object_id": openapi.Schema(
type=openapi.TYPE_STRING,
description="BCO Object Draft ID.",
example="http://127.0.0.1:8000/TEST_000001/1.0"
),
"object_id": openapi.Schema(
type=openapi.TYPE_STRING,
description="BCO Object ID to use for published object.",
example="http://127.0.0.1:8000/TEST_000001/DRAFT"
),
"delete_draft": openapi.Schema(
type=openapi.TYPE_BOOLEAN,
description="Whether or not to delete the draft."\
+" False by default.",
example=False
),
}
)
),
responses={
200: "All requests were accepted.",
207: "Some requests failed and some succeeded. Each object submitted"
" will have it's own response object with it's own status"
" code and message.\n",
400: "All requests were rejected.",
403: "Invalid token.",
},
tags=["BCO Management"],
)

def post(self, request) -> Response:
validator = BcoValidator()
response_data = []
requester = request.user
data = request.data
rejected_requests = False
accepted_requests = False
if 'POST_api_objects_drafts_publish' in request.data:
data = legacy_api_converter(request.data)

for index, object in enumerate(data):
response_id = object.get("object_id", index)
bco_instance = user_can_publish_bco(object, requester)

if bco_instance is None:
response_data.append(response_constructor(
identifier=response_id,
status = "NOT FOUND",
code= 404,
message= f"Invalid BCO: {response_id} does not exist.",
))
rejected_requests = True
continue

if bco_instance is False:
response_data.append(response_constructor(
identifier=response_id,
status = "FORBIDDEN",
code= 403,
message= f"User, {requester}, does not have draft permissions"\
+ f" for BCO {response_id}.",
))
rejected_requests = True
continue

if type(bco_instance) is str:
response_data.append(response_constructor(
identifier=response_id,
status = "BAD REQUEST",
code= 400,
message= bco_instance
))
rejected_requests = True
continue

if type(bco_instance) is tuple:
response_data.append(response_constructor(
identifier=response_id,
status = "BAD REQUEST",
code= 400,
message= f"Invalid `published_object_id`."\
+ f"{bco_instance[0]} and {bco_instance[1]}"\
+ " do not match.",
))
rejected_requests = True
continue

if bco_instance.state == 'PUBLISHED':
object_id = bco_instance.object_id
response_data.append(response_constructor(
identifier=response_id,
status = "CONFLICT",
code= 409,
message= f"Invalid `object_id`: {object_id} already"\
+ " exists.",
))
rejected_requests = True
continue

bco_results = validator.parse_and_validate(bco_instance.contents)
for identifier, result in bco_results.items():
if result["number_of_errors"] > 0:
response_data.append(response_constructor(
identifier=response_id,
status = "REJECTED",
code= 400,
message= f"Publishing BCO {response_id} rejected",
data=bco_results
))
rejected_requests = True

else:
published_bco = publish_draft(bco_instance, requester, object)
identifier=published_bco.object_id
response_data.append(response_constructor(
identifier=identifier,
status = "SUCCESS",
code= 201,
message= f"BCO {identifier} has been published.",
))
accepted_requests = True

status_code = response_status(accepted_requests, rejected_requests)
return Response(status=status_code, data=response_data)

class PublishedRetrieveApi(APIView):
"""Get Published BCO
Expand All @@ -374,7 +647,10 @@ class PublishedRetrieveApi(APIView):
- `bco_version`:
Specifies the version of the BCO to be retrieved.
"""


authentication_classes = []
permission_classes = [AllowAny]

@swagger_auto_schema(
operation_id="api_get_published",
manual_parameters=[
Expand Down
Loading

0 comments on commit 8ee1039

Please sign in to comment.