Skip to content

Commit

Permalink
Enable publish bco endpoint
Browse files Browse the repository at this point in the history
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 committed Apr 11, 2024
1 parent c0dafbd commit 9246ed6
Show file tree
Hide file tree
Showing 8 changed files with 698 additions and 80 deletions.
266 changes: 205 additions & 61 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,8 @@ 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
)

if accepted_requests is True and rejected_requests is True:
return Response(
status=status.HTTP_207_MULTI_STATUS,
data=response_data
)

if accepted_requests is True and rejected_requests is False:
return Response(
status=status.HTTP_200_OK,
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]
Expand All @@ -176,33 +176,39 @@ class DraftsModifyApi(APIView):
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
),
},
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].",
),
description="BCO Drafts to create.",
),
responses={
200: "All requests were accepted.",
207: "Some requests failed and some succeeded. Each object submitted"
Expand Down Expand Up @@ -279,23 +285,8 @@ 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
)

if accepted_requests is True and rejected_requests is True:
return Response(
status=status.HTTP_207_MULTI_STATUS,
data=response_data
)

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

class DraftRetrieveApi(APIView):
"""Get a draft object
Expand Down Expand Up @@ -355,6 +346,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 +515,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 9246ed6

Please sign in to comment.