Skip to content

Commit 301693a

Browse files
authored
Biocompute model (#284)
* Remove API app Changes to be committed: deleted: api/__init__.py deleted: api/admin.py deleted: api/apps.py deleted: api/fixtures/bootstrap.json deleted: api/fixtures/metafixtures deleted: api/fixtures/metafixtures.json deleted: api/keys.sh deleted: api/migrations/0001_initial.py deleted: api/migrations/0002_auto_20220124_2356.py deleted: api/migrations/0003_rename_meta_table_prefix_table.py deleted: api/migrations/0004_rename_group_info_groupinfo.py deleted: api/migrations/0005_rename_prefixes_prefix.py deleted: api/migrations/0006_delete_new_users.py deleted: api/migrations/__init__.py deleted: api/model/__init__.py deleted: api/model/groups.py deleted: api/model/prefix.py deleted: api/models.py deleted: api/permissions.py deleted: api/rdb.sh deleted: api/request_definitions/GET.schema deleted: api/request_definitions/POST.schema deleted: api/request_definitions/templates/DELETE_delete_object_by_id.schema deleted: api/request_definitions/templates/GET_activate_account.schema deleted: api/request_definitions/templates/GET_get_object_by_id.schema deleted: api/request_definitions/templates/GET_retrieve_available_schema.schema deleted: api/request_definitions/templates/POST_convert_existing_object_between_schemas.schema deleted: api/request_definitions/templates/POST_convert_payload_to_schema.schema deleted: api/request_definitions/templates/POST_new_account.schema deleted: api/request_definitions/templates/POST_object_listing_by_token.schema deleted: api/request_definitions/templates/POST_objects_draft.schema deleted: api/request_definitions/templates/POST_objects_publish.schema deleted: api/request_definitions/templates/POST_read_object.schema deleted: api/request_definitions/templates/POST_validate_payload_against_schema.schema deleted: api/scripts/__init__.py deleted: api/scripts/method_specific/GET_draft_object_by_id.py deleted: api/scripts/method_specific/GET_published_object_by_id.py deleted: api/scripts/method_specific/GET_published_object_by_id_with_version.py deleted: api/scripts/method_specific/GET_retrieve_available_schema.py deleted: api/scripts/method_specific/POST_api_objects_drafts_create.py deleted: api/scripts/method_specific/POST_api_objects_drafts_delete.py deleted: api/scripts/method_specific/POST_api_objects_drafts_modify.py deleted: api/scripts/method_specific/POST_api_objects_drafts_permissions.py deleted: api/scripts/method_specific/POST_api_objects_drafts_permissions_set.py deleted: api/scripts/method_specific/POST_api_objects_drafts_publish.py deleted: api/scripts/method_specific/POST_api_objects_drafts_read.py deleted: api/scripts/method_specific/POST_api_objects_drafts_token.py deleted: api/scripts/method_specific/POST_api_objects_publish.py deleted: api/scripts/method_specific/POST_api_objects_published.py deleted: api/scripts/method_specific/POST_api_objects_search.py deleted: api/scripts/method_specific/POST_api_objects_token.py deleted: api/scripts/method_specific/POST_validate_payload_against_schema.py deleted: api/scripts/method_specific/__init__.py deleted: api/scripts/utilities/DbUtils.py deleted: api/scripts/utilities/FileUtils.py deleted: api/scripts/utilities/JsonUtils.py deleted: api/scripts/utilities/RequestUtils.py deleted: api/scripts/utilities/ResponseUtils.py deleted: api/scripts/utilities/SettingsUtils.py deleted: api/scripts/utilities/UserUtils.py deleted: api/scripts/utilities/__init__.py deleted: api/serializers.py deleted: api/signals.py deleted: api/templates/api/account_activation_message.html deleted: api/urls.py deleted: api/validation_definitions/IEEE/2791object.json deleted: api/validation_definitions/IEEE/description_domain.json deleted: api/validation_definitions/IEEE/error_domain.json deleted: api/validation_definitions/IEEE/execution_domain.json deleted: api/validation_definitions/IEEE/io_domain.json deleted: api/validation_definitions/IEEE/parametric_domain.json deleted: api/validation_definitions/IEEE/provenance_domain.json deleted: api/validation_definitions/IEEE/usability_domain.json deleted: api/validation_definitions/IEEE_sub/IEEE2791-2020.schema deleted: api/validation_definitions/IEEE_sub/domains/description_domain.json deleted: api/validation_definitions/IEEE_sub/domains/error_domain.json deleted: api/validation_definitions/IEEE_sub/domains/execution_domain.json deleted: api/validation_definitions/IEEE_sub/domains/io_domain.json deleted: api/validation_definitions/IEEE_sub/domains/parametric_domain.json deleted: api/validation_definitions/IEEE_sub/domains/provenance_domain.json deleted: api/validation_definitions/IEEE_sub/domains/usability_domain.json deleted: api/validation_definitions/uri_external deleted: api/views.py new file: config/settings.py modified: config/urls.py Changes not staged for commit: modified: authentication/apis.py modified: authentication/migrations/0001_initial.py deleted: authentication/migrations/0002_newuser.py modified: authentication/services.py modified: docs/refactor.md modified: search/selectors.py modified: tests/fixtures/test_data.json deleted: tests/test_database.py deleted: tests/test_fixtures.py deleted: tests/test_models modified: tests/test_views/test_api_account_activate.py deleted: tests/test_views/test_api_accounts_describe.py modified: tests/test_views/test_api_auth_add.py modified: tests/test_views/test_api_auth_reset_token.py deleted: tests/test_views/test_api_groups_group_info.py deleted: tests/test_views/test_api_groups_modify.py deleted: tests/test_views/test_api_objects.py deleted: tests/test_views/test_api_objects_drafts_create.py deleted: tests/test_views/test_api_objects_drafts_modify.py deleted: tests/test_views/test_api_objects_drafts_publish.py deleted: tests/test_views/test_api_objects_search.py deleted: tests/test_views/test_api_objects_validate.py deleted: tests/test_views/test_api_prefixes_create.py deleted: tests/test_views/test_api_prefixes_token.py deleted: tests/test_views/test_get_object_id_draft.py deleted: tests/test_views/test_get_objectid.py deleted: tests/test_views/test_published_object_by_id.py modified: token.json * Framework for BioCompute model Changes to be committed: new file: biocompute/__init__.py new file: biocompute/admin.py new file: biocompute/apis.py new file: biocompute/migrations/__init__.py new file: biocompute/models.py new file: biocompute/selectors.py new file: biocompute/services.py new file: biocompute/urls.py * Added files for Prefix model Changes to be committed: new file: prefix/__init__.py new file: prefix/admin.py new file: prefix/apis.py new file: prefix/apps.py new file: prefix/migrations/__init__.py new file: prefix/models.py new file: prefix/selectors.py new file: prefix/services.py new file: prefix/urls.py * Tests for authentication Changes to be committed: modified: .gitignore modified: authentication/apis.py modified: authentication/migrations/0001_initial.py deleted: authentication/migrations/0002_newuser.py modified: authentication/services.py new file: biocompute/migrations/0001_initial.py modified: docs/refactor.md new file: prefix/migrations/0001_initial.py modified: search/selectors.py new file: test.json new file: tests/fixtures/old_test_data.json modified: tests/fixtures/test_data.json deleted: tests/test_database.py deleted: tests/test_fixtures.py deleted: tests/test_models modified: tests/test_views/test_api_account_activate.py renamed: tests/test_views/test_api_accounts_describe.py -> tests/test_views/test_api_account_describe.py modified: tests/test_views/test_api_auth_add.py modified: tests/test_views/test_api_auth_reset_token.py deleted: tests/test_views/test_api_groups_group_info.py deleted: tests/test_views/test_api_groups_modify.py deleted: tests/test_views/test_api_objects.py deleted: tests/test_views/test_api_objects_drafts_create.py deleted: tests/test_views/test_api_objects_drafts_modify.py deleted: tests/test_views/test_api_objects_drafts_publish.py deleted: tests/test_views/test_api_objects_search.py deleted: tests/test_views/test_api_objects_validate.py deleted: tests/test_views/test_api_prefixes_create.py deleted: tests/test_views/test_api_prefixes_token.py deleted: tests/test_views/test_get_object_id_draft.py deleted: tests/test_views/test_get_objectid.py deleted: tests/test_views/test_published_object_by_id.py modified: token.json * Move Swagger files and implement BCO draft create Changes to be committed: modified: biocompute/apis.py modified: biocompute/models.py modified: biocompute/services.py modified: biocompute/urls.py new file: config/services.py modified: config/urls.py modified: docs/refactor.md modified: prefix/urls.py new file: tests/fixtures/example_bco.py new file: tests/test_views/test_api_objects_drafts_create.py * Doc fix in Bco.models Changes to be committed: modified: biocompute/models.py * Updated biocompute.model and prefix.model Added the DraftsCreateApi and tests. This included serializers for BCO creation Changes to be committed: modified: biocompute/apis.py modified: biocompute/services.py modified: config/services.py modified: prefix/models.py modified: prefix/services.py modified: test.json modified: tests/fixtures/test_data.json new file: tests/test_views/test_api_objects_drafts_create.py
1 parent 5103418 commit 301693a

File tree

8 files changed

+304
-47
lines changed

8 files changed

+304
-47
lines changed

biocompute/apis.py

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,18 @@
66

77
from drf_yasg import openapi
88
from drf_yasg.utils import swagger_auto_schema
9+
from django.db import utils
910
from rest_framework.views import APIView
1011
from rest_framework import status
1112
from rest_framework.permissions import IsAuthenticated
1213
from rest_framework.response import Response
1314
from tests.fixtures.example_bco import BCO_000001
14-
from config.services import legacy_api_converter
15+
from config.services import legacy_api_converter, response_constructor
1516
from biocompute.services import BcoDraftSerializer
1617

1718
class DraftsCreateApi(APIView):
1819
"""
19-
Create BCO Draft
20+
Create BCO Draft [Bulk Enabled]
2021
2122
--------------------
2223
@@ -63,49 +64,75 @@ class DraftsCreateApi(APIView):
6364
@swagger_auto_schema(
6465
request_body=request_body,
6566
responses={
66-
200: "Creation of BCO draft is successful.",
67-
300: "Some requests failed and some succeeded.",
68-
400: "Bad request.",
67+
200: "All requests were accepted.",
68+
207: "Some requests failed and some succeeded. Each object submitted"
69+
" will have it's own response object with it's own status"
70+
" code and message.\n",
71+
400: "All requests were rejected.",
6972
403: "Invalid token.",
7073
},
7174
tags=["BCO Management"],
7275
)
7376

7477
def post(self, request) -> Response:
75-
response_data = {}
78+
response_data = []
7679
owner = request.user
7780
data = request.data
78-
all_good = True
81+
rejected_requests = False
82+
accepted_requests = False
7983
if 'POST_api_objects_draft_create' in request.data:
8084
data = legacy_api_converter(request.data)
8185

8286
for index, object in enumerate(data):
83-
list_id = object.get("object_id", index)
87+
response_id = object.get("object_id", index)
8488
bco = BcoDraftSerializer(data=object, context={'request': request})
8589

8690
if bco.is_valid():
87-
bco.create(bco.validated_data)
88-
response_data[list_id] = "bco valid"
91+
try:
92+
bco.create(bco.validated_data)
93+
response_data.append(response_constructor(
94+
identifier=bco['object_id'].value,
95+
status = "SUCCESS",
96+
code= 200,
97+
message= f"BCO {bco['object_id'].value} created",
98+
))
99+
accepted_requests = True
100+
101+
except Exception as err:
102+
print(err)
103+
response_data.append(response_constructor(
104+
identifier=bco['object_id'].value,
105+
status = "SERVER ERROR",
106+
code= 500,
107+
message= f"BCO {bco['object_id'].value} failed",
108+
))
89109

90110
else:
91-
response_data[list_id] = bco.errors
92-
all_good = False
111+
response_data.append(response_constructor(
112+
identifier=response_id,
113+
status = "REJECTED",
114+
code= 400,
115+
message= f"BCO {response_id} rejected",
116+
data=bco.errors
117+
))
118+
rejected_requests = True
119+
120+
print(accepted_requests, rejected_requests )
93121

94-
if all_good is False:
122+
if accepted_requests is False and rejected_requests == True:
123+
return Response(
124+
status=status.HTTP_400_BAD_REQUEST,
125+
data=response_data
126+
)
127+
128+
if accepted_requests is True and rejected_requests is True:
95129
return Response(
96130
status=status.HTTP_207_MULTI_STATUS,
97131
data=response_data
98132
)
99133

100-
return Response(status=status.HTTP_200_OK, data=response_data)
101-
102-
# def create(self, validated_data):
103-
# # Custom creation logic here, if needed
104-
# return Bco.objects.create(**validated_data)
105-
106-
# def update(self, instance, validated_data):
107-
# # Custom update logic here, if needed
108-
# for attr, value in validated_data.items():
109-
# setattr(instance, attr, value)
110-
# instance.save()
111-
# return instance
134+
if accepted_requests is True and rejected_requests is False:
135+
return Response(
136+
status=status.HTTP_200_OK,
137+
data=response_data
138+
)

biocompute/services.py

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
#!/usr/bin/env python3
22
# biocopmute/services.py
33

4+
import re
5+
from urllib.parse import urlparse
6+
from django.conf import settings
47
from django.db import transaction
58
from django.utils import timezone
69
from biocompute.models import Bco
710
from prefix.models import Prefix
11+
from prefix.services import prefix_counter_increment
812
from django.contrib.auth.models import Group, User
913
from rest_framework import serializers
1014

@@ -13,6 +17,8 @@
1317
Service functions for working with BCOs
1418
"""
1519

20+
HOSTNAME = settings.PUBLIC_HOSTNAME
21+
1622
class BcoDraftSerializer(serializers.Serializer):
1723
object_id = serializers.URLField(required=False)
1824
contents = serializers.JSONField()
@@ -21,6 +27,9 @@ class BcoDraftSerializer(serializers.Serializer):
2127
authorized_users = serializers.ListField(child=serializers.CharField(), required=False)
2228

2329
def validate(self, attrs):
30+
"""BCO Draft Validator
31+
"""
32+
2433
errors = {}
2534
request = self.context.get('request')
2635
attrs["owner"] = request.user
@@ -43,36 +52,38 @@ def validate(self, attrs):
4352

4453
# Validate Prefix
4554
try:
46-
attrs['prefix_instance'] = Prefix.objects.get(prefix=attrs['prefix'])
55+
#set a name and instance for Prefix
56+
attrs['prefix'] = Prefix.objects.get(prefix=attrs['prefix'])
57+
attrs['prefix_name'] = attrs['prefix'].prefix
4758
except Prefix.DoesNotExist as err:
4859
errors['prefix'] = 'Invalid prefix.'
60+
raise serializers.ValidationError(errors)
4961

50-
# Validate object_id match
51-
if 'object_id' in attrs and attrs['object_id'] != attrs['contents'].get('object_id', ''):
52-
errors["object_id"] = "object_id does not match object_id in contents."
53-
54-
# Validate that object_id is unique
55-
object_id = attrs['contents'].get('object_id', '')
56-
57-
if not Bco.objects.filter(object_id=object_id).exists():
58-
pass
62+
# Validate or create object_id
63+
if 'object_id' in attrs:
64+
id_errors = validate_bco_object_id(
65+
attrs['object_id'],
66+
attrs['prefix_name']
67+
)
68+
if id_errors != 0:
69+
errors["object_id"] = id_errors
5970
else:
60-
errors["object_id"] = f"That object_id, {attrs['object_id']}, already exists."
71+
attrs['object_id'] = create_bco_id(attrs['prefix'])
6172

73+
# If erros exist than raise and exception and return it, otherwise
74+
# return validated data
6275
if errors:
6376
raise serializers.ValidationError(errors)
6477

6578
return attrs
6679

6780
@transaction.atomic
6881
def create(self, validated_data):
69-
# Remove the non-model field 'prefix' and use 'prefix_instance' instead
70-
prefix_instance = validated_data.pop('prefix_instance', None)
71-
validated_data.pop('prefix')
82+
# Remove the non-model field 'prefix_name' and use 'prefix' instance instead
83+
validated_data.pop('prefix_name')
7284
authorized_group_names = validated_data.pop('authorized_groups', [])
7385
authorized_usernames = validated_data.pop('authorized_users', [])
74-
75-
bco_instance = Bco.objects.create(**validated_data, prefix=prefix_instance, last_update=timezone.now())
86+
bco_instance = Bco.objects.create(**validated_data, last_update=timezone.now())
7687

7788
# Set ManyToMany relations
7889
if authorized_group_names:
@@ -84,3 +95,44 @@ def create(self, validated_data):
8495
bco_instance.authorized_users.set(authorized_users)
8596

8697
return bco_instance
98+
99+
100+
def validate_bco_object_id(object_id: str, prefix_name: str):
101+
"""Validate BCO object ID
102+
103+
Function to validate a proposed BCO object_id. Will reject the ID if the
104+
following constraints are not met:
105+
1. Correct hostname for this BCODB instance
106+
2. Prefix submitted is not in the object_id
107+
3. The object_id already exists
108+
"""
109+
errors = []
110+
111+
if HOSTNAME not in object_id:
112+
errors.append("Object ID does not conform to the required format. "\
113+
+ f"The hostname {HOSTNAME} is not in {object_id}")
114+
if prefix_name not in object_id:
115+
errors.append(f"Object ID, {object_id}, does not contain the "\
116+
+ f"submitted prefix, {prefix_name}.")
117+
118+
if not Bco.objects.filter(object_id=object_id).exists():
119+
pass
120+
else:
121+
errors.append(f"That object_id, {object_id}, already exists.")
122+
123+
if errors:
124+
return errors
125+
return 0
126+
127+
def create_bco_id(prefix: Prefix) -> str:
128+
"""Create BCO object_id
129+
130+
Function to construct BCO object_id. Takes a Prefix model instance and
131+
returns a bco.object_id.
132+
"""
133+
134+
count = prefix_counter_increment(prefix)
135+
bco_identifier = format(count, "06d")
136+
bco_id = f"{HOSTNAME}/{prefix}_{bco_identifier}/DRAFT"
137+
138+
return bco_id

config/services.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,27 @@ def legacy_api_converter(data:dict) ->dict:
1313
"""
1414

1515
_, new_data = data.popitem()
16-
return new_data
16+
return new_data
17+
18+
def response_constructor(
19+
identifier: str,
20+
status: str,
21+
code: str,
22+
message: str=None,
23+
data: dict= None
24+
)-> dict:
25+
"""Response Data Proccessing
26+
"""
27+
response_object = {
28+
identifier: {
29+
"request_status": status,
30+
"status_code": code
31+
}
32+
}
33+
34+
if data is not None:
35+
response_object[identifier]["data"] = data
36+
if message is not None:
37+
response_object[identifier]["message"] = message
38+
39+
return response_object

prefix/models.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,26 @@ class Prefix(models.Model):
88

99
prefix = models.CharField(primary_key=True, max_length=5)
1010
certifying_key = models.TextField(blank=True, null=True)
11-
created = models.DateTimeField(default=timezone.now, blank=True, null=True)
11+
created = models.DateTimeField(
12+
default=timezone.now,
13+
blank=True,
14+
null=True
15+
)
1216
description = models.TextField(blank=True, null=True)
13-
owner = models.ForeignKey(User, on_delete=models.CASCADE, to_field="username")
14-
authorized_groups = models.ManyToManyField(Group, blank=True, related_name='authorized_prefix')
17+
owner = models.ForeignKey(
18+
User,
19+
on_delete=models.CASCADE,
20+
to_field="username"
21+
)
22+
authorized_groups = models.ManyToManyField(
23+
Group,
24+
blank=True,
25+
related_name='authorized_prefix'
26+
)
27+
counter = models.IntegerField(
28+
default=0,
29+
help_text="Counter for object_id asignment"
30+
)
1531

1632
def __str__(self):
1733
"""String for representing the BCO model (in Admin site etc.)."""

prefix/services.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env python3
2+
# prefix/services.py
3+
4+
import re
5+
from urllib.parse import urlparse
6+
from django.conf import settings
7+
from django.utils import timezone
8+
from biocompute.models import Bco
9+
from prefix.models import Prefix
10+
from django.db.models import F
11+
12+
"""Prefix Services
13+
14+
Service functions for working with BCO Prefixes
15+
"""
16+
17+
def prefix_counter_increment(prefix: Prefix) -> int:
18+
"""Prefix Counter Increment
19+
20+
Simple incrementing function.
21+
Counter for BCO object_id asignment.
22+
"""
23+
24+
Prefix.objects.update(counter=F("counter") + 1)
25+
count = prefix.counter
26+
return count

test.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -855,7 +855,8 @@
855855
"created": "2024-03-14T13:53:59Z",
856856
"description": "Default prefix for all BioCompute Objects",
857857
"owner": "AnonymousUser",
858-
"authorized_groups": []
858+
"authorized_groups": [],
859+
"counter": 0
859860
}
860861
}
861862
]

tests/fixtures/test_data.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -855,7 +855,8 @@
855855
"created": "2024-03-14T13:53:59Z",
856856
"description": "Default prefix for all BioCompute Objects",
857857
"owner": "AnonymousUser",
858-
"authorized_groups": []
858+
"authorized_groups": [],
859+
"counter": 0
859860
}
860861
}
861862
]

0 commit comments

Comments
 (0)