diff --git a/admin_only/db.sqlite3.dev b/admin_only/db.sqlite3.dev index 05d8618c..f8543e90 100644 Binary files a/admin_only/db.sqlite3.dev and b/admin_only/db.sqlite3.dev differ diff --git a/apitests.py b/apitests.py index 4d8d783d..e1fe29cc 100644 --- a/apitests.py +++ b/apitests.py @@ -47,24 +47,18 @@ # (P) */ - - # ----- TESTS ----- # - - -HEADER = '\033[95m' -OKBLUE = '\033[94m' -OKCYAN = '\033[96m' -OKGREEN = '\033[92m' -WARNING = '\033[93m' -FAIL = '\033[91m' -ENDC = '\033[0m' -BOLD = '\033[1m' -UNDERLINE = '\033[4m' - - +HEADER = "\033[95m" +OKBLUE = "\033[94m" +OKCYAN = "\033[96m" +OKGREEN = "\033[92m" +WARNING = "\033[93m" +FAIL = "\033[91m" +ENDC = "\033[0m" +BOLD = "\033[1m" +UNDERLINE = "\033[4m" def pretty_output( @@ -72,116 +66,108 @@ def pretty_output( method, test_info, url, - direct_call = False, - json_send = False, - pull_key = False, - token = False + direct_call=False, + json_send=False, + pull_key=False, + token=False, ): # Describe the test. - print('\n') + print("\n") print(f"{BOLD}======= Test {test_info['test_number']} of 16 ======={ENDC}") - print('\n') - print('Description: ' + test_info['description']) - print('\n') - + print("\n") + print("Description: " + test_info["description"]) + print("\n") + # Is the call constructed or direct? - call = '' + call = "" if direct_call == True: call = url else: call = hostname + url - + # Make the request. - r = '' + r = "" + + if method == "GET": - if method == 'GET': - if token != False: - hdrs = { - 'Authorization': 'Token {}'.format(token) - } + hdrs = {"Authorization": "Token {}".format(token)} print(f"{OKCYAN}Call ({method}): {call}") print(f"Headers: {json.dumps(hdrs, indent = 4)}{ENDC}") - print('\n') - - r = requests.get( - call, - headers = hdrs - ) + print("\n") + + r = requests.get(call, headers=hdrs) else: print(f"{OKCYAN}Call ({method}): {call}") print(f"Headers: None") - print('\n') + print("\n") + + r = requests.get(call) + + elif method == "POST": - r = requests.get( - call - ) - - elif method == 'POST': - if token != False: - - hdrs = { - 'Authorization': 'Token {}'.format(token) - } - + + hdrs = {"Authorization": "Token {}".format(token)} + print(f"{OKCYAN}Call ({method}): {call}") print(f"Headers: {json.dumps(hdrs, indent = 4)}") print(f"Request body: {json.dumps(json_send, indent = 4)}{ENDC}") - print('\n') - - r = requests.post( - call, - json = json_send, - headers = hdrs - ) - + print("\n") + + r = requests.post(call, json=json_send, headers=hdrs) + else: print(f"{OKCYAN}Call ({method}): {call}") print(f"Headers: None") print(f"Request body: {json.dumps(json_send, indent = 4)}{ENDC}") - print('\n') - - r = requests.post( - call, - json = json_send - ) - + print("\n") + + r = requests.post(call, json=json_send) + print(f"{WARNING}+++ TEST RESULTS +++") - print('\n') - + print("\n") + # What did we get? - if str(r.status_code) + ' ' + r.reason == test_info['expected_response_code']: + if str(r.status_code) + " " + r.reason == test_info["expected_response_code"]: + + print( + f"{BOLD}{OKGREEN}Expected response code: {test_info['expected_response_code']}" + ) + print( + f"{OKGREEN}Receieved response code: {str(r.status_code) + ' ' + r.reason}{ENDC}" + ) + print("\n") - print(f"{BOLD}{OKGREEN}Expected response code: {test_info['expected_response_code']}") - print(f"{OKGREEN}Receieved response code: {str(r.status_code) + ' ' + r.reason}{ENDC}") - print('\n') - else: - print(f"{BOLD}{FAIL}Expected response code: {test_info['expected_response_code']}") - print(f"{FAIL}Receieved response code: {str(r.status_code) + ' ' + r.reason}{ENDC}") - print('\n') - + print( + f"{BOLD}{FAIL}Expected response code: {test_info['expected_response_code']}" + ) + print( + f"{FAIL}Receieved response code: {str(r.status_code) + ' ' + r.reason}{ENDC}" + ) + print("\n") + try: - dumped = json.dumps(json.loads(r.text), indent = 4) + dumped = json.dumps(json.loads(r.text), indent=4) print(f"{OKBLUE}Response") - print('--------') + print("--------") print(f"{dumped}{ENDC}") - print('\n') + print("\n") # Do we need to pull anything? if pull_key != False: - + # Is the pull the entire response or # just one of the keys? @@ -189,23 +175,23 @@ def pretty_output( return json.loads(r.text) else: return json.loads(r.text)[pull_key] - + except json.decoder.JSONDecodeError: print(f"{BOLD}{FAIL}Response") print(f"--------") print(f"Response failed...{ENDC}") - print('\n') + print("\n") -def sub_test_no_objects_for_new_user( - testable -): +def sub_test_no_objects_for_new_user(testable): - print('--- Sub-test A ---') - print('\n') - print('Description: A newly created user should not have any objects associated with their account.') - print('\n') + print("--- Sub-test A ---") + print("\n") + print( + "Description: A newly created user should not have any objects associated with their account." + ) + print("\n") # Create a flag to indicate failure. failed = False @@ -214,7 +200,7 @@ def sub_test_no_objects_for_new_user( print(f"--------------------{ENDC}") for k, v in testable.items(): - + # Any items? if len(testable[k]) == 0: print(f"{OKGREEN}{k} ({str(len(testable[k]))}){ENDC}") @@ -223,315 +209,292 @@ def sub_test_no_objects_for_new_user( # Failure. failed = True print(f"{FAIL}{k} ({str(len(testable[k]))}){ENDC}") - - print('\n') + + print("\n") # Give back the status. if failed == False: print(f"{OKGREEN}Test Status - PASS{ENDC}") else: print(f"{FAIL}Test Status - FAILED{ENDC}") - - print('\n') - + + print("\n") + # A brand new user should not have any objects # associated with their account. return None -def sub_test_check_missing_table( - testable -): +def sub_test_check_missing_table(testable): - print('--- Sub-test A ---') - print('\n') - print('Description: Check that the missing table returns a 404.') - print('\n') + print("--- Sub-test A ---") + print("\n") + print("Description: Check that the missing table returns a 404.") + print("\n") # Create a flag to indicate failure. failed = False print(f"{WARNING}Table status") print(f"------------{ENDC}") - + # Any items? - if testable[0]['status_code'] == '404': + if testable[0]["status_code"] == "404": print(f"{OKGREEN}{testable[0]['status_code']}{ENDC}") else: # Failure. failed = True print(f"{FAIL}{testable[0]['status_code']}{ENDC}") - - print('\n') + + print("\n") # Give back the status. if failed == False: print(f"{OKGREEN}Test Status - PASS{ENDC}") else: print(f"{FAIL}Test Status - FAILED{ENDC}") - - print('\n') - + + print("\n") + return None -def sub_test_not_in_owner_group( - testable -): +def sub_test_not_in_owner_group(testable): - print('--- Sub-test A ---') - print('\n') - print('Description: Check that the provided token returns a 403.') - print('\n') + print("--- Sub-test A ---") + print("\n") + print("Description: Check that the provided token returns a 403.") + print("\n") # Create a flag to indicate failure. failed = False print(f"{WARNING}Request status") print(f"------------{ENDC}") - + # Any items? - if testable[0]['status_code'] == '403': + if testable[0]["status_code"] == "403": print(f"{OKGREEN}{testable[0]['status_code']}{ENDC}") else: # Failure. failed = True print(f"{FAIL}{testable[0]['status_code']}{ENDC}") - - print('\n') + + print("\n") # Give back the status. if failed == False: print(f"{OKGREEN}Test Status - PASS{ENDC}") else: print(f"{FAIL}Test Status - FAILED{ENDC}") - - print('\n') - - return None + print("\n") -def sub_test_in_owner_group_insufficient_write_permissions( - testable -): + return None - print('--- Sub-test A ---') - print('\n') - print('Description: Check that the provided token returns a 403.') - print('\n') +def sub_test_in_owner_group_insufficient_write_permissions(testable): + + print("--- Sub-test A ---") + print("\n") + print("Description: Check that the provided token returns a 403.") + print("\n") # Create a flag to indicate failure. failed = False print(f"{WARNING}Request status") print(f"------------{ENDC}") - + # Any items? - if testable[0]['status_code'] == '403': + if testable[0]["status_code"] == "403": print(f"{OKGREEN}{testable[0]['status_code']}{ENDC}") else: # Failure. failed = True print(f"{FAIL}{testable[0]['status_code']}{ENDC}") - - print('\n') + + print("\n") # Give back the status. if failed == False: print(f"{OKGREEN}Test Status - PASS{ENDC}") else: print(f"{FAIL}Test Status - FAILED{ENDC}") - - print('\n') - + + print("\n") + return None -def sub_test_in_owner_group_has_write_permissions( - testable -): +def sub_test_in_owner_group_has_write_permissions(testable): - print('--- Sub-test A ---') - print('\n') - print('Description: Check that the provided token (user) has write permissions on the given table.') - print('\n') + print("--- Sub-test A ---") + print("\n") + print( + "Description: Check that the provided token (user) has write permissions on the given table." + ) + print("\n") # Create a flag to indicate failure. failed = False print(f"{WARNING}Request status") print(f"------------{ENDC}") - + # Any items? - if testable[0]['status_code'] == '201': + if testable[0]["status_code"] == "201": print(f"{OKGREEN}{testable[0]['status_code']}{ENDC}") else: # Failure. failed = True print(f"{FAIL}{testable[0]['status_code']}{ENDC}") - - print('\n') + + print("\n") # Give back the status. if failed == False: print(f"{OKGREEN}Test Status - PASS{ENDC}") else: print(f"{FAIL}Test Status - FAILED{ENDC}") - - print('\n') + + print("\n") return None -def sub_test_update_id_doesnt_exist( - testable -): +def sub_test_update_id_doesnt_exist(testable): - print('--- Sub-test A ---') - print('\n') - print('Description: Check that the a non-existent object can\'t be updated.') - print('\n') + print("--- Sub-test A ---") + print("\n") + print("Description: Check that the a non-existent object can't be updated.") + print("\n") # Create a flag to indicate failure. failed = False print(f"{WARNING}Request status") print(f"------------{ENDC}") - + # Any items? - if testable[0]['status_code'] == '404': + if testable[0]["status_code"] == "404": print(f"{OKGREEN}{testable[0]['status_code']}{ENDC}") else: # Failure. failed = True print(f"{FAIL}{testable[0]['status_code']}{ENDC}") - - print('\n') + + print("\n") # Give back the status. if failed == False: print(f"{OKGREEN}Test Status - PASS{ENDC}") else: print(f"{FAIL}Test Status - FAILED{ENDC}") - - print('\n') - + + print("\n") + return None -def sub_test_update_id_does_exist( - testable -): +def sub_test_update_id_does_exist(testable): - print('--- Sub-test A ---') - print('\n') - print('Description: Check that the an existent object can be updated.') - print('\n') + print("--- Sub-test A ---") + print("\n") + print("Description: Check that the an existent object can be updated.") + print("\n") # Create a flag to indicate failure. failed = False print(f"{WARNING}Request status") print(f"------------{ENDC}") - + # Any items? - if testable[0]['status_code'] == '201': + if testable[0]["status_code"] == "201": print(f"{OKGREEN}{testable[0]['status_code']}{ENDC}") else: # Failure. failed = True print(f"{FAIL}{testable[0]['status_code']}{ENDC}") - - print('\n') + + print("\n") # Give back the status. if failed == False: print(f"{OKGREEN}Test Status - PASS{ENDC}") else: print(f"{FAIL}Test Status - FAILED{ENDC}") - - print('\n') - + + print("\n") + return None -def sub_test_good_wheel_token_bad_prefix( - testable -): +def sub_test_good_wheel_token_bad_prefix(testable): - print('--- Sub-test A ---') - print('\n') - print('Description: Check that the provided prefix was unable to be created.') - print('\n') + print("--- Sub-test A ---") + print("\n") + print("Description: Check that the provided prefix was unable to be created.") + print("\n") # Create a flag to indicate failure. failed = False print(f"{WARNING}Request status") print(f"------------{ENDC}") - + # Any items? - if testable[0]['status_code'] == '409': + if testable[0]["status_code"] == "409": print(f"{OKGREEN}{testable[0]['status_code']}{ENDC}") else: # Failure. failed = True print(f"{FAIL}{testable[0]['status_code']}{ENDC}") - - print('\n') + + print("\n") # Give back the status. if failed == False: print(f"{OKGREEN}Test Status - PASS{ENDC}") else: print(f"{FAIL}Test Status - FAILED{ENDC}") - - print('\n') - - return None - - - - + print("\n") + return None # ------- TESTS ------- # - - - - - - # Source: https://stackoverflow.com/a/287944 # Source: https://www.tutorialspoint.com/python/python_command_line_arguments.htm -def main( - argv -): +def main(argv): - anon_key = '' - hostname = '' - wheel_key = '' + anon_key = "" + hostname = "" + wheel_key = "" try: - opts, args = getopt.getopt(argv,"mp:a:w:",["public-hostname=", "anon-key=", "wheel-key="]) + opts, args = getopt.getopt( + argv, "mp:a:w:", ["public-hostname=", "anon-key=", "wheel-key="] + ) except getopt.GetoptError: - print('Usage: apitests.py -p -a -w ') + print("Usage: apitests.py -p -a -w ") sys.exit(2) for opt, arg in opts: - if opt == '-m': - print('Usage: apitests.py -p -a -w ') + if opt == "-m": + print( + "Usage: apitests.py -p -a -w " + ) sys.exit() elif opt in ("-p", "--public-hostname"): hostname = arg @@ -539,48 +502,42 @@ def main( anon_key = arg elif opt in ("-w", "--wheel-key"): wheel_key = arg - + # Were any arguments provided at all? if opts == []: - print('No arguments provided. Run \'apitests.py -m\' to get usage.') + print("No arguments provided. Run 'apitests.py -m' to get usage.") sys.exit(2) - + # Make sure everything was given. - if hostname == '': - print('Public hostname must be provided.') + if hostname == "": + print("Public hostname must be provided.") sys.exit(2) - - if anon_key == '': - print('Anonymous key must be provided.') + + if anon_key == "": + print("Anonymous key must be provided.") sys.exit(2) - - if wheel_key == '': - print('Wheel key must be provided.') + + if wheel_key == "": + print("Wheel key must be provided.") sys.exit(2) - print('\n\n') - print('******* BCO API Testing Suite *******') - print('\n\n') - + print("\n\n") + print("******* BCO API Testing Suite *******") + print("\n\n") + pretty_output( - hostname = hostname, - method = 'GET', - test_info = { - 'description': 'Ask the API to describe itself using the generic GET method.', - 'expected_response_code': '200 OK', - 'test_number': '1' + hostname=hostname, + method="GET", + test_info={ + "description": "Ask the API to describe itself using the generic GET method.", + "expected_response_code": "200 OK", + "test_number": "1", }, - url = '/api/public/describe/' + url="/api/public/describe/", ) - - - # ----- Wheel tests ----- # - - - # # Try to create a prefix as wheel. # pretty_output( # hostname = hostname, @@ -653,13 +610,7 @@ def main( # url = '/api/prefixes/delete/' # ) - - - # ----- Anon tests ----- # - - - # # Try to create a prefix as the anonymous user. # pretty_output( @@ -685,8 +636,6 @@ def main( # token = anon_key, # url = '/api/prefixes/create/' # ) - - # # Get the prefix permissions for a given token. # pretty_output( @@ -702,7 +651,6 @@ def main( # url = '/api/prefixes/token/' # ) - # # Get the prefix permissions for a given token. # pretty_output( # hostname = hostname, @@ -717,23 +665,18 @@ def main( # url = '/api/prefixes/token/flat/' # ) - - - # # Create a new account and pull the token. r_token_username = pretty_output( - json_send = { - 'email': 'generic3@email.com' + json_send={"email": "generic3@email.com"}, + hostname=hostname, + method="POST", + pull_key=True, + test_info={ + "description": "Create a new account given a generic e-Mail.", + "expected_response_code": "201 Created", + "test_number": "2", }, - hostname = hostname, - method = 'POST', - pull_key = True, - test_info = { - 'description': 'Create a new account given a generic e-Mail.', - 'expected_response_code': '201 Created', - 'test_number': '2' - }, - url = '/api/accounts/new/' + url="/api/accounts/new/", ) # # Describe the newly created account. @@ -801,64 +744,64 @@ def main( # Delete a prefix. pretty_output( - hostname = hostname, - json_send = { - 'POST_api_prefixes_delete': { - 'prefixes': [ + hostname=hostname, + json_send={ + "POST_api_prefixes_delete": { + "prefixes": [ { - 'prefix': 'khyy', + "prefix": "khyy", } ] } }, - method = 'POST', - test_info = { - 'description': 'Delete an existing prefix.', - 'expected_response_code': '403 Forbidden', - 'test_number': '2' + method="POST", + test_info={ + "description": "Delete an existing prefix.", + "expected_response_code": "403 Forbidden", + "test_number": "2", }, - token = r_token_username['token'], - url = '/api/prefixes/delete/' + token=r_token_username["token"], + url="/api/prefixes/delete/", ) # Get the prefix permissions for a given token. pretty_output( - hostname = hostname, - json_send = {}, - method = 'POST', - test_info = { - 'description': 'Get the prefix permissions for a token.', - 'expected_response_code': '200 OK', - 'test_number': '2' + hostname=hostname, + json_send={}, + method="POST", + test_info={ + "description": "Get the prefix permissions for a token.", + "expected_response_code": "200 OK", + "test_number": "2", }, - token = r_token_username['token'], - url = '/api/prefixes/token/' + token=r_token_username["token"], + url="/api/prefixes/token/", ) # Create a test prefix and assign it to our newly created # user. pretty_output( - hostname = hostname, - json_send = { - 'POST_api_prefixes_create': { - 'prefixes': [ + hostname=hostname, + json_send={ + "POST_api_prefixes_create": { + "prefixes": [ { - 'description': 'Generic test prefix.', - 'owner_group': 'bco_publisher', - 'owner_user': r_token_username['username'], - 'prefix': 'TEST', + "description": "Generic test prefix.", + "owner_group": "bco_publisher", + "owner_user": r_token_username["username"], + "prefix": "TEST", } ] } }, - method = 'POST', - test_info = { - 'description': 'Create a test prefix and assign it to our newly created user.', - 'expected_response_code': '200 OK', - 'test_number': '1' + method="POST", + test_info={ + "description": "Create a test prefix and assign it to our newly created user.", + "expected_response_code": "200 OK", + "test_number": "1", }, - token = wheel_key, - url = '/api/prefixes/create/' + token=wheel_key, + url="/api/prefixes/create/", ) # pretty_output( @@ -1050,18 +993,16 @@ def main( # Create a third user so that we can test group # modification logic. r_token_username_third_user = pretty_output( - json_send = { - 'email': 'generic2@email.com' - }, - hostname = hostname, - method = 'POST', - pull_key = True, - test_info = { - 'description': 'Create a new account given a generic e-Mail.', - 'expected_response_code': '201 Created', - 'test_number': '2' + json_send={"email": "generic2@email.com"}, + hostname=hostname, + method="POST", + pull_key=True, + test_info={ + "description": "Create a new account given a generic e-Mail.", + "expected_response_code": "201 Created", + "test_number": "2", }, - url = '/api/accounts/new/' + url="/api/accounts/new/", ) # Modify the groups. @@ -1069,201 +1010,166 @@ def main( # NOTE: can also be used to create a new group # using the optional argument 'new_group'. pretty_output( - hostname = hostname, - json_send = { - 'POST_api_groups_modify': [ + hostname=hostname, + json_send={ + "POST_api_groups_modify": [ { - 'actions': { - 'add_users': [ - 'some_user_that_doesnt_exist', - 'wheel' - ], - 'disinherit_from': [ - 'some_group_that_doesnt_exist', - 'some_other_test_group' + "actions": { + "add_users": ["some_user_that_doesnt_exist", "wheel"], + "disinherit_from": [ + "some_group_that_doesnt_exist", + "some_other_test_group", ], - 'inherit_from': [ - r_token_username_third_user['username'], - 'some_other_group_that_doesnt_exist' + "inherit_from": [ + r_token_username_third_user["username"], + "some_other_group_that_doesnt_exist", ], - 'owner_group': 'this_group_doesnt_exist', - 'owner_user': 'wheel', - 'redescribe': 'Just some other new description.', - 'remove_users': [ - r_token_username['username'], - 'this_user_also_doesnt_exist' + "owner_group": "this_group_doesnt_exist", + "owner_user": "wheel", + "redescribe": "Just some other new description.", + "remove_users": [ + r_token_username["username"], + "this_user_also_doesnt_exist", ], - 'rename': 'the_new_group_name' + "rename": "the_new_group_name", }, - 'name': 'some_test_group' + "name": "some_test_group", } ] }, - method = 'POST', - test_info = { - 'description': 'Modify some groups.', - 'expected_response_code': '200 OK', - 'test_number': '2' + method="POST", + test_info={ + "description": "Modify some groups.", + "expected_response_code": "200 OK", + "test_number": "2", }, - token = r_token_username['token'], - url = '/api/groups/modify/' + token=r_token_username["token"], + url="/api/groups/modify/", ) # Delete the groups. pretty_output( - hostname = hostname, - json_send = { - 'POST_api_groups_delete': { - 'names': [ - 'some_test_group', - 'some_other_test_group', - 'the_new_group_name' + hostname=hostname, + json_send={ + "POST_api_groups_delete": { + "names": [ + "some_test_group", + "some_other_test_group", + "the_new_group_name", ] } }, - method = 'POST', - test_info = { - 'description': 'Delete the groups.', - 'expected_response_code': '200 OK', - 'test_number': '2' + method="POST", + test_info={ + "description": "Delete the groups.", + "expected_response_code": "200 OK", + "test_number": "2", }, - token = r_token_username['token'], - url = '/api/groups/delete/' + token=r_token_username["token"], + url="/api/groups/delete/", ) - - - # --- DRAFTING --- # - - # Set permissions for the BCO prefix. # TODO: put "append" key to add to existing # permissions, false values leads to complete # overwrite. pretty_output( - hostname = hostname, - json_send = { - 'POST_api_prefixes_permissions_set': [ + hostname=hostname, + json_send={ + "POST_api_prefixes_permissions_set": [ { - 'group': [ - 'bco_drafter' - ], - 'permissions': [ - 'change', - 'delete', - 'view' - ], - 'prefix': 'BCO', - 'username': [ - r_token_username['username'] - ], + "group": ["bco_drafter"], + "permissions": ["change", "delete", "view"], + "prefix": "BCO", + "username": [r_token_username["username"]], } ], }, - method = 'POST', - test_info = { - 'description': 'Set the permissions for a user for the BCO prefix.', - 'expected_response_code': '200 OK', - 'test_number': '2' + method="POST", + test_info={ + "description": "Set the permissions for a user for the BCO prefix.", + "expected_response_code": "200 OK", + "test_number": "2", }, - token = wheel_key, - url = '/api/prefixes/permissions/set/' + token=wheel_key, + url="/api/prefixes/permissions/set/", ) - + # Create a draft object. drafted = pretty_output( - hostname = hostname, - json_send = { + hostname=hostname, + json_send={ "POST_api_objects_draft_create": [ { "contents": {}, "owner_group": "bco_drafter", "prefix": "BCO", - "schema": "IEEE" + "schema": "IEEE", } ] }, - method = 'POST', - pull_key = True, - test_info = { - 'description': 'Write a draft object.', - 'expected_response_code': '200 OK', - 'test_number': '8' + method="POST", + pull_key=True, + test_info={ + "description": "Write a draft object.", + "expected_response_code": "200 OK", + "test_number": "8", }, - token = r_token_username['token'], - url = '/api/objects/drafts/create/' + token=r_token_username["token"], + url="/api/objects/drafts/create/", ) - - - - - - - pretty_output( - hostname = hostname, - json_send = { - 'POST_api_objects_drafts_permissions_set': [ + hostname=hostname, + json_send={ + "POST_api_objects_drafts_permissions_set": [ { - 'actions': { - 'full_permissions': { - 'view': { - 'users': [ - r_token_username['username'] - ], - 'groups': [ - 'bco_publisher', - 'bco_drafter' - ] + "actions": { + "full_permissions": { + "view": { + "users": [r_token_username["username"]], + "groups": ["bco_publisher", "bco_drafter"], } } }, - 'object_id': drafted[0]['object_id'] + "object_id": drafted[0]["object_id"], } - ] }, - method = 'POST', - test_info = { - 'description': 'Set some object permissions.', - 'expected_response_code': '200 OK', - 'test_number': '2' + method="POST", + test_info={ + "description": "Set some object permissions.", + "expected_response_code": "200 OK", + "test_number": "2", }, - token = r_token_username['token'], - url = '/api/objects/drafts/permissions/set/' + token=r_token_username["token"], + url="/api/objects/drafts/permissions/set/", ) # Get all the user's objects by token. pretty_output( - hostname = hostname, - json_send = { - 'POST_api_objects_drafts_token': { - 'fields': [ - 'contents', - 'object_id', - 'owner_user' - ] + hostname=hostname, + json_send={ + "POST_api_objects_drafts_token": { + "fields": ["contents", "object_id", "owner_user"] } }, - method = 'POST', - test_info = { - 'description': 'Get the objects available to a user based on their token.', - 'expected_response_code': '200 OK', - 'test_number': '2' + method="POST", + test_info={ + "description": "Get the objects available to a user based on their token.", + "expected_response_code": "200 OK", + "test_number": "2", }, - token = r_token_username['token'], - url = '/api/objects/drafts/token/' + token=r_token_username["token"], + url="/api/objects/drafts/token/", ) - - # Publish. # Publish a draft, publish directly, publish directly. - # + # # contents and draft_id are the "sources" of the object. # contents and draft_id are mutually exclusive keys. # @@ -1348,479 +1254,256 @@ def main( # Get the draft permissions pretty_output( - hostname = hostname, - json_send = { + hostname=hostname, + json_send={ "POST_api_objects_drafts_permissions": [ - { - "object_id": drafted[0]['object_id'] - } + {"object_id": drafted[0]["object_id"]} ] }, - method = 'POST', - pull_key = True, - test_info = { - 'description': 'Get draft permissions.', - 'expected_response_code': '200 OK', - 'test_number': '8' + method="POST", + pull_key=True, + test_info={ + "description": "Get draft permissions.", + "expected_response_code": "200 OK", + "test_number": "8", }, - token = r_token_username['token'], - url = '/api/objects/drafts/permissions/' + token=r_token_username["token"], + url="/api/objects/drafts/permissions/", ) # Modify the draft object. # Notes: prefix, schema, and state should NOT change # after draft is initially created. pretty_output( - hostname = hostname, - json_send = { + hostname=hostname, + json_send={ "POST_api_objects_drafts_modify": [ { - "contents": { - "test_key": "test_value" - }, - "object_id": drafted[0]['object_id'] + "contents": {"test_key": "test_value"}, + "object_id": drafted[0]["object_id"], } ] }, - method = 'POST', - pull_key = True, - test_info = { - 'description': 'Modify a draft object.', - 'expected_response_code': '200 OK', - 'test_number': '8' + method="POST", + pull_key=True, + test_info={ + "description": "Modify a draft object.", + "expected_response_code": "200 OK", + "test_number": "8", }, - token = r_token_username['token'], - url = '/api/objects/drafts/modify/' + token=r_token_username["token"], + url="/api/objects/drafts/modify/", ) # Get the draft permissions pretty_output( - hostname = hostname, - json_send = { + hostname=hostname, + json_send={ "POST_api_objects_drafts_permissions": [ - { - "object_id": drafted[0]['object_id'] - } + {"object_id": drafted[0]["object_id"]} ] }, - method = 'POST', - pull_key = True, - test_info = { - 'description': 'Get draft permissions.', - 'expected_response_code': '200 OK', - 'test_number': '8' + method="POST", + pull_key=True, + test_info={ + "description": "Get draft permissions.", + "expected_response_code": "200 OK", + "test_number": "8", }, - token = r_token_username['token'], - url = '/api/objects/drafts/permissions/' + token=r_token_username["token"], + url="/api/objects/drafts/permissions/", ) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # There should be no objects associated with this account yet. objects = pretty_output( - hostname = hostname, - method = 'POST', - test_info = { - 'description': 'Get all objects available for this token (user).', - 'expected_response_code': '200 OK', - 'test_number': '4' + hostname=hostname, + method="POST", + test_info={ + "description": "Get all objects available for this token (user).", + "expected_response_code": "200 OK", + "test_number": "4", }, - pull_key = True, - token = r_token_username['token'], - url = '/api/objects/token/' + pull_key=True, + token=r_token_username["token"], + url="/api/objects/token/", ) # Conduct the sub-test. - - - # --- No draft ID provided --- # - - - # Conduct the sub-test. - # Conduct the sub-test. - # Request a table that exists and the requestor # is in the provided owner group, and the group # does have write permissions. - # Conduct the sub-test. - - - - # --- Draft ID provided --- # - - - - # Conduct the sub-test. # Attempt to retrieve the draft object via - # Attempt to retrieve the draft object via # the GET method with a valid token. - + # Straight to publishing (no draft) with # an invalid token. pretty_output( - hostname = hostname, - json_send = {}, - method = 'POST', - test_info = { - 'description': 'Try to go straight to publishing (no draft) with an invalid token.', - 'expected_response_code': '401 Unauthorized', - 'test_number': '13' + hostname=hostname, + json_send={}, + method="POST", + test_info={ + "description": "Try to go straight to publishing (no draft) with an invalid token.", + "expected_response_code": "401 Unauthorized", + "test_number": "13", }, - token = 'this_token_should_not_exist', - url = '/api/objects/publish/' + token="this_token_should_not_exist", + url="/api/objects/publish/", ) # Straight to publishing (no draft) with # a valid token, but with an incorrect publishing group. pretty_output( - hostname = hostname, - json_send = { + hostname=hostname, + json_send={ "POST_api_objects_publish": [ - { - "contents": {}, - "owner_group": "bco_drafter", - "prefix": "BCO" - } + {"contents": {}, "owner_group": "bco_drafter", "prefix": "BCO"} ] }, - method = 'POST', - test_info = { - 'description': 'Try to go straight to publishing (no draft) with a valid token.', - 'expected_response_code': '403 Forbidden', - 'test_number': '14' + method="POST", + test_info={ + "description": "Try to go straight to publishing (no draft) with a valid token.", + "expected_response_code": "403 Forbidden", + "test_number": "14", }, - token = r_token_username['token'], - url = '/api/objects/publish/' + token=r_token_username["token"], + url="/api/objects/publish/", ) # Straight to publishing (no draft) with # a valid token and a correct publishing group. published = pretty_output( - hostname = hostname, - json_send = { - "POST_api_objects_publish": [ - { - "contents": {}, - "prefix": "BCO" - } - ] - }, - method = 'POST', - pull_key = True, - test_info = { - 'description': 'Straight to publishing (no draft) with a valid token, and a correct publishing group.', - 'expected_response_code': '200 OK', - 'test_number': '15' + hostname=hostname, + json_send={"POST_api_objects_publish": [{"contents": {}, "prefix": "BCO"}]}, + method="POST", + pull_key=True, + test_info={ + "description": "Straight to publishing (no draft) with a valid token, and a correct publishing group.", + "expected_response_code": "200 OK", + "test_number": "15", }, - token = r_token_username['token'], - url = '/api/objects/publish/' + token=r_token_username["token"], + url="/api/objects/publish/", ) - - # View the directly published object. pretty_output( - direct_call = True, - hostname = hostname, - method = 'GET', - test_info = { - 'description': 'View the directly published object.', - 'expected_response_code': '200 OK', - 'test_number': '16' + direct_call=True, + hostname=hostname, + method="GET", + test_info={ + "description": "View the directly published object.", + "expected_response_code": "200 OK", + "test_number": "16", }, - url = published[0]['published_id'] + url=published[0]["published_id"], ) - - - #Try to publish a draft object that doesn't exist. + # Try to publish a draft object that doesn't exist. published = pretty_output( - hostname = hostname, - json_send = { + hostname=hostname, + json_send={ "POST_api_objects_drafts_publish": [ { "draft_id": "this_object_id_should_not_exist", "schema": "IEEE", - "prefix": "BCO" + "prefix": "BCO", } ] }, - method = 'POST', - test_info = { - 'description': 'Try to publish a draft object that doesn\'t exist.', - 'expected_response_code': '200 OK', - 'test_number': '17' + method="POST", + test_info={ + "description": "Try to publish a draft object that doesn't exist.", + "expected_response_code": "200 OK", + "test_number": "17", }, - token = r_token_username['token'], - url = '/api/objects/drafts/publish/' + token=r_token_username["token"], + url="/api/objects/drafts/publish/", ) - + # Note that the table that a draft exists on and its corresponding # publish table do not have to be the same! pretty_output( - hostname = hostname, - json_send = { + hostname=hostname, + json_send={ "POST_api_objects_drafts_publish": [ - { - "draft_id": drafted[0]['object_id'], - "schema": "IEEE", - "prefix": "BCO" - } + {"draft_id": drafted[0]["object_id"], "schema": "IEEE", "prefix": "BCO"} ] }, - method = 'POST', - test_info = { - 'description': 'Try to publish a draft object that doesn\'t exist.', - 'expected_response_code': '200 OK', - 'test_number': '17' + method="POST", + test_info={ + "description": "Try to publish a draft object that doesn't exist.", + "expected_response_code": "200 OK", + "test_number": "17", }, - token = r_token_username['token'], - url = '/api/objects/drafts/publish/' + token=r_token_username["token"], + url="/api/objects/drafts/publish/", ) pretty_output( - hostname = hostname, - json_send = { + hostname=hostname, + json_send={ "POST_api_objects_drafts_publish": [ { "contents": {}, "draft_id": "http://127.0.0.1:8000/BCO_00027/1", "schema": "IEEE", - "prefix": "BCO" + "prefix": "BCO", } ] }, - method = 'POST', - test_info = { - 'description': 'Try to publish a draft object that doesn\'t exist.', - 'expected_response_code': '200 OK', - 'test_number': '17' + method="POST", + test_info={ + "description": "Try to publish a draft object that doesn't exist.", + "expected_response_code": "200 OK", + "test_number": "17", }, - token = r_token_username['token'], - url = '/api/objects/drafts/publish/' + token=r_token_username["token"], + url="/api/objects/drafts/publish/", ) # Try to publish a draft object with destroying # the draft. - - # ----- non-object tests ----- # - - # Try to create a prefix using a bad token. pretty_output( - hostname = hostname, - json_send = { + hostname=hostname, + json_send={ "POST_api_prefixes_create": [ { "prefix": "GLY", } ] }, - method = 'POST', - test_info = { - 'description': 'Try to create a prefix using a bad token.', - 'expected_response_code': '401 Unauthorized', - 'test_number': '19' + method="POST", + test_info={ + "description": "Try to create a prefix using a bad token.", + "expected_response_code": "401 Unauthorized", + "test_number": "19", }, - token = 'this_token_should_not_exist', - url = '/api/prefixes/create/' + token="this_token_should_not_exist", + url="/api/prefixes/create/", ) - + # --- works --- # - + # # Create a malformed prefix using the wheel token. # prefixed = pretty_output( # hostname = hostname, @@ -1848,22 +1531,22 @@ def main( # Create a valid prefix using the wheel token. prefixed = pretty_output( - hostname = hostname, - json_send = { + hostname=hostname, + json_send={ "POST_api_prefixes_create": [ { "prefix": "TEST", } ] }, - method = 'POST', - test_info = { - 'description': 'Create a valid prefix using the wheel token.', - 'expected_response_code': '200 OK', - 'test_number': '19' + method="POST", + test_info={ + "description": "Create a valid prefix using the wheel token.", + "expected_response_code": "200 OK", + "test_number": "19", }, - token = 'db36da5a582701a7cb6131c64cde3439c189e220', - url = '/api/prefixes/create/' + token="db36da5a582701a7cb6131c64cde3439c189e220", + url="/api/prefixes/create/", ) # # Create a new prefix. @@ -1887,11 +1570,6 @@ def main( # ) - - - - - # Take command line arguments for the tests. -if __name__ == '__main__': - main(sys.argv[1:]) \ No newline at end of file +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/apitests_new.py b/apitests_new.py index 897698c4..785497e5 100644 --- a/apitests_new.py +++ b/apitests_new.py @@ -2,133 +2,124 @@ import requests +HEADER = "\033[95m" +OKBLUE = "\033[94m" +OKCYAN = "\033[96m" +OKGREEN = "\033[92m" +WARNING = "\033[93m" +FAIL = "\033[91m" +ENDC = "\033[0m" +BOLD = "\033[1m" +UNDERLINE = "\033[4m" -HEADER = '\033[95m' -OKBLUE = '\033[94m' -OKCYAN = '\033[96m' -OKGREEN = '\033[92m' -WARNING = '\033[93m' -FAIL = '\033[91m' -ENDC = '\033[0m' -BOLD = '\033[1m' -UNDERLINE = '\033[4m' - def pretty_output( hostname, method, test_info, url, - direct_call = False, - json_send = False, - pull_key = False, - token = False + direct_call=False, + json_send=False, + pull_key=False, + token=False, ): # Describe the test. - print('\n') + print("\n") print(f"{BOLD}======= Test {test_info['test_number']} of 16 ======={ENDC}") - print('\n') - print('Description: ' + test_info['description']) - print('\n') - + print("\n") + print("Description: " + test_info["description"]) + print("\n") + # Is the call constructed or direct? - call = '' + call = "" if direct_call == True: call = url else: call = hostname + url - + # Make the request. - r = '' + r = "" + + if method == "GET": - if method == 'GET': - if token != False: - hdrs = { - 'Authorization': 'Token {}'.format(token) - } + hdrs = {"Authorization": "Token {}".format(token)} print(f"{OKCYAN}Call ({method}): {call}") print(f"Headers: {json.dumps(hdrs, indent = 4)}{ENDC}") - print('\n') - - r = requests.get( - call, - headers = hdrs - ) + print("\n") + + r = requests.get(call, headers=hdrs) else: print(f"{OKCYAN}Call ({method}): {call}") print(f"Headers: None") - print('\n') + print("\n") + + r = requests.get(call) + + elif method == "POST": - r = requests.get( - call - ) - - elif method == 'POST': - if token != False: - - hdrs = { - 'Authorization': 'Token {}'.format(token) - } - + + hdrs = {"Authorization": "Token {}".format(token)} + print(f"{OKCYAN}Call ({method}): {call}") print(f"Headers: {json.dumps(hdrs, indent = 4)}") print(f"Request body: {json.dumps(json_send, indent = 4)}{ENDC}") - print('\n') - - r = requests.post( - call, - json = json_send, - headers = hdrs - ) - + print("\n") + + r = requests.post(call, json=json_send, headers=hdrs) + else: print(f"{OKCYAN}Call ({method}): {call}") print(f"Headers: None") print(f"Request body: {json.dumps(json_send, indent = 4)}{ENDC}") - print('\n') - - r = requests.post( - call, - json = json_send - ) - + print("\n") + + r = requests.post(call, json=json_send) + print(f"{WARNING}+++ TEST RESULTS +++") - print('\n') - + print("\n") + # What did we get? - if str(r.status_code) + ' ' + r.reason == test_info['expected_response_code']: + if str(r.status_code) + " " + r.reason == test_info["expected_response_code"]: + + print( + f"{BOLD}{OKGREEN}Expected response code: {test_info['expected_response_code']}" + ) + print( + f"{OKGREEN}Receieved response code: {str(r.status_code) + ' ' + r.reason}{ENDC}" + ) + print("\n") - print(f"{BOLD}{OKGREEN}Expected response code: {test_info['expected_response_code']}") - print(f"{OKGREEN}Receieved response code: {str(r.status_code) + ' ' + r.reason}{ENDC}") - print('\n') - else: - print(f"{BOLD}{FAIL}Expected response code: {test_info['expected_response_code']}") - print(f"{FAIL}Receieved response code: {str(r.status_code) + ' ' + r.reason}{ENDC}") - print('\n') - + print( + f"{BOLD}{FAIL}Expected response code: {test_info['expected_response_code']}" + ) + print( + f"{FAIL}Receieved response code: {str(r.status_code) + ' ' + r.reason}{ENDC}" + ) + print("\n") + try: - dumped = json.dumps(json.loads(r.text), indent = 4) + dumped = json.dumps(json.loads(r.text), indent=4) print(f"{OKBLUE}Response") - print('--------') + print("--------") print(f"{dumped}{ENDC}") - print('\n') + print("\n") # Do we need to pull anything? if pull_key != False: - + # Is the pull the entire response or # just one of the keys? @@ -136,25 +127,16 @@ def pretty_output( return json.loads(r.text) else: return json.loads(r.text)[pull_key] - + except json.decoder.JSONDecodeError: print(f"{BOLD}{FAIL}Response") print(f"--------") print(f"Response failed...{ENDC}") - print('\n') + print("\n") - - -def relationships( - hn, - oi, - user1, - group1, - group2, - wheel_key -): +def relationships(hn, oi, user1, group1, group2, wheel_key): print("------------Relationship Tests ------------") # Go through all possible user/group object permissions combinatorially. @@ -164,57 +146,54 @@ def relationships( # Set each test, then compare the object permission # results with what we would expect. - for value in [1,2,3]: - for perm in ['change', 'delete', 'view']: - - # Don't share prefix permissions. + for value in [1, 2, 3]: + for perm in ["change", "delete", "view"]: + + # Don't share prefix permissions. - # We can set an arbitrary prefix permission. + # We can set an arbitrary prefix permission. pretty_output( - hostname = hn, - json_send = { - 'POST_api_prefixes_permissions_set': [ + hostname=hn, + json_send={ + "POST_api_prefixes_permissions_set": [ { - 'group': [ - 'bco_drafter' - ], - 'permissions': [ - 'view' - ], - 'prefix': 'TEST', - 'username': [ - user1['username'] - ], + "group": ["bco_drafter"], + "permissions": ["view"], + "prefix": "TEST", + "username": [user1["username"]], } ], }, - method = 'POST', - test_info = { - 'description': 'Set the prefix permissions for a user or a group.', - 'expected_response_code': '200 OK' + method="POST", + test_info={ + "description": "Set the prefix permissions for a user or a group.", + "expected_response_code": "200 OK", }, - token = user1['token'], - url = '/api/prefixes/permissions/set/' + token=user1["token"], + url="/api/prefixes/permissions/set/", ) for possibly in [0, 1]: - for perm in ['change', 'delete', 'view']: + for perm in ["change", "delete", "view"]: print("hi") - # Share prefix permissions - + # Share prefix permissions -def superuser (): + +def superuser(): print("info") + def account(): print("info") + def publishing(): print("info") + def drafts(): print("info") def group(): - print("info") \ No newline at end of file + print("info") diff --git a/bco_api/api/apps.py b/bco_api/api/apps.py index 84e9962e..03bac099 100755 --- a/bco_api/api/apps.py +++ b/bco_api/api/apps.py @@ -9,12 +9,12 @@ from django.db.models.signals import post_migrate from api.signals import populate_models + class ApiConfig(AppConfig): - """API Configuration - """ + """API Configuration""" - default_auto_field = 'django.db.models.AutoField' - name = 'api' + default_auto_field = "django.db.models.AutoField" + name = "api" def ready(self): """Create the anonymous user if they don't exist.""" diff --git a/bco_api/api/migrations/0001_initial.py b/bco_api/api/migrations/0001_initial.py index 85b5043f..538b352a 100644 --- a/bco_api/api/migrations/0001_initial.py +++ b/bco_api/api/migrations/0001_initial.py @@ -11,64 +11,149 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), + ("auth", "0012_alter_user_first_name_max_length"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='meta_table', + name="meta_table", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('n_objects', models.IntegerField()), - ('prefix', models.CharField(max_length=5)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("n_objects", models.IntegerField()), + ("prefix", models.CharField(max_length=5)), ], ), migrations.CreateModel( - name='new_users', + name="new_users", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('email', models.EmailField(max_length=254)), - ('temp_identifier', models.TextField(max_length=100)), - ('token', models.TextField(blank=True, null=True)), - ('hostname', models.TextField(blank=True, null=True)), - ('created', models.DateTimeField(default=django.utils.timezone.now)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("email", models.EmailField(max_length=254)), + ("temp_identifier", models.TextField(max_length=100)), + ("token", models.TextField(blank=True, null=True)), + ("hostname", models.TextField(blank=True, null=True)), + ("created", models.DateTimeField(default=django.utils.timezone.now)), ], ), migrations.CreateModel( - name='prefixes', + name="prefixes", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('prefix', models.CharField(max_length=5)), - ('owner_group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.group', to_field='name')), - ('owner_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, to_field='username')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("prefix", models.CharField(max_length=5)), + ( + "owner_group", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="auth.group", + to_field="name", + ), + ), + ( + "owner_user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + to_field="username", + ), + ), ], ), migrations.CreateModel( - name='group_info', + name="group_info", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('delete_members_on_group_deletion', models.BooleanField(default=False)), - ('description', models.TextField()), - ('expiration', models.DateTimeField(blank=True, null=True)), - ('max_n_members', models.IntegerField(blank=True, null=True)), - ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.group', to_field='name')), - ('owner_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, to_field='username')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "delete_members_on_group_deletion", + models.BooleanField(default=False), + ), + ("description", models.TextField()), + ("expiration", models.DateTimeField(blank=True, null=True)), + ("max_n_members", models.IntegerField(blank=True, null=True)), + ( + "group", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="auth.group", + to_field="name", + ), + ), + ( + "owner_user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + to_field="username", + ), + ), ], ), migrations.CreateModel( - name='bco', + name="bco", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('contents', models.JSONField()), - ('object_class', models.TextField(blank=True, null=True)), - ('object_id', models.TextField()), - ('prefix', models.CharField(max_length=5)), - ('schema', models.TextField()), - ('state', models.TextField()), - ('last_update', models.DateTimeField()), - ('owner_group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.group', to_field='name')), - ('owner_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, to_field='username')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("contents", models.JSONField()), + ("object_class", models.TextField(blank=True, null=True)), + ("object_id", models.TextField()), + ("prefix", models.CharField(max_length=5)), + ("schema", models.TextField()), + ("state", models.TextField()), + ("last_update", models.DateTimeField()), + ( + "owner_group", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="auth.group", + to_field="name", + ), + ), + ( + "owner_user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + to_field="username", + ), + ), ], ), - ] \ No newline at end of file + ] diff --git a/bco_api/api/migrations/0002_auto_20220124_2356.py b/bco_api/api/migrations/0002_auto_20220124_2356.py index 2f3333ad..778906d1 100644 --- a/bco_api/api/migrations/0002_auto_20220124_2356.py +++ b/bco_api/api/migrations/0002_auto_20220124_2356.py @@ -10,43 +10,51 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('api', '0001_initial'), + ("api", "0001_initial"), ] operations = [ migrations.AddField( - model_name='prefixes', - name='certifying_key', + model_name="prefixes", + name="certifying_key", field=models.TextField(blank=True, null=True), ), migrations.AddField( - model_name='prefixes', - name='certifying_server', + model_name="prefixes", + name="certifying_server", field=models.TextField(blank=True, null=True), ), migrations.AddField( - model_name='prefixes', - name='created', - field=models.DateTimeField(blank=True, default=django.utils.timezone.now, null=True), + model_name="prefixes", + name="created", + field=models.DateTimeField( + blank=True, default=django.utils.timezone.now, null=True + ), ), migrations.AddField( - model_name='prefixes', - name='created_by', - field=models.ForeignKey(default='wheel', on_delete=django.db.models.deletion.CASCADE, related_name='created_by', to=settings.AUTH_USER_MODEL, to_field='username'), + model_name="prefixes", + name="created_by", + field=models.ForeignKey( + default="wheel", + on_delete=django.db.models.deletion.CASCADE, + related_name="created_by", + to=settings.AUTH_USER_MODEL, + to_field="username", + ), ), migrations.AddField( - model_name='prefixes', - name='description', + model_name="prefixes", + name="description", field=models.TextField(blank=True, null=True), ), migrations.AddField( - model_name='prefixes', - name='expires', + model_name="prefixes", + name="expires", field=models.DateTimeField(blank=True, null=True), ), migrations.AlterField( - model_name='group_info', - name='description', + model_name="group_info", + name="description", field=models.TextField(blank=True), ), ] diff --git a/bco_api/api/migrations/0003_rename_meta_table_prefix_table.py b/bco_api/api/migrations/0003_rename_meta_table_prefix_table.py index 3ee33446..697f102d 100644 --- a/bco_api/api/migrations/0003_rename_meta_table_prefix_table.py +++ b/bco_api/api/migrations/0003_rename_meta_table_prefix_table.py @@ -6,12 +6,12 @@ class Migration(migrations.Migration): dependencies = [ - ('api', '0002_auto_20220124_2356'), + ("api", "0002_auto_20220124_2356"), ] operations = [ migrations.RenameModel( - old_name='meta_table', - new_name='prefix_table', + old_name="meta_table", + new_name="prefix_table", ), ] diff --git a/bco_api/api/migrations/0004_rename_group_info_groupinfo.py b/bco_api/api/migrations/0004_rename_group_info_groupinfo.py index c16736de..94c31c1f 100644 --- a/bco_api/api/migrations/0004_rename_group_info_groupinfo.py +++ b/bco_api/api/migrations/0004_rename_group_info_groupinfo.py @@ -8,13 +8,13 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('auth', '0012_alter_user_first_name_max_length'), - ('api', '0003_rename_meta_table_prefix_table'), + ("auth", "0012_alter_user_first_name_max_length"), + ("api", "0003_rename_meta_table_prefix_table"), ] operations = [ migrations.RenameModel( - old_name='group_info', - new_name='GroupInfo', + old_name="group_info", + new_name="GroupInfo", ), ] diff --git a/bco_api/api/migrations/0005_rename_prefixes_prefix.py b/bco_api/api/migrations/0005_rename_prefixes_prefix.py index 639b7fc8..d253bdfc 100644 --- a/bco_api/api/migrations/0005_rename_prefixes_prefix.py +++ b/bco_api/api/migrations/0005_rename_prefixes_prefix.py @@ -8,13 +8,13 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('auth', '0012_alter_user_first_name_max_length'), - ('api', '0004_rename_group_info_groupinfo'), + ("auth", "0012_alter_user_first_name_max_length"), + ("api", "0004_rename_group_info_groupinfo"), ] operations = [ migrations.RenameModel( - old_name='prefixes', - new_name='Prefix', + old_name="prefixes", + new_name="Prefix", ), ] diff --git a/bco_api/api/model/groups.py b/bco_api/api/model/groups.py index 5e769362..83af2e9a 100755 --- a/bco_api/api/model/groups.py +++ b/bco_api/api/model/groups.py @@ -28,46 +28,45 @@ class GroupInfo(models.Model): delete_members_on_group_deletion = models.BooleanField(default=False) description = models.TextField(blank=True) expiration = models.DateTimeField(blank=True, null=True) - group = models.ForeignKey(Group, on_delete=models.CASCADE, to_field='name') + group = models.ForeignKey(Group, on_delete=models.CASCADE, to_field="name") max_n_members = models.IntegerField(blank=True, null=True) - owner_user = models.ForeignKey(User, on_delete=models.CASCADE, to_field='username') + owner_user = models.ForeignKey(User, on_delete=models.CASCADE, to_field="username") def __str__(self): """String for representing the GroupInfo model (in Admin site etc.).""" return f"{self.group}" + def post_api_groups_info(request): - """Retrieve Group information by user - - """ + """Retrieve Group information by user""" user = usr_utils.user_from_request(request=request) - bulk_request = request.data['POST_api_groups_info'] + bulk_request = request.data["POST_api_groups_info"] group_info = [] - for index, value in enumerate(bulk_request['names']): + for index, value in enumerate(bulk_request["names"]): group = Group.objects.get(name=value) try: admin = GroupInfo.objects.get(group=value).owner_user == user description = GroupInfo.objects.get(group=value).description except GroupInfo.DoesNotExist: - admin=False - description = 'N/A' + admin = False + description = "N/A" group_permissions = list( - group.permissions.all().values_list('codename', flat=True) + group.permissions.all().values_list("codename", flat=True) ) - group_members = list( - group.user_set.all().values_list('username', flat=True) + group_members = list(group.user_set.all().values_list("username", flat=True)) + group_info.append( + { + "name": group.name, + "permissions": group_permissions, + "members": group_members, + "admin": admin, + "description": description, + } ) - group_info.append({ - 'name': group.name, - 'permissions': group_permissions, - 'members': group_members, - 'admin': admin, - 'description': description - }) # print(usr_utils.get_user_groups_by_username(un=username)) # user.get_all_permissions() @@ -95,90 +94,96 @@ def post_api_groups_create(request): this is necessary since the request to create the group doesn't have a concept of type bool. Add users which exist and give an error for those that don't. - + As this view is for a bulk operation, status 200 means that the request was successfully processed, but NOT necessarily each item in the request. """ - bulk_request = request.data['POST_api_groups_create'] + bulk_request = request.data["POST_api_groups_create"] group_admin = usr_utils.user_from_request(request=request) - groups = list(Group.objects.all().values_list('name', flat=True)) + groups = list(Group.objects.all().values_list("name", flat=True)) return_data = [] any_failed = False for creation_object in bulk_request: - standardized = creation_object['name'].lower() + standardized = creation_object["name"].lower() if standardized not in groups: - if 'usernames' not in creation_object: - creation_object['usernames'] = [] - if 'delete_members_on_group_deletion' not in creation_object: - creation_object['delete_members_on_group_deletion'] = False + if "usernames" not in creation_object: + creation_object["usernames"] = [] + if "delete_members_on_group_deletion" not in creation_object: + creation_object["delete_members_on_group_deletion"] = False - if 'description' not in creation_object: - creation_object['description'] = '' + if "description" not in creation_object: + creation_object["description"] = "" - if 'max_n_members' not in creation_object: - creation_object['max_n_members'] = -1 + if "max_n_members" not in creation_object: + creation_object["max_n_members"] = -1 - Group.objects.create(name=creation_object['name']) - group_admin.groups.add( - Group.objects.get(name=creation_object['name']) - ) + Group.objects.create(name=creation_object["name"]) + group_admin.groups.add(Group.objects.get(name=creation_object["name"])) - if 'expiration' not in creation_object or \ - creation_object['expiration'] == '': + if ( + "expiration" not in creation_object + or creation_object["expiration"] == "" + ): GroupInfo.objects.create( delete_members_on_group_deletion=bool( - creation_object['delete_members_on_group_deletion'] + creation_object["delete_members_on_group_deletion"] ), - description=creation_object['description'], - group=Group.objects.get(name=creation_object['name']), - max_n_members=creation_object['max_n_members'], - owner_user=group_admin + description=creation_object["description"], + group=Group.objects.get(name=creation_object["name"]), + max_n_members=creation_object["max_n_members"], + owner_user=group_admin, ) else: GroupInfo.objects.create( delete_members_on_group_deletion=bool( - creation_object['delete_members_on_group_deletion'] + creation_object["delete_members_on_group_deletion"] ), - description=creation_object['description'], - expiration=creation_object['expiration'], - group=Group.objects.get( - name=creation_object['name'] - ), - max_n_members=creation_object['max_n_members'], - owner_user=group_admin + description=creation_object["description"], + expiration=creation_object["expiration"], + group=Group.objects.get(name=creation_object["name"]), + max_n_members=creation_object["max_n_members"], + owner_user=group_admin, ) - users_added =[] + users_added = [] users_excluded = [] - for usrnm in creation_object['usernames']: + for usrnm in creation_object["usernames"]: if usr_utils.check_user_exists(un=usrnm): User.objects.get(username=usrnm).groups.add( - Group.objects.get(name=creation_object['name']) + Group.objects.get(name=creation_object["name"]) ) users_added.append(usrnm) else: users_excluded.append(usrnm) - if len(users_excluded) > 0 : - return_data.append(db_utils.messages(parameters={ - 'group': standardized, - 'users_excluded': users_excluded - })['201_group_users_excluded']) + if len(users_excluded) > 0: + return_data.append( + db_utils.messages( + parameters={ + "group": standardized, + "users_excluded": users_excluded, + } + )["201_group_users_excluded"] + ) else: - return_data.append(db_utils.messages( - parameters={'group': standardized})['201_group_create'] + return_data.append( + db_utils.messages(parameters={"group": standardized})[ + "201_group_create" + ] ) else: # Update the request status. - return_data.append(db_utils.messages( - parameters={'group': standardized})['409_group_conflict'] + return_data.append( + db_utils.messages(parameters={"group": standardized})[ + "409_group_conflict" + ] ) any_failed = True @@ -191,7 +196,7 @@ def post_api_groups_create(request): def post_api_groups_delete(request): """Instantiate any necessary imports.""" - bulk_request = request.data['POST_api_groups_delete']['names'] + bulk_request = request.data["POST_api_groups_delete"]["names"] # Establish who has made the request. requestor_info = usr_utils.user_from_request(request=request) @@ -200,7 +205,7 @@ def post_api_groups_delete(request): # This is a better solution than querying for # each individual group name. - groups = list(Group.objects.all().values_list('name', flat=True)) + groups = list(Group.objects.all().values_list("name", flat=True)) # Construct an array to return information about processing # the request. @@ -227,29 +232,41 @@ def post_api_groups_delete(request): User.objects.filter(groups__name=grouped.name).delete() # Delete the group itself. deleted_count, deleted_info = grouped.delete() - if deleted_count < 3: + if deleted_count < 2: # Too few deleted, error with this delete - returning.append(db_utils.messages(parameters={ - 'group': grouped.name})['404_missing_bulk_parameters']) + return_data.append( + db_utils.messages(parameters={"group": grouped.name})[ + "404_missing_bulk_parameters" + ] + ) any_failed = True continue - elif deleted_count > 3: - print(deleted_count, 'deleted_count') + elif deleted_count > 2: + print(deleted_count, "deleted_count") # We don't expect there to be duplicates, so while this was successful it should throw a warning - returning.append(db_utils.messages(parameters={ - 'group': grouped.name})['418_too_many_deleted']) + return_data.append( + db_utils.messages(parameters={"group": grouped.name})[ + "418_too_many_deleted" + ] + ) any_failed = True continue # Everything looks OK - return_data.append(db_utils.messages(parameters={'group': grouped.name})['200_OK_group_delete']) + return_data.append( + db_utils.messages(parameters={"group": grouped.name})[ + "200_OK_group_delete" + ] + ) else: # Requestor is not the admin. - return_data.append(db_utils.messages(parameters={})['403_insufficient_permissions']) + return_data.append( + db_utils.messages(parameters={})["403_insufficient_permissions"] + ) any_failed = True else: # Update the request status. - return_data.append(db_utils.messages(parameters={})['400_bad_request']) + return_data.append(db_utils.messages(parameters={})["400_bad_request"]) any_failed = True if any_failed: @@ -261,15 +278,18 @@ def post_api_groups_delete(request): def post_api_groups_modify(request): """Instantiate any necessary imports.""" - bulk_request = request.data['POST_api_groups_modify'] + bulk_request = request.data["POST_api_groups_modify"] requestor_info = usr_utils.user_from_request(request=request) - groups = list(Group.objects.all().values_list('name', flat=True)) + groups = list(Group.objects.all().values_list("name", flat=True)) return_data = [] for modification_object in bulk_request: - standardized = modification_object['name'].lower() + standardized = modification_object["name"].lower() if standardized in groups: grouped = Group.objects.get(name=standardized) - if requestor_info.is_superuser == True or grouped in requestor_info.groups.all(): + if ( + requestor_info.is_superuser == True + or grouped in requestor_info.groups.all() + ): # TODO: We shouldn't use a try/except as an if statement; I think there is actually # a get_or_create() function: # group_information = GroupInfo.objects.get_or_create(group=grouped, owner_user=requestor_info) @@ -280,8 +300,8 @@ def post_api_groups_modify(request): group_information = GroupInfo.objects.create( group=grouped, owner_user=requestor_info ) - if 'actions' in modification_object: - action_set = modification_object['actions'] + if "actions" in modification_object: + action_set = modification_object["actions"] # Invalid inputs don't throw 400, 401, or 403 for the # request. That is, provided parameters that don't @@ -289,38 +309,38 @@ def post_api_groups_modify(request): # simply get skipped over. # First do the "easy" tasks - name and description. # Change name of group if set in actions - if 'rename' in action_set: + if "rename" in action_set: # Simply re-name to whatever we've been provided, # assuming the group doesn't already exist. - if action_set['rename'] not in groups: - grouped.name = action_set['rename'] + if action_set["rename"] not in groups: + grouped.name = action_set["rename"] grouped.save() # Change description of group if set in actions. - if 'redescribe' in action_set: - group_information.description = action_set['redescribe'] + if "redescribe" in action_set: + group_information.description = action_set["redescribe"] group_information.save() # Now the ownership tasks. # TODO: Is owner_group defined for this type of object? # Does not appear to be set, also does not appear to be inherited. # WARNING: This could cause an error if this is sent in! - if 'owner_group' in action_set: + if "owner_group" in action_set: # Make sure the provided owner group exists. - if usr_utils.check_group_exists(n=action_set['owner_group']): + if usr_utils.check_group_exists(n=action_set["owner_group"]): group_information.owner_group = Group.objects.get( - name=action_set['owner_group'] + name=action_set["owner_group"] ) group_information.save() else: # TODO: This seems to be some type of error state pass - if 'owner_user' in action_set: + if "owner_user" in action_set: # Make sure the provided owner user exists. - if usr_utils.check_user_exists(un=action_set['owner_user']): + if usr_utils.check_user_exists(un=action_set["owner_user"]): group_information.owner_user = User.objects.get( - username=action_set['owner_user'] + username=action_set["owner_user"] ) group_information.save() else: @@ -335,20 +355,22 @@ def post_api_groups_modify(request): # Removals are processed first, then additions. # Remove the users provided, if any. - if 'remove_users' in action_set: - users = User.objects.filter(username__in=action_set['remove_users']) + if "remove_users" in action_set: + users = User.objects.filter( + username__in=action_set["remove_users"] + ) for user in users: user.groups.remove(grouped) # Get the users in the groups provided, if any. - if 'disinherit_from' in action_set: + if "disinherit_from" in action_set: # Get all the groups first, then get the user list. rm_group_users = list( User.objects.filter( groups__in=Group.objects.filter( - name__in=action_set['disinherit_from'] + name__in=action_set["disinherit_from"] ) - ).values_list('username', flat=True) + ).values_list("username", flat=True) ) all_users = all_users - set(rm_group_users) @@ -356,31 +378,39 @@ def post_api_groups_modify(request): # Addition explained at https://stackoverflow.com/a/1306663 # Add the users provided, if any. - if 'add_users' in action_set: - users = User.objects.filter(username__in=action_set['add_users']) + if "add_users" in action_set: + users = User.objects.filter( + username__in=action_set["add_users"] + ) for user in users: user.groups.add(grouped) # Get the users in the groups provided, if any. - if 'inherit_from' in action_set: + if "inherit_from" in action_set: # Get all the groups first, then get the user list. a_group_users = list( User.objects.filter( groups__in=Group.objects.filter( - name__in=action_set['inherit_from'] + name__in=action_set["inherit_from"] ) - ).values_list('username', flat=True) + ).values_list("username", flat=True) ) all_users.update(a_group_users) else: pass - return_data.append(db_utils.messages(parameters={'group': grouped.name})['200_OK_group_modify']) + return_data.append( + db_utils.messages(parameters={"group": grouped.name})[ + "200_OK_group_modify" + ] + ) else: # Requestor is not the admin. - return_data.append(db_utils.messages(parameters={})['403_insufficient_permissions']) + return_data.append( + db_utils.messages(parameters={})["403_insufficient_permissions"] + ) else: # Update the request status. - return_data.append(db_utils.messages(parameters={})['400_bad_request']) + return_data.append(db_utils.messages(parameters={})["400_bad_request"]) # As this view is for a bulk operation, status 200 # means that the request was successfully processed, @@ -403,6 +433,10 @@ def associate_user_group(sender, instance, created, **kwargs): Group.objects.create(name=instance) group = Group.objects.get(name=instance) group.user_set.add(instance) - if instance.username not in ['anon', 'bco_drafter', 'bco_publisher']: - User.objects.get(username=instance).groups.add(Group.objects.get(name='bco_drafter')) - User.objects.get(username=instance).groups.add(Group.objects.get(name='bco_publisher')) + if instance.username not in ["anon", "bco_drafter", "bco_publisher"]: + User.objects.get(username=instance).groups.add( + Group.objects.get(name="bco_drafter") + ) + User.objects.get(username=instance).groups.add( + Group.objects.get(name="bco_publisher") + ) diff --git a/bco_api/api/model/prefix.py b/bco_api/api/model/prefix.py index 0567b0dc..d64b4102 100755 --- a/bco_api/api/model/prefix.py +++ b/bco_api/api/model/prefix.py @@ -35,6 +35,7 @@ def __str__(self): """String for representing the BCO model (in Admin site etc.).""" return self.prefix + class Prefix(models.Model): """Link Prefix to groups and users. @@ -44,26 +45,27 @@ class Prefix(models.Model): What is the certifying key? """ - certifying_server = models.TextField(blank = True, null = True) - certifying_key = models.TextField(blank = True, null = True) - created = models.DateTimeField(default = timezone.now, blank = True, null = True) + certifying_server = models.TextField(blank=True, null=True) + certifying_key = models.TextField(blank=True, null=True) + created = models.DateTimeField(default=timezone.now, blank=True, null=True) created_by = models.ForeignKey( User, - on_delete = models.CASCADE, - related_name = 'created_by', - to_field = 'username', - default='wheel' + on_delete=models.CASCADE, + related_name="created_by", + to_field="username", + default="wheel", ) - description = models.TextField(blank = True, null = True) - expires = models.DateTimeField(blank = True, null = True) - owner_group = models.ForeignKey(Group, on_delete=models.CASCADE, to_field='name') - owner_user = models.ForeignKey(User, on_delete=models.CASCADE, to_field='username') + description = models.TextField(blank=True, null=True) + expires = models.DateTimeField(blank=True, null=True) + owner_group = models.ForeignKey(Group, on_delete=models.CASCADE, to_field="name") + owner_user = models.ForeignKey(User, on_delete=models.CASCADE, to_field="username") prefix = models.CharField(max_length=5) def __str__(self): """String for representing the BCO model (in Admin site etc.).""" return f"{self.prefix}" + def post_api_prefixes_create(request): """Create a prefix @@ -92,11 +94,10 @@ def post_api_prefixes_create(request): user_utils = UserUtils.UserUtils() # Define the bulk request. - bulk_request = request.data['POST_api_prefixes_create'] + bulk_request = request.data["POST_api_prefixes_create"] # Get all existing prefixes. - available_prefixes = list( - Prefix.objects.all().values_list('prefix', flat = True)) + available_prefixes = list(Prefix.objects.all().values_list("prefix", flat=True)) # Construct an array to return information about processing # the request. @@ -107,11 +108,11 @@ def post_api_prefixes_create(request): for creation_object in bulk_request: # Go over each prefix proposed. - for prfx in creation_object['prefixes']: + for prfx in creation_object["prefixes"]: # Create a list to hold information about errors. errors = {} # Standardize the prefix name. - standardized = prfx['prefix'].upper() + standardized = prfx["prefix"].upper() # TODO: abstract this error check to schema checker? # Check for each error. # Create a flag for if one of these checks fails. @@ -121,72 +122,82 @@ def post_api_prefixes_create(request): error_check = True # Bad request because the prefix doesn't follow # the naming rules. - errors['400_bad_request_malformed_prefix'] = db_utils.messages( - parameters = {'prefix': standardized.upper()} - )['400_bad_request_malformed_prefix'] + errors["400_bad_request_malformed_prefix"] = db_utils.messages( + parameters={"prefix": standardized.upper()} + )["400_bad_request_malformed_prefix"] # Has the prefix already been created? if standardized in available_prefixes: error_check = True # Update the request status. - errors['409_prefix_conflict'] = db_utils.messages( - parameters = { - 'prefix': standardized.upper() - } - )['409_prefix_conflict'] + errors["409_prefix_conflict"] = db_utils.messages( + parameters={"prefix": standardized.upper()} + )["409_prefix_conflict"] # Does the user exist? - if user_utils.check_user_exists(un = creation_object['owner_user']) is False: - + if ( + user_utils.check_user_exists(user_name=creation_object["owner_user"]) + is False + ): + error_check = True - + # Bad request. - errors['404_user_not_found'] = db_utils.messages( - parameters = {'username': creation_object['owner_user']} - )['404_user_not_found'] - + errors["404_user_not_found"] = db_utils.messages( + parameters={"username": creation_object["owner_user"]} + )["404_user_not_found"] + # Does the group exist? - if user_utils.check_group_exists(n = creation_object['owner_group']) is False: + if ( + user_utils.check_group_exists(name=creation_object["owner_group"]) + is False + ): error_check = True # Bad request. - errors['404_group_not_found'] = db_utils.messages( - parameters = {'group': creation_object['owner_group']} - )['404_group_not_found'] + errors["404_group_not_found"] = db_utils.messages( + parameters={"group": creation_object["owner_group"]} + )["404_group_not_found"] # Was the expiration date validly formatted and, if so, # is it after right now? - if 'expiration_date' in prfx: - if db_utils.check_expiration(dt_string = prfx['expiration_date']) is not None: + if "expiration_date" in prfx: + if ( + db_utils.check_expiration(dt_string=prfx["expiration_date"]) + is not None + ): error_check = True # Bad request. - errors['400_invalid_expiration_date'] = db_utils.messages( - parameters = { - 'expiration_date': prfx['expiration_date']} - )['400_invalid_expiration_date'] + errors["400_invalid_expiration_date"] = db_utils.messages( + parameters={"expiration_date": prfx["expiration_date"]} + )["400_invalid_expiration_date"] # Did any check fail? if error_check is False: - # The prefix has not been created, so create it. + # The prefix has not been created, so create it. DbUtils.DbUtils().write_object( - p_app_label = 'api', - p_model_name = 'Prefix', - p_fields = ['created_by', 'description', 'owner_group', 'owner_user', 'prefix'], - p_data = { - 'created_by': user_utils.user_from_request( - request = request + p_app_label="api", + p_model_name="Prefix", + p_fields=[ + "created_by", + "description", + "owner_group", + "owner_user", + "prefix", + ], + p_data={ + "created_by": user_utils.user_from_request( + request=request ).username, - 'description': prfx['description'], - 'owner_group': creation_object['owner_group'], - 'owner_user': creation_object['owner_user'], - 'prefix': standardized - } + "description": prfx["description"], + "owner_group": creation_object["owner_group"], + "owner_user": creation_object["owner_user"], + "prefix": standardized, + }, ) # Created the prefix. - errors['201_prefix_create'] = db_utils.messages( - parameters = { - 'prefix': standardized - } - )['201_prefix_create'] + errors["201_prefix_create"] = db_utils.messages( + parameters={"prefix": standardized} + )["201_prefix_create"] # Append the possible "errors". returning.append(errors) @@ -194,7 +205,8 @@ def post_api_prefixes_create(request): # As this view is for a bulk operation, status 200 # means that the request was successfully processed, # but NOT necessarily each item in the request. - return Response(status = status.HTTP_200_OK, data = returning) + return Response(status=status.HTTP_200_OK, data=returning) + def post_api_prefixes_delete(request): """Deletes a prefix @@ -219,11 +231,10 @@ def post_api_prefixes_delete(request): db_utils = DbUtils.DbUtils() - bulk_request = request.data['POST_api_prefixes_delete'] + bulk_request = request.data["POST_api_prefixes_delete"] # Get all existing prefixes. - available_prefixes = list( - Prefix.objects.all().values_list('prefix', flat=True)) + available_prefixes = list(Prefix.objects.all().values_list("prefix", flat=True)) returning = [] @@ -240,9 +251,10 @@ def post_api_prefixes_delete(request): if standardized not in available_prefixes: error_check = True - # Update the request status. - errors['404_missing_prefix'] = db_utils.messages(parameters={ - 'prefix': standardized})['404_missing_prefix'] + # Update the request status. + errors["404_missing_prefix"] = db_utils.messages( + parameters={"prefix": standardized} + )["404_missing_prefix"] if error_check is False: # The prefix exists, so delete it. @@ -253,8 +265,9 @@ def post_api_prefixes_delete(request): prefixed = Prefix.objects.get(prefix=standardized) prefixed.delete() # Deleted the prefix. - errors['200_OK_prefix_delete'] = db_utils.messages(parameters={ - 'prefix': standardized})['200_OK_prefix_delete'] + errors["200_OK_prefix_delete"] = db_utils.messages( + parameters={"prefix": standardized} + )["200_OK_prefix_delete"] # Append the possible "errors". returning.append(errors) @@ -264,6 +277,7 @@ def post_api_prefixes_delete(request): # but NOT necessarily each item in the request. return Response(status=status.HTTP_200_OK, data=returning) + def post_api_prefixes_modify(request): """Modify a Prefix @@ -285,9 +299,8 @@ def post_api_prefixes_modify(request): db_utils = DbUtils.DbUtils() user_utils = UserUtils.UserUtils() - bulk_request = request.data['POST_api_prefixes_modify'] - available_prefixes = list( - Prefix.objects.all().values_list('prefix', flat = True)) + bulk_request = request.data["POST_api_prefixes_modify"] + available_prefixes = list(Prefix.objects.all().values_list("prefix", flat=True)) # Construct an array to return information about processing # the request. @@ -296,15 +309,15 @@ def post_api_prefixes_modify(request): # Since bulk_request is an array, go over each # item in the array. for creation_object in bulk_request: - + # Go over each prefix proposed. - for prfx in creation_object['prefixes']: - + for prfx in creation_object["prefixes"]: + # Create a list to hold information about errors. errors = {} - + # Standardize the prefix name. - standardized = prfx['prefix'].upper() + standardized = prfx["prefix"].upper() # Create a flag for if one of these checks fails. error_check = False @@ -312,269 +325,266 @@ def post_api_prefixes_modify(request): if standardized not in available_prefixes: error_check = True - + # Update the request status. # Bad request. - errors['404_missing_prefix'] = db_utils.messages( - parameters = { - 'prefix': standardized - } - )['404_missing_prefix'] - + errors["404_missing_prefix"] = db_utils.messages( + parameters={"prefix": standardized} + )["404_missing_prefix"] + # Does the user exist? - if user_utils.check_user_exists(un = creation_object['owner_user']) is False: - + if ( + user_utils.check_user_exists(user_name=creation_object["owner_user"]) + is False + ): + error_check = True - + # Bad request. - errors['404_user_not_found'] = db_utils.messages( - parameters = { - 'username': creation_object['owner_user'] - } - )['404_user_not_found'] - + errors["404_user_not_found"] = db_utils.messages( + parameters={"username": creation_object["owner_user"]} + )["404_user_not_found"] + # Does the group exist? - if user_utils.check_group_exists(n = creation_object['owner_group']) is False: - + if ( + user_utils.check_group_exists(name=creation_object["owner_group"]) + is False + ): + error_check = True - + # Bad request. - errors['404_group_not_found'] = db_utils.messages( - parameters = { - 'group': creation_object['owner_group'] - } - )['404_group_not_found'] - + errors["404_group_not_found"] = db_utils.messages( + parameters={"group": creation_object["owner_group"]} + )["404_group_not_found"] + # Was the expiration date validly formatted and, if so, # is it after right now? - if 'expiration_date' in prfx: - if db_utils.check_expiration(dt_string = prfx['expiration_date']) is not None: - + if "expiration_date" in prfx: + if ( + db_utils.check_expiration(dt_string=prfx["expiration_date"]) + is not None + ): + error_check = True # Bad request. - errors['400_invalid_expiration_date'] = db_utils.messages( - parameters = { - 'expiration_date': prfx['expiration_date'] - } - )['400_invalid_expiration_date'] - + errors["400_invalid_expiration_date"] = db_utils.messages( + parameters={"expiration_date": prfx["expiration_date"]} + )["400_invalid_expiration_date"] + # Did any check fail? if error_check is False: - - # The prefix has not been created, so create it. + + # The prefix has not been created, so create it. DbUtils.DbUtils().write_object( - p_app_label = 'api', - p_model_name = 'Prefix', - p_fields = ['created_by', 'description', 'owner_group', 'owner_user', 'prefix'], - p_data = { - 'created_by': user_utils.user_from_request( - request = request + p_app_label="api", + p_model_name="Prefix", + p_fields=[ + "created_by", + "description", + "owner_group", + "owner_user", + "prefix", + ], + p_data={ + "created_by": user_utils.user_from_request( + request=request ).username, - 'description': prfx['description'], - 'owner_group': creation_object['owner_group'], - 'owner_user': creation_object['owner_user'], - 'prefix': standardized - } + "description": prfx["description"], + "owner_group": creation_object["owner_group"], + "owner_user": creation_object["owner_user"], + "prefix": standardized, + }, ) # Created the prefix. - errors['201_prefix_modify'] = db_utils.messages( - parameters = { - 'prefix': standardized - } - )['201_prefix_modify'] - + errors["201_prefix_modify"] = db_utils.messages( + parameters={"prefix": standardized} + )["201_prefix_modify"] + # Append the possible "errors". returning.append(errors) - + # As this view is for a bulk operation, status 200 # means that the request was successfully processed, # but NOT necessarily each item in the request. - return(Response(status = status.HTTP_200_OK, data = returning)) - -def post_api_prefixes_permissions_set( - request -): - - # Set the permissions for prefixes. - - # Instantiate any necessary imports. - db = DbUtils.DbUtils() - uu = UserUtils.UserUtils() - - # First, get which user we're dealing with. - user = uu.user_from_request( - rq = request - ) - - # Define the bulk request. - bulk_request = request.data['POST_api_prefixes_permissions_set'] - - # Get all existing prefixes. - available_prefixes = list( - Prefix.objects.all().values_list( - 'prefix', - flat = True - ) - ) - - # Construct an array to return information about processing - # the request. - returning = [] - - # Since bulk_request is an array, go over each - # item in the array. - for creation_object in bulk_request: - - # Go over each prefix proposed. - for prfx in creation_object['prefixes']: - - # Create a list to hold information about errors. - errors = {} - - # Standardize the prefix name. - standardized = prfx.upper() - - # Create a flag for if one of these checks fails. - error_check = False - - # Has the prefix already been created? - if standardized not in available_prefixes: - - error_check = True - - # Update the request status. - errors['404_missing_prefix'] = db.messages( - parameters = { - 'prefix': standardized - } - )['404_missing_prefix'] - - # The prefix exists, but is the requestor the owner? - if uu.check_user_owns_prefix(un = user.username, prfx = standardized) is False and user.username != 'wheel': - - error_check = True - - # Bad request, the user isn't the owner or wheel. - errors['403_requestor_is_not_prefix_owner'] = db.messages( - parameters = { - 'prefix': standardized - } - )['403_requestor_is_not_prefix_owner'] - - # The "expensive" work of assigning permissions is held off - # if any of the above checks fails. - - # Did any check fail? - if error_check is False: - - # Split out the permissions assignees into users and groups. - assignees = { - 'group': [], - 'username': [] - } - - if 'username' in creation_object: - assignees['username'] = creation_object['username'] - - if 'group' in creation_object: - assignees['group'] = creation_object['group'] - - # Go through each one. - for un in assignees['username']: - - # Create a list to hold information about sub-errors. - sub_errors = {} - - # Create a flag for if one of these sub-checks fails. - sub_error_check = False - - # Get the user whose permissions are being assigned. - if uu.check_user_exists(un = un) is False: - - sub_error_check = True - - # Bad request, the user doesn't exist. - sub_errors['404_user_not_found'] = db.messages( - parameters = { - 'username': un - } - )['404_user_not_found'] - - # Was the user found? - if sub_error_check is False: - - assignee = User.objects.get(username = un) - - # Permissions are defined directly as they are - # in the POST request. - - # Assumes permissions are well-formed... - - # Source: https://docs.djangoproject.com/en/3.2/topics/auth/default/#permissions-and-authorization - assignee.user_permissions.set([Permission.objects.get(codename = i + '_' + prfx) for i in creation_object['permissions']]) - - # Permissions assigned. - sub_errors['200_OK_prefix_permissions_update'] = db.messages( - parameters = { - 'prefix': standardized - } - )['200_OK_prefix_permissions_update'] - - # Add the sub-"errors". - errors['username'] = sub_errors - - for g in assignees['group']: - - # Create a list to hold information about sub-errors. - sub_errors = {} - - # Create a flag for if one of these sub-checks fails. - sub_error_check = False - - # Get the group whose permissions are being assigned. - if uu.check_group_exists(n = g) is False: - - sub_error_check = True - - # Bad request, the group doesn't exist. - sub_errors['404_group_not_found'] = db.messages( - parameters = { - 'group': g - } - )['404_group_not_found'] - - # Was the group found? - if sub_error_check is False: - - assignee = Group.objects.get(name = g) - - # Permissions are defined directly as they are - # in the POST request. - - # Assumes permissions are well-formed... - - # Source: https://docs.djangoproject.com/en/3.2/topics/auth/default/#permissions-and-authorization - assignee.permissions.set([Permission.objects.get(codename = i + '_' + prfx) for i in creation_object['permissions']]) - - # Permissions assigned. - sub_errors['200_OK_prefix_permissions_update'] = db.messages( - parameters = { - 'prefix': standardized - } - )['200_OK_prefix_permissions_update'] - - # Add the sub-"errors". - errors['group'] = sub_errors - - # Append the possible "errors". - returning.append(errors) - - # As this view is for a bulk operation, status 200 - # means that the request was successfully processed, - # but NOT necessarily each item in the request. - return(Response(status = status.HTTP_200_OK, data = returning)) + return Response(status=status.HTTP_200_OK, data=returning) + + +def post_api_prefixes_permissions_set(request): + + # Set the permissions for prefixes. + + # Instantiate any necessary imports. + db = DbUtils.DbUtils() + uu = UserUtils.UserUtils() + + # First, get which user we're dealing with. + user = uu.user_from_request(request=request) + + # Define the bulk request. + bulk_request = request.data["POST_api_prefixes_permissions_set"] + + # Get all existing prefixes. + available_prefixes = list(Prefix.objects.all().values_list("prefix", flat=True)) + + # Construct an array to return information about processing + # the request. + returning = [] + + # Since bulk_request is an array, go over each + # item in the array. + for creation_object in bulk_request: + + # Go over each prefix proposed. + for prfx in creation_object["prefixes"]: + + # Create a list to hold information about errors. + errors = {} + + # Standardize the prefix name. + standardized = prfx.upper() + + # Create a flag for if one of these checks fails. + error_check = False + + # Has the prefix already been created? + if standardized not in available_prefixes: + + error_check = True + + # Update the request status. + errors["404_missing_prefix"] = db.messages( + parameters={"prefix": standardized} + )["404_missing_prefix"] + + # The prefix exists, but is the requestor the owner? + if ( + uu.check_user_owns_prefix(user_name=user.username, prfx=standardized) + is False + and user.username != "wheel" + ): + + error_check = True + + # Bad request, the user isn't the owner or wheel. + errors["403_requestor_is_not_prefix_owner"] = db.messages( + parameters={"prefix": standardized} + )["403_requestor_is_not_prefix_owner"] + + # The "expensive" work of assigning permissions is held off + # if any of the above checks fails. + + # Did any check fail? + if error_check is False: + + # Split out the permissions assignees into users and groups. + assignees = {"group": [], "username": []} + + if "username" in creation_object: + assignees["username"] = creation_object["username"] + + if "group" in creation_object: + assignees["group"] = creation_object["group"] + + # Go through each one. + for user_name in assignees["username"]: + + # Create a list to hold information about sub-errors. + sub_errors = {} + + # Create a flag for if one of these sub-checks fails. + sub_error_check = False + + # Get the user whose permissions are being assigned. + if uu.check_user_exists(user_name=user_name) is False: + + sub_error_check = True + + # Bad request, the user doesn't exist. + sub_errors["404_user_not_found"] = db.messages( + parameters={"username": user_name} + )["404_user_not_found"] + + # Was the user found? + if sub_error_check is False: + + assignee = User.objects.get(username=user_name) + + # Permissions are defined directly as they are + # in the POST request. + + # Assumes permissions are well-formed... + + # Source: https://docs.djangoproject.com/en/3.2/topics/auth/default/#permissions-and-authorization + assignee.user_permissions.set( + [ + Permission.objects.get(codename=i + "_" + prfx) + for i in creation_object["permissions"] + ] + ) + + # Permissions assigned. + sub_errors["200_OK_prefix_permissions_update"] = db.messages( + parameters={"prefix": standardized} + )["200_OK_prefix_permissions_update"] + + # Add the sub-"errors". + errors["username"] = sub_errors + + for g in assignees["group"]: + + # Create a list to hold information about sub-errors. + sub_errors = {} + + # Create a flag for if one of these sub-checks fails. + sub_error_check = False + + # Get the group whose permissions are being assigned. + if uu.check_group_exists(name=g) is False: + + sub_error_check = True + + # Bad request, the group doesn't exist. + sub_errors["404_group_not_found"] = db.messages( + parameters={"group": g} + )["404_group_not_found"] + + # Was the group found? + if sub_error_check is False: + + assignee = Group.objects.get(name=g) + + # Permissions are defined directly as they are + # in the POST request. + + # Assumes permissions are well-formed... + + # Source: https://docs.djangoproject.com/en/3.2/topics/auth/default/#permissions-and-authorization + assignee.permissions.set( + [ + Permission.objects.get(codename=i + "_" + prfx) + for i in creation_object["permissions"] + ] + ) + + # Permissions assigned. + sub_errors["200_OK_prefix_permissions_update"] = db.messages( + parameters={"prefix": standardized} + )["200_OK_prefix_permissions_update"] + + # Add the sub-"errors". + errors["group"] = sub_errors + + # Append the possible "errors". + returning.append(errors) + + # As this view is for a bulk operation, status 200 + # means that the request was successfully processed, + # but NOT necessarily each item in the request. + return Response(status=status.HTTP_200_OK, data=returning) + def post_api_prefixes_token(request): """Get Prefixes for a Token @@ -599,9 +609,11 @@ def post_api_prefixes_token(request): """ prefixes = UserUtils.UserUtils().prefix_perms_for_user( - user_object = UserUtils.UserUtils().user_from_request( - request = request).username, flatten = False) - return Response(status = status.HTTP_200_OK, data = prefixes) + user_object=UserUtils.UserUtils().user_from_request(request=request).username, + flatten=False, + ) + return Response(status=status.HTTP_200_OK, data=prefixes) + def post_api_prefixes_token_flat(request): """Get Prefixes for a Token @@ -625,10 +637,11 @@ def post_api_prefixes_token_flat(request): """ prefixes = UserUtils.UserUtils().prefix_perms_for_user( - user_object = UserUtils.UserUtils().user_from_request( - request = request).username,flatten = True) + user_object=UserUtils.UserUtils().user_from_request(request=request).username, + flatten=True, + ) - return Response(status = status.HTTP_200_OK, data = prefixes) + return Response(status=status.HTTP_200_OK, data=prefixes) # --- Prefix --- # @@ -657,9 +670,9 @@ def create_permissions_for_prefix(sender, instance=None, created=False, **kwargs if created: owner_user = User.objects.get(username=instance.owner_user) owner_group = Group.objects.get(name=instance.owner_group_id) - draft = instance.prefix.lower() + '_drafter' - publish = instance.prefix.lower() + '_publisher' - + draft = instance.prefix.lower() + "_drafter" + publish = instance.prefix.lower() + "_publisher" + if len(Group.objects.filter(name=draft)) != 0: drafters = Group.objects.get(name=draft) owner_user.groups.add(drafters) @@ -672,7 +685,7 @@ def create_permissions_for_prefix(sender, instance=None, created=False, **kwargs description=instance.description, group=drafters, max_n_members=-1, - owner_user=owner_user + owner_user=owner_user, ) if len(Group.objects.filter(name=publish)) != 0: @@ -687,33 +700,30 @@ def create_permissions_for_prefix(sender, instance=None, created=False, **kwargs description=instance.description, group=publishers, max_n_members=-1, - owner_user=owner_user + owner_user=owner_user, ) try: - for perm in ['add', 'change', 'delete', 'view', 'draft', 'publish']: + for perm in ["add", "change", "delete", "view", "draft", "publish"]: Permission.objects.create( - name='Can ' + perm + ' BCOs with prefix ' + instance.prefix, - content_type=ContentType.objects.get( - app_label='api', - model='bco' - ), - codename=perm + '_' + instance.prefix + name="Can " + perm + " BCOs with prefix " + instance.prefix, + content_type=ContentType.objects.get(app_label="api", model="bco"), + codename=perm + "_" + instance.prefix, ) - new_perm = Permission.objects.get( - codename=perm + '_' + instance.prefix) + new_perm = Permission.objects.get(codename=perm + "_" + instance.prefix) owner_user.user_permissions.add(new_perm) owner_group.permissions.add(new_perm) publishers.permissions.add(new_perm) - if perm == 'publish': + if perm == "publish": pass else: - drafters.permissions.add(new_perm) + drafters.permissions.add(new_perm) except PermErrors.IntegrityError: # The permissions already exist. pass + @receiver(post_save, sender=Prefix) def create_counter_for_prefix(sender, instance=None, created=False, **kwargs): """Create prefix counter @@ -728,7 +738,8 @@ def create_counter_for_prefix(sender, instance=None, created=False, **kwargs): """ if created: - prefix_table.objects.create(n_objects=1,prefix=instance.prefix) + prefix_table.objects.create(n_objects=1, prefix=instance.prefix) + @receiver(post_delete, sender=Prefix) def delete_permissions_for_prefix(sender, instance=None, **kwargs): @@ -737,9 +748,9 @@ def delete_permissions_for_prefix(sender, instance=None, **kwargs): a filter. """ - Permission.objects.filter(codename='add_' + instance.prefix).delete() - Permission.objects.filter(codename='change_' + instance.prefix).delete() - Permission.objects.filter(codename='delete_' + instance.prefix).delete() - Permission.objects.filter(codename='view_' + instance.prefix).delete() - Permission.objects.filter(codename='draft_' + instance.prefix).delete() - Permission.objects.filter(codename='publish_' + instance.prefix).delete() + Permission.objects.filter(codename="add_" + instance.prefix).delete() + Permission.objects.filter(codename="change_" + instance.prefix).delete() + Permission.objects.filter(codename="delete_" + instance.prefix).delete() + Permission.objects.filter(codename="view_" + instance.prefix).delete() + Permission.objects.filter(codename="draft_" + instance.prefix).delete() + Permission.objects.filter(codename="publish_" + instance.prefix).delete() diff --git a/bco_api/api/models.py b/bco_api/api/models.py index 7e7d9b06..8bb393fa 100755 --- a/bco_api/api/models.py +++ b/bco_api/api/models.py @@ -59,8 +59,8 @@ class BCO(models.Model): contents = models.JSONField() object_class = models.TextField(blank=True, null=True) object_id = models.TextField() - owner_group = models.ForeignKey(Group, on_delete=models.CASCADE, to_field='name') - owner_user = models.ForeignKey(User, on_delete=models.CASCADE, to_field='username') + owner_group = models.ForeignKey(Group, on_delete=models.CASCADE, to_field="name") + owner_user = models.ForeignKey(User, on_delete=models.CASCADE, to_field="username") prefix = models.CharField(max_length=5) schema = models.TextField() state = models.TextField() @@ -76,6 +76,7 @@ class new_users(models.Model): """Instead of using the User model, just use a crude table to store the temporary information when someone asks for a new account.""" + email = models.EmailField() temp_identifier = models.TextField(max_length=100) # In case we are writing back to UserDB. @@ -100,8 +101,6 @@ def __temp_identifier__(self): return str(self.temp_identifier) - - # def get_first_name(self): # return self.first_name @@ -120,6 +119,7 @@ def __temp_identifier__(self): # --- User --- # + @receiver(post_save, sender=User) def create_auth_token(sender, instance=None, created=False, **kwargs): """Link user creation to token generation. @@ -128,7 +128,7 @@ def create_auth_token(sender, instance=None, created=False, **kwargs): if created: # The anonymous user's token is hard-coded # in server.conf. - if instance.username == 'anon': + if instance.username == "anon": # Create anon's record with the hard-coded key. Token.objects.create(user=instance, key=settings.ANON_KEY) else: diff --git a/bco_api/api/permissions.py b/bco_api/api/permissions.py index 9f82dd44..d387b7e8 100644 --- a/bco_api/api/permissions.py +++ b/bco_api/api/permissions.py @@ -17,231 +17,152 @@ from django.contrib.auth.models import User, Group - - # ----- Admin Permissions ----- # - - -class RequestorInGroupAdminsGroup( - permissions.BasePermission -): - - def has_permission( - self, - request, - view - ): +class RequestorInGroupAdminsGroup(permissions.BasePermission): + def has_permission(self, request, view): # Check to see if the requester is in the group admins group. - + # Get the groups for this token (user). # This means getting the user ID for the token, # then the username. user_id = Token.objects.get( - key = request.META.get( - 'HTTP_AUTHORIZATION' - ).split(' ')[1] + key=request.META.get("HTTP_AUTHORIZATION").split(" ")[1] ).user_id - username = User.objects.get( - id = user_id - ) - - # Get the prefix admins. - group_admins = Group.objects.filter( - user = username, - name = 'group_admins' - ) - - return len(group_admins) > 0 + username = User.objects.get(id=user_id) + # Get the prefix admins. + group_admins = Group.objects.filter(user=username, name="group_admins") + return len(group_admins) > 0 class RequestorInPrefixAdminsGroup(permissions.BasePermission): """ Check to see if the requester is in the prefix admins group. - + Get the groups for this token (user). Slight tweak in case the proper headers were not provided... In particular, Swagger will cause an Internal Error 500 if this logic is not here AND a view uses non-object-level - permissions (i.e. RequestorInPrefixAdminsGroup, see + permissions (i.e. RequestorInPrefixAdminsGroup, see ApiPrefixesPermissionsSet in views.py) """ + def has_permission(self, request, view): """ - This means getting the user ID for the token, - then the username. - Get the prefix admins. + This means getting the user ID for the token, + then the username. + Get the prefix admins. """ - if 'HTTP_AUTHORIZATION' in request.META: + if "HTTP_AUTHORIZATION" in request.META: user_id = Token.objects.get( - key = request.META.get('HTTP_AUTHORIZATION').split(' ')[1] + key=request.META.get("HTTP_AUTHORIZATION").split(" ")[1] ).user_id - username = User.objects.get(id = user_id) + username = User.objects.get(id=user_id) - prefix_admins = Group.objects.filter( - user = username, - name = 'prefix_admins' - ) + prefix_admins = Group.objects.filter(user=username, name="prefix_admins") return len(prefix_admins) > 0 else: return False - - - - # ----- Table Permissions ----- # - - - - - # ----- Object Permissions ----- # - - # Permissions based on REST. # Source: https://stackoverflow.com/a/18646798 -class RequestorIsObjectOwner( - permissions.BasePermission -): - - def has_object_permission( - self, - request, - view, - obj - ): +class RequestorIsObjectOwner(permissions.BasePermission): + def has_object_permission(self, request, view, obj): # Check to see if the requester is in a particular owner group. - + # Get the groups for this token (user). # This means getting the user ID for the token, # then the username. user_id = Token.objects.get( - key = request.META.get( - 'HTTP_AUTHORIZATION' - ).split(' ')[1] + key=request.META.get("HTTP_AUTHORIZATION").split(" ")[1] ).user_id - username = User.objects.get( - id = user_id - ) + username = User.objects.get(id=user_id) # Get the groups for this username (at a minimum the user # group created when the account was created should show up). # Now get the user's groups. - groups = Group.objects.filter( - user = username - ) + groups = Group.objects.filter(user=username) # Check that the user is in the ownership group. # Note that view permissions are NOT checked because # the owner automatically has full permissions on the # object. - owner_group = apps.get_model( - app_label = 'api', - model_name = request.data['table_name'] - ).objects.get( - object_id = request.data['object_id'] - ).owner_group + owner_group = ( + apps.get_model(app_label="api", model_name=request.data["table_name"]) + .objects.get(object_id=request.data["object_id"]) + .owner_group + ) # Note: could use https://docs.djangoproject.com/en/3.2/topics/auth/customizing/#custom-permissions # to set these, but group membership confers all read # permissions. # Is this user in the ownership group? - return groups.filter( - name = owner_group - ).exists() - - + return groups.filter(name=owner_group).exists() -class RequestorInObjectOwnerGroup( - permissions.BasePermission -): - - def has_object_permission( - self, - request, - view, - obj - ): +class RequestorInObjectOwnerGroup(permissions.BasePermission): + def has_object_permission(self, request, view, obj): # Check to see if the requester is in a particular owner group. - + # Get the groups for this token (user). # This means getting the user ID for the token, # then the username. user_id = Token.objects.get( - key = request.META.get( - 'HTTP_AUTHORIZATION' - ).split(' ')[1] + key=request.META.get("HTTP_AUTHORIZATION").split(" ")[1] ).user_id - username = User.objects.get( - id = user_id - ) + username = User.objects.get(id=user_id) # Get the groups for this username (at a minimum the user # group created when the account was created should show up). # Now get the user's groups. - groups = Group.objects.filter( - user = username - ) + groups = Group.objects.filter(user=username) # Check that the user is in the ownership group. # Note that view permissions are NOT checked because # the owner automatically has full permissions on the # object. - owner_group = apps.get_model( - app_label = 'api', - model_name = request.data['table_name'] - ).objects.get( - object_id = request.data['object_id'] - ).owner_group + owner_group = ( + apps.get_model(app_label="api", model_name=request.data["table_name"]) + .objects.get(object_id=request.data["object_id"]) + .owner_group + ) # Note: could use https://docs.djangoproject.com/en/3.2/topics/auth/customizing/#custom-permissions # to set these, but group membership confers all read # permissions. # Is this user in the ownership group? - return groups.filter( - name = owner_group - ).exists() - - + return groups.filter(name=owner_group).exists() # Generic object-level permissions checker. -class HasObjectGenericPermission( - permissions.BasePermission -): - - def has_object_permission( - self, - request, - view, - obj - ): +class HasObjectGenericPermission(permissions.BasePermission): + def has_object_permission(self, request, view, obj): # Check to see if the requester (group) has the given permission on the given object. @@ -254,23 +175,18 @@ def has_object_permission( # then the username. # Source: https://stackoverflow.com/questions/31813572/access-token-from-view user_id = Token.objects.get( - key = request.META.get( - 'HTTP_AUTHORIZATION' - ).split(' ')[1] + key=request.META.get("HTTP_AUTHORIZATION").split(" ")[1] ).user_id - username = User.objects.get( - id = user_id - ) - + username = User.objects.get(id=user_id) + # See if the group can do something with this object. # Source: https://django-guardian.readthedocs.io/en/stable/userguide/check.html#get-perms # Get the group object first, then check. - if request.data['perm_type'] + '_' + request.data['table_name'] in get_group_perms( - username, - obj - ): - + if request.data["perm_type"] + "_" + request.data[ + "table_name" + ] in get_group_perms(username, obj): + return True else: @@ -279,71 +195,40 @@ def has_object_permission( return False - - # Specific permissions (necessary to use logical operators # when checking permissions). # These are all just specific cases of HasObjectGenericPermission -class HasObjectAddPermission( - permissions.BasePermission -): - - def has_object_permission( - self, - request, - view, - obj - ): - +class HasObjectAddPermission(permissions.BasePermission): + def has_object_permission(self, request, view, obj): + user_id = Token.objects.get( - key = request.META.get( - 'HTTP_AUTHORIZATION' - ).split(' ')[1] + key=request.META.get("HTTP_AUTHORIZATION").split(" ")[1] ).user_id - username = User.objects.get( - id = user_id - ) + username = User.objects.get(id=user_id) # Get the group object first, then check. - if 'add_' + request.data['table_name'] in get_group_perms( - username, - obj - ): - + if "add_" + request.data["table_name"] in get_group_perms(username, obj): + return True else: # User doesn't have the right permissions for this object. return False - -class HasObjectChangePermission( - permissions.BasePermission -): - - def has_object_permission( - self, - request, - view, - obj - ): - + + +class HasObjectChangePermission(permissions.BasePermission): + def has_object_permission(self, request, view, obj): + user_id = Token.objects.get( - key = request.META.get( - 'HTTP_AUTHORIZATION' - ).split(' ')[1] + key=request.META.get("HTTP_AUTHORIZATION").split(" ")[1] ).user_id - username = User.objects.get( - id = user_id - ) + username = User.objects.get(id=user_id) # Get the group object first, then check. - if 'change_' + request.data['table_name'] in get_group_perms( - username, - obj - ): - + if "change_" + request.data["table_name"] in get_group_perms(username, obj): + return True else: @@ -351,29 +236,18 @@ def has_object_permission( # User doesn't have the right permissions for this object. return False -class HasObjectDeletePermission( - permissions.BasePermission -): - - def has_object_permission( - self, - request, - view, - obj - ): - + +class HasObjectDeletePermission(permissions.BasePermission): + def has_object_permission(self, request, view, obj): + user_id = Token.objects.get( - key = request.META.get( - 'HTTP_AUTHORIZATION' - ).split(' ')[1] + key=request.META.get("HTTP_AUTHORIZATION").split(" ")[1] ).user_id - username = User.objects.get( - id = user_id - ) + username = User.objects.get(id=user_id) # Get the group object first, then check. - if 'delete_' + request.data['table_name'] in get_group_perms(username, obj): - + if "delete_" + request.data["table_name"] in get_group_perms(username, obj): + return True else: @@ -381,32 +255,21 @@ def has_object_permission( # User doesn't have the right permissions for this object. return False -class HasObjectViewPermission( - permissions.BasePermission -): - - def has_object_permission( - self, - request, - view, - obj - ): - + +class HasObjectViewPermission(permissions.BasePermission): + def has_object_permission(self, request, view, obj): + user_id = Token.objects.get( - key = request.META.get( - 'HTTP_AUTHORIZATION' - ).split(' ')[1] + key=request.META.get("HTTP_AUTHORIZATION").split(" ")[1] ).user_id - username = User.objects.get( - id = user_id - ) + username = User.objects.get(id=user_id) # Get the group object first, then check. - if 'view_' + request.data['table_name'] in get_group_perms(username, obj): - + if "view_" + request.data["table_name"] in get_group_perms(username, obj): + return True else: # User doesn't have the right permissions for this object. - return False \ No newline at end of file + return False diff --git a/bco_api/api/scripts/method_specific/GET_activate_account.py b/bco_api/api/scripts/method_specific/GET_activate_account.py index 7a7386d1..57707855 100755 --- a/bco_api/api/scripts/method_specific/GET_activate_account.py +++ b/bco_api/api/scripts/method_specific/GET_activate_account.py @@ -7,13 +7,15 @@ # For url from django.conf import settings + # Responses from rest_framework import status from rest_framework.response import Response # Source: https://codeloop.org/django-rest-framework-course-for-beginners/ -def GET_activate_account(username,temp_identifier): + +def GET_activate_account(username, temp_identifier): """Activate Account Parameters @@ -34,31 +36,34 @@ def GET_activate_account(username,temp_identifier): # The account has not been activated, but does it exist # in the temporary table? if db_utils.check_activation_credentials( - p_app_label='api', - p_model_name='new_users', - p_email=username, - p_temp_identifier=temp_identifier - ): + p_app_label="api", + p_model_name="new_users", + p_email=username, + p_temp_identifier=temp_identifier, + ): # The credentials match, so activate the account. credential_try = db_utils.activate_account(p_email=username) if len(credential_try) > 0: # Everything went fine. - return (Response({ - 'activation_success': True, - 'activation_url': settings.PUBLIC_HOSTNAME+'/login/', - 'username': credential_try[0], - 'status': status.HTTP_201_CREATED, - }, status=status.HTTP_201_CREATED)) + return Response( + { + "activation_success": True, + "activation_url": settings.PUBLIC_HOSTNAME + "/login/", + "username": credential_try[0], + "status": status.HTTP_201_CREATED, + }, + status=status.HTTP_201_CREATED, + ) # The credentials weren't good. - return(Response({ - 'activation_success': False, - 'status' : status.HTTP_403_FORBIDDEN}, - status=status.HTTP_403_FORBIDDEN)) + return Response( + {"activation_success": False, "status": status.HTTP_403_FORBIDDEN}, + status=status.HTTP_403_FORBIDDEN, + ) - return(Response({ - 'activation_success': False, - 'status': status.HTTP_424_FAILED_DEPENDENCY}, - status=status.HTTP_424_FAILED_DEPENDENCY)) + return Response( + {"activation_success": False, "status": status.HTTP_424_FAILED_DEPENDENCY}, + status=status.HTTP_424_FAILED_DEPENDENCY, + ) diff --git a/bco_api/api/scripts/method_specific/GET_draft_object_by_id.py b/bco_api/api/scripts/method_specific/GET_draft_object_by_id.py index 1ddc87c5..efaefa19 100755 --- a/bco_api/api/scripts/method_specific/GET_draft_object_by_id.py +++ b/bco_api/api/scripts/method_specific/GET_draft_object_by_id.py @@ -12,6 +12,7 @@ from rest_framework.response import Response from guardian.shortcuts import get_objects_for_user + def get_draft_object_by_id(do_id, request): """Get a draft object @@ -28,42 +29,42 @@ def get_draft_object_by_id(do_id, request): it is returned. If not the response is HTTP_403_FORBIDDEN. """ - filtered = BCO.objects.filter(object_id__regex=rf'(.*?)/{do_id}', state="DRAFT") + filtered = BCO.objects.filter(object_id__regex=rf"(.*?)/{do_id}", state="DRAFT") if filtered.exists(): if len(filtered) > 1: # There are multiple matches; this shouldn't be possible return Response( - data='The contents of the draft could not be sent back because' - 'there are multiple draft matches. Please contact and admin.', - status=status.HTTP_400_BAD_REQUEST + data="The contents of the draft could not be sent back because" + "there are multiple draft matches. Please contact and admin.", + status=status.HTTP_400_BAD_REQUEST, ) # Get the requestor's info. user = UserUtils.UserUtils().user_from_request(request=request) user_perms = UserUtils.UserUtils().prefix_perms_for_user( - flatten = True, - user_object = user, - specific_permission = ['view'] + flatten=True, user_object=user, specific_permission=["view"] + ) + user_objects = get_objects_for_user( + user=user, perms=[], klass=BCO, any_perm=True ) - user_objects = get_objects_for_user(user=user, perms=[], klass=BCO, any_perm=True) # Does the requestor have permissions for the object? - full_object_id = filtered.values_list('object_id', flat=True)[0] + full_object_id = filtered.values_list("object_id", flat=True)[0] objected = BCO.objects.get(object_id=full_object_id) prefix = objected.prefix object_permission = objected in user_objects - group_permission = ('view_' + prefix) in user_perms + group_permission = ("view_" + prefix) in user_perms if object_permission is True or group_permission is True: return Response(data=objected.contents, status=status.HTTP_200_OK) return Response( - data='The contents of the draft could not be sent back because' - ' the requestor did not have appropriate permissions.', - status=status.HTTP_403_FORBIDDEN + data="The contents of the draft could not be sent back because" + " the requestor did not have appropriate permissions.", + status=status.HTTP_403_FORBIDDEN, ) # the root ID does not exist at all. return Response( - data='The draft could not be found on the server.', - status=status.HTTP_400_BAD_REQUEST + data="The draft could not be found on the server.", + status=status.HTTP_400_BAD_REQUEST, ) diff --git a/bco_api/api/scripts/method_specific/GET_published_object_by_id.py b/bco_api/api/scripts/method_specific/GET_published_object_by_id.py index ca7b0b12..58d15908 100755 --- a/bco_api/api/scripts/method_specific/GET_published_object_by_id.py +++ b/bco_api/api/scripts/method_specific/GET_published_object_by_id.py @@ -33,7 +33,7 @@ def coerce(version: str) -> Tuple[Version, Optional[str]]: * Tries to detect a "basic" version string (``major.minor.patch``). * If not enough components can be found, missing components are - set to zero to obtain a valid semver version. + set to zero to obtain a valid semver version. :param str version: the version string to convert :return: a tuple with a :class:`Version` instance (or ``None`` @@ -75,14 +75,10 @@ def GET_published_object_by_id(oi_root): # since I'm not sure why it was ever added (maybe there is a reason?) # oi_root = oi_root.split("_")[0] + '{:06d}'.format(int(oi_root.split("_")[1])) all_versions = list( - BCO.objects.filter( - object_id__regex=rf'(.*?)/{oi_root}/', - state='PUBLISHED' - ).values_list( - 'object_id', - flat=True - ) - ) + BCO.objects.filter( + object_id__regex=rf"(.*?)/{oi_root}/", state="PUBLISHED" + ).values_list("object_id", flat=True) + ) # Get the latest version for this object if we have any. if len(all_versions) > 0: @@ -92,27 +88,24 @@ def GET_published_object_by_id(oi_root): # not a version was also passed. # First find the latest version of the object. - latest_version = [i.split('/')[-1:][0] for i in all_versions] + latest_version = [i.split("/")[-1:][0] for i in all_versions] l_version, _ = coerce(max(latest_version, key=coerce)) # Kick back the latest version. return Response( - data=BCO.objects.filter( - object_id__regex=rf'{oi_root}/{l_version.major}.{l_version.minor}?.?{l_version.patch}', - state='PUBLISHED' - ).values_list( - 'contents', - flat=True - ), - status=status.HTTP_200_OK - ) + data=BCO.objects.filter( + object_id__regex=rf"{oi_root}/{l_version.major}.{l_version.minor}?.?{l_version.patch}", + state="PUBLISHED", + ).values_list("contents", flat=True), + status=status.HTTP_200_OK, + ) else: # If all_versions has 0 length, then the # the root ID does not exist at all. - print('No objects were found for the root ID provided.') + print("No objects were found for the root ID provided.") return Response( - data='No objects were found for the root ID provided.', - status=status.HTTP_400_BAD_REQUEST - ) + data="No objects were found for the root ID provided.", + status=status.HTTP_400_BAD_REQUEST, + ) diff --git a/bco_api/api/scripts/method_specific/GET_published_object_by_id_with_version.py b/bco_api/api/scripts/method_specific/GET_published_object_by_id_with_version.py index eeebf474..801320b7 100755 --- a/bco_api/api/scripts/method_specific/GET_published_object_by_id_with_version.py +++ b/bco_api/api/scripts/method_specific/GET_published_object_by_id_with_version.py @@ -24,9 +24,9 @@ def GET_published_object_by_id_with_version(oi_root, oi_version): if underscores < 1: # ERROR - there should be an underscore separating the prefix and the BCO name return Response( - data='This API requires that the prefix and the BCO name be separated by an underscore \'_\' in the object_id_root PATH variable.', - status=status.HTTP_400_BAD_REQUEST - ) + data="This API requires that the prefix and the BCO name be separated by an underscore '_' in the object_id_root PATH variable.", + status=status.HTTP_400_BAD_REQUEST, + ) # TODO: This allows BCO Names to support underscores - not sure if that is valid though # This can be 'fixed' by adding in a check for > 1 above @@ -47,14 +47,10 @@ def GET_published_object_by_id_with_version(oi_root, oi_version): # ) # The object ID either exists or it does not. retrieved = list( - BCO.objects.filter( - object_id__regex=rf'(.*?)/{oi_root}/{oi_version}', - state='PUBLISHED' - ).values_list( - 'contents', - flat=True - ) - ) + BCO.objects.filter( + object_id__regex=rf"(.*?)/{oi_root}/{oi_version}", state="PUBLISHED" + ).values_list("contents", flat=True) + ) # Was the object found? if len(retrieved) > 0: # Kick it back. @@ -62,11 +58,11 @@ def GET_published_object_by_id_with_version(oi_root, oi_version): else: # If all_versions has 0 length, then the # the root ID does not exist at all. - print('No objects were found for the root ID and version provided.') + print("No objects were found for the root ID and version provided.") return Response( - data='No objects were found for the root ID and version provided.', - status=status.HTTP_400_BAD_REQUEST - ) + data="No objects were found for the root ID and version provided.", + status=status.HTTP_400_BAD_REQUEST, + ) # TODO: This code from here on down appears to be unreachable? The above if/else will always return the request # Maybe this is placeholder code for something? @@ -74,55 +70,45 @@ def GET_published_object_by_id_with_version(oi_root, oi_version): db = DbUtils.DbUtils() # First, get the table based on the requested published object. - table_name = ( - oi_root.split('_')[0] + '_publish' - ).lower() + table_name = (oi_root.split("_")[0] + "_publish").lower() # Does the table exist? # TODO: replace with better table call... - available_tables = settings.MODELS['json_object'] + available_tables = settings.MODELS["json_object"] if table_name in available_tables: # Construct the object ID. - constructed = object_id = settings.PUBLIC_HOSTNAME + '/' + oi_root + '/' + oi_version + constructed = object_id = ( + settings.PUBLIC_HOSTNAME + "/" + oi_root + "/" + oi_version + ) # Does the object exist in the table? - if apps.get_model( - app_label='api', - model_name=table_name - ).objects.filter( - object_id=constructed - ).exists(): + if ( + apps.get_model(app_label="api", model_name=table_name) + .objects.filter(object_id=constructed) + .exists() + ): # Get the object, then check the permissions. objected = apps.get_model( - app_label='api', - model_name=table_name - ).objects.get( - object_id=constructed - ) + app_label="api", model_name=table_name + ).objects.get(object_id=constructed) return Response( - data=serializers.serialize( - 'json', - [objected, ] - ), - status=status.HTTP_200_OK - ) + data=serializers.serialize( + "json", + [ + objected, + ], + ), + status=status.HTTP_200_OK, + ) else: - return ( - Response( - status=status.HTTP_400_BAD_REQUEST - ) - ) + return Response(status=status.HTTP_400_BAD_REQUEST) else: - return ( - Response( - status=status.HTTP_400_BAD_REQUEST - ) - ) + return Response(status=status.HTTP_400_BAD_REQUEST) diff --git a/bco_api/api/scripts/method_specific/GET_retrieve_available_schema.py b/bco_api/api/scripts/method_specific/GET_retrieve_available_schema.py index 40c3418f..bcd643cc 100755 --- a/bco_api/api/scripts/method_specific/GET_retrieve_available_schema.py +++ b/bco_api/api/scripts/method_specific/GET_retrieve_available_schema.py @@ -6,58 +6,44 @@ # Put try catch in later to indicate failure to load schema... -def GET_retrieve_available_schema( - bulk_request -): - - # We don't use settings.VALIDATION_TEMPLATES because - # that contains paths on the server which we don't - # want to reveal. - - # Get the schema from the validation_definitions folder. - folder_schema = FileUtils.FileUtils().get_folder_tree( - search_folder = 'validation_definitions/' - )['paths'] - - # Define a list to hold the processed paths. - processed_paths = [] - - # Strip out everything that is above the server folder level. - for path in folder_schema: - - # Split the path up to help construct the root folder. - file_name_split = path.split('/') - - # Where is the 'validation_definitions/' item? - vd_index = file_name_split.index( - 'validation_definitions' - ) - - # Collapse everything after this index. - collapsed = '/'.join( - file_name_split[vd_index+1:] - ) - - # Set the name. - processed_paths.append( - collapsed - ) - - # Create a usable structure. - - # Source: https://stackoverflow.com/questions/9618862/how-to-parse-a-directory-structure-into-dictionary - dct = {} - - for item in processed_paths: - p = dct - for x in item.split('/'): - p = p.setdefault( - x, {} - ) - - return( - { - 'request_status': 'success', - 'contents': dct - } - ) \ No newline at end of file + +def GET_retrieve_available_schema(bulk_request): + + # We don't use settings.VALIDATION_TEMPLATES because + # that contains paths on the server which we don't + # want to reveal. + + # Get the schema from the validation_definitions folder. + folder_schema = FileUtils.FileUtils().get_folder_tree( + search_folder="validation_definitions/" + )["paths"] + + # Define a list to hold the processed paths. + processed_paths = [] + + # Strip out everything that is above the server folder level. + for path in folder_schema: + + # Split the path up to help construct the root folder. + file_name_split = path.split("/") + + # Where is the 'validation_definitions/' item? + vd_index = file_name_split.index("validation_definitions") + + # Collapse everything after this index. + collapsed = "/".join(file_name_split[vd_index + 1 :]) + + # Set the name. + processed_paths.append(collapsed) + + # Create a usable structure. + + # Source: https://stackoverflow.com/questions/9618862/how-to-parse-a-directory-structure-into-dictionary + dct = {} + + for item in processed_paths: + p = dct + for x in item.split("/"): + p = p.setdefault(x, {}) + + return {"request_status": "success", "contents": dct} diff --git a/bco_api/api/scripts/method_specific/POST_api_accounts_describe.py b/bco_api/api/scripts/method_specific/POST_api_accounts_describe.py index fe118d7e..40a662c0 100755 --- a/bco_api/api/scripts/method_specific/POST_api_accounts_describe.py +++ b/bco_api/api/scripts/method_specific/POST_api_accounts_describe.py @@ -9,23 +9,20 @@ # Source: https://codeloop.org/django-rest-framework-course-for-beginners/ -def POST_api_accounts_describe( - token -): - - # The token has already been validated, - # so the user is guaranteed to exist. - - # A little bit of processing required here... - processed = token.split(' ')[1] - - # Instantiate UserUtils - uu = UserUtils.UserUtils() - - # Get the user's information - return Response( - data = uu.get_user_info( - username = Token.objects.get(key = processed).user.username - ), - status = status.HTTP_200_OK - ) \ No newline at end of file + +def POST_api_accounts_describe(token): + + # The token has already been validated, + # so the user is guaranteed to exist. + + # A little bit of processing required here... + processed = token.split(" ")[1] + + # Instantiate UserUtils + uu = UserUtils.UserUtils() + + # Get the user's information + return Response( + data=uu.get_user_info(username=Token.objects.get(key=processed).user.username), + status=status.HTTP_200_OK, + ) diff --git a/bco_api/api/scripts/method_specific/POST_api_accounts_new.py b/bco_api/api/scripts/method_specific/POST_api_accounts_new.py index e4c95651..a4fe853b 100755 --- a/bco_api/api/scripts/method_specific/POST_api_accounts_new.py +++ b/bco_api/api/scripts/method_specific/POST_api_accounts_new.py @@ -30,6 +30,7 @@ # Source: https://codeloop.org/django-rest-framework-course-for-beginners/ + def POST_api_accounts_new(request): # An e-mail is provided, and if the e-mail already exists # as an account, then return 403. @@ -40,10 +41,18 @@ def POST_api_accounts_new(request): # Does the account associated with this e-mail already # exist in either a temporary or a permanent user profile? - if db.check_user_exists( p_app_label='api', p_model_name='new_users', p_email=bulk_request['email']) is None: - if User.objects.filter(email=bulk_request['email']).exists(): + if ( + db.check_user_exists( + p_app_label="api", p_model_name="new_users", p_email=bulk_request["email"] + ) + is None + ): + if User.objects.filter(email=bulk_request["email"]).exists(): # Account has already been activated. - return Response(status=status.HTTP_409_CONFLICT, data={"message": "Account has already been activated."}) + return Response( + status=status.HTTP_409_CONFLICT, + data={"message": "Account has already been activated."}, + ) # The email has not already been asked for and # it has not been activated. @@ -56,65 +65,75 @@ def POST_api_accounts_new(request): # Create a temporary identifier. temp_identifier = uuid.uuid4().hex - if 'token' in bulk_request and 'hostname' in bulk_request: + if "token" in bulk_request and "hostname" in bulk_request: p_data = { - 'email': bulk_request['email'], - 'temp_identifier': temp_identifier, - 'hostname': bulk_request['hostname'], - 'token': bulk_request['token'] + "email": bulk_request["email"], + "temp_identifier": temp_identifier, + "hostname": bulk_request["hostname"], + "token": bulk_request["token"], } else: p_data = { - 'email': bulk_request['email'], - 'temp_identifier': temp_identifier + "email": bulk_request["email"], + "temp_identifier": temp_identifier, } objects_written = db.write_object( - p_app_label='api', - p_model_name='new_users', - p_fields=['email', 'temp_identifier', 'hostname', 'token'], - p_data=p_data + p_app_label="api", + p_model_name="new_users", + p_fields=["email", "temp_identifier", "hostname", "token"], + p_data=p_data, ) if objects_written < 1: # There is a problem with the write. - return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR, data="Not able to save the new account.") + return Response( + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + data="Not able to save the new account.", + ) # Send an e-mail to let the requestor know that they # need to follow the activation link within 10 minutes. # Source: https://realpython.com/python-send-email/#sending-fancy-emails - activation_link = '' - template = '' + activation_link = "" + template = "" - activation_link = settings.PUBLIC_HOSTNAME + '/api/accounts/activate/' + urllib.parse.quote(bulk_request['email']) + '/' + temp_identifier + activation_link = ( + settings.PUBLIC_HOSTNAME + + "/api/accounts/activate/" + + urllib.parse.quote(bulk_request["email"]) + + "/" + + temp_identifier + ) template = '

Please click this link within the next 10 minutes to activate your BioCompute Portal account: {}.

'.format( - activation_link, activation_link) + activation_link, activation_link + ) try: send_mail( - subject='Registration for BioCompute Portal', - message='Testing.', + subject="Registration for BioCompute Portal", + message="Testing.", html_message=template, - from_email='mail_sender@portal.aws.biochemistry.gwu.edu', - recipient_list=[ - bulk_request['email'] - ], + from_email="mail_sender@portal.aws.biochemistry.gwu.edu", + recipient_list=[bulk_request["email"]], fail_silently=False, ) except Exception as e: - print('activation_link', activation_link) + print("activation_link", activation_link) # print('ERROR: ', e) # TODO: Should handle when the send_mail function fails? - #return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR, data={"message": "Not able to send authentication email: {}".format(e)}) + # return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR, data={"message": "Not able to send authentication email: {}".format(e)}) return Response(status=status.HTTP_201_CREATED) else: # Account has already been asked for. - return Response(status=status.HTTP_409_CONFLICT, data={"message": "Account has already been requested."}) - + return Response( + status=status.HTTP_409_CONFLICT, + data={"message": "Account has already been requested."}, + ) diff --git a/bco_api/api/scripts/method_specific/POST_api_objects_drafts_create.py b/bco_api/api/scripts/method_specific/POST_api_objects_drafts_create.py index a0018321..c73676b4 100755 --- a/bco_api/api/scripts/method_specific/POST_api_objects_drafts_create.py +++ b/bco_api/api/scripts/method_specific/POST_api_objects_drafts_create.py @@ -5,8 +5,7 @@ -------------------- Creates a new BCO draft object. """ - -from email import message +from api.models import BCO from api.scripts.utilities import DbUtils, UserUtils from api.model.prefix import prefix_table from django.conf import settings @@ -15,6 +14,7 @@ from rest_framework import status from rest_framework.response import Response + def post_api_objects_drafts_create(request): """Create BCO Draft @@ -31,19 +31,17 @@ def post_api_objects_drafts_create(request): """ db_utils = DbUtils.DbUtils() - user = UserUtils.UserUtils().user_from_request(request = request) + user = UserUtils.UserUtils().user_from_request(request=request) prefix_perms = UserUtils.UserUtils().prefix_perms_for_user( - flatten = True, - user_object = user, - specific_permission = ['add'] + flatten=True, user_object=user, specific_permission=["add"] ) # Define the bulk request. - bulk_request = request.data['POST_api_objects_draft_create'] + bulk_request = request.data["POST_api_objects_draft_create"] # Get the object naming information. object_naming_info = settings.OBJECT_NAMING - + root_uri = settings.OBJECT_NAMING["root_uri"] # Construct an array to return the objects. returning = [] any_failed = False @@ -52,118 +50,112 @@ def post_api_objects_drafts_create(request): # item in the array. for creation_object in bulk_request: - # Standardize the prefix. - standardized = creation_object['prefix'].upper() - + prefix = creation_object["prefix"].upper() # Require the macro-level and draft-specific permissions. - if 'add_' + standardized in prefix_perms and 'draft_' + standardized in prefix_perms: - - # Make sure the group the object is being - # assigned to exists. + if "add_" + prefix in prefix_perms and "draft_" + prefix in prefix_perms: + prefix_counter = prefix_table.objects.get(prefix=prefix) + if "object_id" in creation_object: + if BCO.objects.filter(object_id=creation_object["object_id"]).exists(): + returning.append( + db_utils.messages( + parameters={"object_id": creation_object["object_id"]} + )["409_object_conflict"] + ) + any_failed = True + continue + constructed_obj_id = creation_object["object_id"] + else: + object_num = format(prefix_counter.n_objects, "06d") + constructed_obj_id = ( + root_uri + "/" + prefix + "_" + object_num + "/DRAFT" + ) + creation_object["object_id"] = constructed_obj_id - # User does *NOT* have to be in the owner group! - # to assign the object's group owner. if Group.objects.filter( - name = creation_object['owner_group'].lower() + name=creation_object["owner_group"].lower() ).exists(): # TODO: abstract this out to DbUtils. - - # The prefix permission exists and the presumptive - # group owner also exists, so write the object. - - # Source: https://www.webforefront.com/django/singlemodelrecords.html - - # Create the ID template. - - # Use the root URI and prefix to construct the name. - constructed_name = object_naming_info['uri_regex'].replace( - 'root_uri', - object_naming_info['root_uri'] - ) - constructed_name = constructed_name.replace('prefix', standardized) - - # Get rid of the rest of the regex for the name. - prefix_location = constructed_name.index(standardized) - prefix_length = len(standardized) - constructed_name = constructed_name[0:prefix_location+prefix_length] - # Create a draft ID that is essentially randomized. - prefix_counter = prefix_table.objects.get(prefix=standardized) - creation_object['object_id'] = constructed_name + '_' + \ - '{:06d}'.format(prefix_counter.n_objects) + '/DRAFT' - + # constructed_name = object_naming_info["uri_regex"].replace( + # "root_uri", object_naming_info["root_uri"] + # ) + # constructed_name = constructed_name.replace("prefix", prefix) + + # prefix_location = constructed_name.index(prefix) + # prefix_length = len(prefix) + # constructed_name = constructed_name[0 : prefix_location + prefix_length] + # + # creation_object["object_id"] = ( + # constructed_name + # + "_" + # + "{:06d}".format(prefix_counter.n_objects) + # + "/DRAFT" + # ) # Make sure to create the object ID field in our draft. - creation_object['contents']['object_id'] = creation_object['object_id'] - + creation_object["contents"]["object_id"] = constructed_obj_id # Instantiate the owner group as we'll need it a few times here. - owner_group = Group.objects.get(name = creation_object['owner_group']) + owner_group = Group.objects.get(name=creation_object["owner_group"]) # Django wants a primary key for the Group... - creation_object['owner_group'] = owner_group.name + creation_object["owner_group"] = owner_group.name # Set the owner user (the requestor). - creation_object['owner_user'] = user.username + creation_object["owner_user"] = user.username # Give the creation object the prefix. - creation_object['prefix'] = standardized + creation_object["prefix"] = prefix # This is a DRAFT. - creation_object['state'] = 'DRAFT' + creation_object["state"] = "DRAFT" # Set the datetime properly. - creation_object['last_update'] = timezone.now() + creation_object["last_update"] = timezone.now() # Write to the database. objects_written = db_utils.write_object( - p_app_label = 'api', - p_model_name = 'bco', - p_fields = [ - 'contents', - 'last_update', - 'object_id', - 'owner_group', - 'owner_user', - 'prefix', - 'schema', - 'state'], - p_data = creation_object + p_app_label="api", + p_model_name="bco", + p_fields=[ + "contents", + "last_update", + "object_id", + "owner_group", + "owner_user", + "prefix", + "schema", + "state", + ], + p_data=creation_object, ) - prefix_counter.n_objects = prefix_counter.n_objects + 1 - prefix_counter.save() if objects_written < 1: # Issue with writing out to DB - returning.append(db_utils.messages(parameters={ })['400_bad_request']) + returning.append( + db_utils.messages(parameters={})["400_bad_request"] + ) any_failed = True - - # Object creator automatically has full permissions - # on the object. This is checked by checking whether - # or not the requestor matches the owner_user primary - # key OR if they are in a group with given permissions - # (not done here, but in the urls that request - # a draft object, i.e. (GET) - # and (POST) api/objects/read/). - - # The owner group is given permissions in the post_save - # receiver in models.py - + prefix_counter.n_objects = prefix_counter.n_objects + 1 + prefix_counter.save() # Update the request status. - returning.append(db_utils.messages(parameters = creation_object)['201_create']) + returning.append( + db_utils.messages(parameters=creation_object)["201_create"] + ) else: # Update the request status. - returning.append(db_utils.messages(parameters = {}) - ['400_bad_request']) + returning.append(db_utils.messages(parameters={})["400_bad_request"]) any_failed = True else: # Update the request status. - returning.append(db_utils.messages(parameters = { - 'prefix': creation_object['prefix']}) - ['401_prefix_unauthorized']) + returning.append( + db_utils.messages(parameters={"prefix": creation_object["prefix"]})[ + "401_prefix_unauthorized" + ] + ) any_failed = True if any_failed: return Response(status=status.HTTP_207_MULTI_STATUS, data=returning) - return Response(status = status.HTTP_200_OK, data = returning) + return Response(status=status.HTTP_200_OK, data=returning) diff --git a/bco_api/api/scripts/method_specific/POST_api_objects_drafts_delete.py b/bco_api/api/scripts/method_specific/POST_api_objects_drafts_delete.py index 403a7ea7..95d36b69 100755 --- a/bco_api/api/scripts/method_specific/POST_api_objects_drafts_delete.py +++ b/bco_api/api/scripts/method_specific/POST_api_objects_drafts_delete.py @@ -16,122 +16,102 @@ # Source: https://codeloop.org/django-rest-framework-course-for-beginners/ -def POST_api_objects_drafts_delete( - incoming -): - - # Take the bulk request and delete a draft object from it. - - # Instantiate any necessary imports. - db = DbUtils.DbUtils() - uu = UserUtils.UserUtils() - - # The token has already been validated, - # so the user is guaranteed to exist. - - # Get the User object. - user = uu.user_from_request( - rq = incoming - ) - - # Get the user's prefix permissions. - px_perms = uu.prefix_perms_for_user( - flatten = True, - user_object = user, - specific_permission = ['add'] - ) - - # Define the bulk request. - bulk_request = incoming.data['POST_api_objects_drafts_delete'] - - # Construct an array to return the objects. - returning = [] - - # Since bulk_request is an array, go over each - # item in the array. - for deletion_object in bulk_request: - - # Get the prefix for this draft. - standardized = deletion_object['object_id'].split('/')[-1].split('_')[0].upper() - - # Does the requestor have delete permissions for - # the *prefix*? - if 'delete_' + standardized in px_perms: - - # The requestor has delete permissions for - # the prefix, but do they have object-level - # delete permissions? - - # This can be checked by seeing if the requestor - # is the object owner OR they are a user with - # object-level delete permissions OR if they are in a - # group that has object-level change permissions. - - # To check these options, we need the actual object. - if bco.objects.filter(object_id = deletion_object['object_id']).exists(): - - objected = bco.objects.get( - object_id = deletion_object['object_id'] - ) - - # We don't care where the delete permission comes from, - # be it a User permission or a Group permission. - all_permissions = get_perms( - user, - objected - ) - - if user.username == objected.owner_user.username or 'delete_' + standardized in all_permissions: - - # Delete the object. - objected.delete() - - # Update the request status. - returning.append( - db.messages( - parameters = { - 'object_id': deletion_object['object_id'] - } - )['200_OK_object_delete'] - ) - - else: - - # Insufficient permissions. - returning.append( - db.messages( - parameters = {} - )['403_insufficient_permissions'] - ) - - else: - - # Couldn't find the object. - returning.append( - db.messages( - parameters = { - 'object_id': deletion_object['object_id'] - } - ) - )['404_object_id'] - - else: - - # Update the request status. - returning.append( - db.messages( - parameters = { - 'prefix': standardized - } - )['401_prefix_unauthorized'] - ) - - # As this view is for a bulk operation, status 200 - # means that the request was successfully processed, - # but NOT necessarily each item in the request. - # For example, a table may not have been found for the first - # requested draft. - return Response( - status = status.HTTP_200_OK, - data = returning - ) \ No newline at end of file + +def POST_api_objects_drafts_delete(incoming): + + # Take the bulk request and delete a draft object from it. + + # Instantiate any necessary imports. + db = DbUtils.DbUtils() + uu = UserUtils.UserUtils() + + # The token has already been validated, + # so the user is guaranteed to exist. + + # Get the User object. + user = uu.user_from_request(rq=incoming) + + # Get the user's prefix permissions. + px_perms = uu.prefix_perms_for_user( + flatten=True, user_object=user, specific_permission=["add"] + ) + + # Define the bulk request. + bulk_request = incoming.data["POST_api_objects_drafts_delete"] + + # Construct an array to return the objects. + returning = [] + + # Since bulk_request is an array, go over each + # item in the array. + for deletion_object in bulk_request: + + # Get the prefix for this draft. + standardized = deletion_object["object_id"].split("/")[-1].split("_")[0].upper() + + # Does the requestor have delete permissions for + # the *prefix*? + if "delete_" + standardized in px_perms: + + # The requestor has delete permissions for + # the prefix, but do they have object-level + # delete permissions? + + # This can be checked by seeing if the requestor + # is the object owner OR they are a user with + # object-level delete permissions OR if they are in a + # group that has object-level change permissions. + + # To check these options, we need the actual object. + if bco.objects.filter(object_id=deletion_object["object_id"]).exists(): + + objected = bco.objects.get(object_id=deletion_object["object_id"]) + + # We don't care where the delete permission comes from, + # be it a User permission or a Group permission. + all_permissions = get_perms(user, objected) + + if ( + user.username == objected.owner_user.username + or "delete_" + standardized in all_permissions + ): + + # Delete the object. + objected.delete() + + # Update the request status. + returning.append( + db.messages( + parameters={"object_id": deletion_object["object_id"]} + )["200_OK_object_delete"] + ) + + else: + + # Insufficient permissions. + returning.append( + db.messages(parameters={})["403_insufficient_permissions"] + ) + + else: + + # Couldn't find the object. + returning.append( + db.messages(parameters={"object_id": deletion_object["object_id"]}) + )["404_object_id"] + + else: + + # Update the request status. + returning.append( + db.messages(parameters={"prefix": standardized})[ + "401_prefix_unauthorized" + ] + ) + + # As this view is for a bulk operation, status 200 + # means that the request was successfully processed, + # but NOT necessarily each item in the request. + # For example, a table may not have been found for the first + # requested draft. + return Response(status=status.HTTP_200_OK, data=returning) diff --git a/bco_api/api/scripts/method_specific/POST_api_objects_drafts_modify.py b/bco_api/api/scripts/method_specific/POST_api_objects_drafts_modify.py index 09b8cd86..af6bd27e 100755 --- a/bco_api/api/scripts/method_specific/POST_api_objects_drafts_modify.py +++ b/bco_api/api/scripts/method_specific/POST_api_objects_drafts_modify.py @@ -18,8 +18,9 @@ # Source: https://codeloop.org/django-rest-framework-course-for-beginners/ + def post_api_objects_drafts_modify(request): - """ Modify Draft + """Modify Draft Take the bulk request and modify a draft object from it. @@ -39,12 +40,10 @@ def post_api_objects_drafts_modify(request): """ db_utils = DbUtils.DbUtils() - user = UserUtils.UserUtils().user_from_request(request = request) - bulk_request = request.data['POST_api_objects_drafts_modify'] + user = UserUtils.UserUtils().user_from_request(request=request) + bulk_request = request.data["POST_api_objects_drafts_modify"] px_perms = UserUtils.UserUtils().prefix_perms_for_user( - flatten = True, - user_object = user, - specific_permission = ['add'] + flatten=True, user_object=user, specific_permission=["add"] ) # Construct an array to return the objects. @@ -52,14 +51,14 @@ def post_api_objects_drafts_modify(request): any_failed = False for draft_object in bulk_request: # Get the prefix for this draft. - prefix = draft_object['object_id'].split('/')[-2].split('_')[0].upper() + prefix = draft_object["object_id"].split("/")[-2].split("_")[0].upper() # Does the requestor have change permissions for # the *prefix*? # TODO: add permission setting view... - #if 'change_' + prefix in px_perms: - if 'add_' + prefix in px_perms: + # if 'change_' + prefix in px_perms: + if "add_" + prefix in px_perms: # The requestor has change permissions for # the prefix, but do they have object-level @@ -70,17 +69,21 @@ def post_api_objects_drafts_modify(request): # object-level change permissions OR if they are in a # group that has object-level change permissions. # To check these options, we need the actual object. - if BCO.objects.filter(object_id = draft_object['contents']['object_id']).exists(): + if BCO.objects.filter( + object_id=draft_object["contents"]["object_id"] + ).exists(): objected = BCO.objects.get( - object_id = draft_object['contents']['object_id'] + object_id=draft_object["contents"]["object_id"] ) # We don't care where the view permission comes from, # be it a User permission or a Group permission. all_permissions = get_perms(user, objected) # TODO: add permission setting view... - if user.username == objected.owner_user.username or \ - 'add_' + prefix in px_perms: + if ( + user.username == objected.owner_user.username + or "add_" + prefix in px_perms + ): # # User does *NOT* have to be in the owner group! # # to assign the object's group owner. @@ -90,12 +93,12 @@ def post_api_objects_drafts_modify(request): # # Update the object. # *** COMPLETELY OVERWRITES CONTENTS!!! *** - objected.contents = draft_object['contents'] + objected.contents = draft_object["contents"] + + if "state" in draft_object: + if draft_object["state"] == "DELETE": + objected.state = "DELETE" - if 'state' in draft_object: - if draft_object['state'] == 'DELETE': - objected.state = 'DELETE' - # Set the update time. objected.last_update = timezone.now() @@ -104,31 +107,37 @@ def post_api_objects_drafts_modify(request): # Update the request status. returning.append( - db_utils.messages(parameters = { - 'object_id': draft_object['object_id']} - )['200_update']) + db_utils.messages( + parameters={"object_id": draft_object["object_id"]} + )["200_update"] + ) else: # Insufficient permissions. - returning.append(db_utils.messages(parameters = {} - )['403_insufficient_permissions']) + returning.append( + db_utils.messages(parameters={})["403_insufficient_permissions"] + ) any_failed = True else: returning.append( - db_utils.messages(parameters = {'object_id': draft_object['object_id']} - )['404_object_id']) + db_utils.messages( + parameters={"object_id": draft_object["object_id"]} + )["404_object_id"] + ) any_failed = True else: returning.append( - db_utils.messages(parameters = {'prefix': prefix} - )['401_prefix_unauthorized']) + db_utils.messages(parameters={"prefix": prefix})[ + "401_prefix_unauthorized" + ] + ) any_failed = True if any_failed and len(returning) == 1: - if returning[0]['status_code'] == '403': + if returning[0]["status_code"] == "403": return Response(status=status.HTTP_403_FORBIDDEN, data=returning) else: return Response(status=status.HTTP_300_MULTIPLE_CHOICES, data=returning) if any_failed and len(returning) > 1: return Response(status=status.HTTP_300_MULTIPLE_CHOICES, data=returning) else: - return Response(status = status.HTTP_200_OK, data = returning) + return Response(status=status.HTTP_200_OK, data=returning) diff --git a/bco_api/api/scripts/method_specific/POST_api_objects_drafts_permissions.py b/bco_api/api/scripts/method_specific/POST_api_objects_drafts_permissions.py index 3129368a..e5395c99 100755 --- a/bco_api/api/scripts/method_specific/POST_api_objects_drafts_permissions.py +++ b/bco_api/api/scripts/method_specific/POST_api_objects_drafts_permissions.py @@ -39,7 +39,7 @@ def POST_api_objects_drafts_permissions(incoming): px_perms = uu.prefix_perms_for_user(flatten=True, user_object=user) # Define the bulk request. - bulk_request = incoming.data['POST_api_objects_drafts_permissions'] + bulk_request = incoming.data["POST_api_objects_drafts_permissions"] # Construct an array to return the objects. returning = [] @@ -50,24 +50,27 @@ def POST_api_objects_drafts_permissions(incoming): for creation_object in bulk_request: # Get the prefix for this object. - standardized = creation_object['object_id'].split('/')[-1].split('_')[0].upper() + standardized = creation_object["object_id"].split("/")[-1].split("_")[0].upper() # Does the requestor have view permissions for # the *prefix*? - if 'view_' + standardized in px_perms: - print('bulk_request', list(BCO.objects.filter(object_id=creation_object['object_id']))) + if "view_" + standardized in px_perms: + print( + "bulk_request", + list(BCO.objects.filter(object_id=creation_object["object_id"])), + ) # The requestor has change view for # the prefix, but do they have object-level # view permissions? # This can be checked by seeing if the requestor # is the object owner OR they are a user with - # object-level view permissions OR if they are in a + # object-level view permissions OR if they are in a # group that has object-level view permissions. # To check these options, we need the actual object. - if BCO.objects.filter(object_id=creation_object['object_id']).exists(): - objected = BCO.objects.get(object_id=creation_object['object_id']) + if BCO.objects.filter(object_id=creation_object["object_id"]).exists(): + objected = BCO.objects.get(object_id=creation_object["object_id"]) # We don't care where the view permission comes from, # be it a User permission or a Group permission. @@ -77,30 +80,29 @@ def POST_api_objects_drafts_permissions(incoming): # basic view permissions for this user and object. all_permissions = get_perms(user, objected) - if user.username == objected.owner_user.username or 'view_' + standardized in all_permissions: + if ( + user.username == objected.owner_user.username + or "view_" + standardized in all_permissions + ): # Kick back the permissions, # *** but only for this requestor (user) ***. # Create a dictionary to return the permissions. - perms = { - 'username' : { }, - 'group_names': { } - } + perms = {"username": {}, "group_names": {}} # We want to return the permissions in fine detail # by user permissions and group permissions. up = get_user_perms(user, objected) - perms['username'][user.username] = list(up) + perms["username"][user.username] = list(up) # Get user's groups. user_groups = list( - Group.objects.filter(user=user.pk).values_list( - 'name', - flat=True - ) - ) + Group.objects.filter(user=user.pk).values_list( + "name", flat=True + ) + ) gp = get_groups_with_perms(objected, attach_perms=True) @@ -108,25 +110,41 @@ def POST_api_objects_drafts_permissions(incoming): # the user's groups. for g, p in gp.items(): if g.name in user_groups: - perms['group_names'][g.name] = p + perms["group_names"][g.name] = p # print(perms) # Update the request status. - returning.append(db.messages(parameters={'object_id': creation_object['object_id'], 'object_perms': perms})['200_OK_object_permissions']) + returning.append( + db.messages( + parameters={ + "object_id": creation_object["object_id"], + "object_perms": perms, + } + )["200_OK_object_permissions"] + ) else: # Insufficient permissions. - returning.append(db.messages(parameters={})['403_insufficient_permissions']) + returning.append( + db.messages(parameters={})["403_insufficient_permissions"] + ) any_failed = True else: # Couldn't find the object. - returning.append(db.messages(parameters={ - 'object_id': creation_object['object_id']})['404_object_id']) + returning.append( + db.messages(parameters={"object_id": creation_object["object_id"]})[ + "404_object_id" + ] + ) any_failed = True else: # Update the request status. - returning.append(db.messages(parameters={'prefix': standardized})['401_prefix_unauthorized']) + returning.append( + db.messages(parameters={"prefix": standardized})[ + "401_prefix_unauthorized" + ] + ) any_failed = True # As this view is for a bulk operation, status 200 diff --git a/bco_api/api/scripts/method_specific/POST_api_objects_drafts_permissions_set.py b/bco_api/api/scripts/method_specific/POST_api_objects_drafts_permissions_set.py index 08d21225..cf0aaaf1 100755 --- a/bco_api/api/scripts/method_specific/POST_api_objects_drafts_permissions_set.py +++ b/bco_api/api/scripts/method_specific/POST_api_objects_drafts_permissions_set.py @@ -8,7 +8,13 @@ from ..utilities import UserUtils # Permisions for objects -from guardian.shortcuts import assign_perm, get_perms, get_groups_with_perms, get_users_with_perms, remove_perm +from guardian.shortcuts import ( + assign_perm, + get_perms, + get_groups_with_perms, + get_users_with_perms, + remove_perm, +) from django.contrib.auth.models import Group, User, Permission # Responses @@ -34,13 +40,11 @@ def POST_api_objects_drafts_permissions_set(incoming): # Get the user's prefix permissions. px_perms = uu.prefix_perms_for_user( - flatten=True, - user_object=user, - specific_permission=['change'] - ) + flatten=True, user_object=user, specific_permission=["change"] + ) # Define the bulk request. - bulk_request = incoming.data['POST_api_objects_drafts_permissions_set'] + bulk_request = incoming.data["POST_api_objects_drafts_permissions_set"] # Construct an array to return the objects. returning = [] @@ -50,7 +54,9 @@ def POST_api_objects_drafts_permissions_set(incoming): for permission_object in bulk_request: # Get the prefix for this object. - standardized = permission_object['object_id'].split('/')[-1].split('_')[0].upper() + standardized = ( + permission_object["object_id"].split("/")[-1].split("_")[0].upper() + ) # Does the requestor have any change # permissions for the prefix? @@ -62,7 +68,7 @@ def POST_api_objects_drafts_permissions_set(incoming): # In essence, we are asking whether or not # the requestor can change any object # under this prefix. - if 'change_' + standardized in px_perms: + if "change_" + standardized in px_perms: # The requestor has change for # the prefix, but do they have object-level @@ -74,107 +80,159 @@ def POST_api_objects_drafts_permissions_set(incoming): # group that has object-level change permissions. # To check these options, we need the actual object. - if BCO.objects.filter(object_id=permission_object['object_id']).exists(): + if BCO.objects.filter(object_id=permission_object["object_id"]).exists(): - objected = BCO.objects.get( - object_id=permission_object['object_id'] - ) + objected = BCO.objects.get(object_id=permission_object["object_id"]) # We don't care where the change permission comes from, # be it a User permission or a Group permission. - all_permissions = get_perms( - user, - objected - ) + all_permissions = get_perms(user, objected) - if user.username == objected.owner_user.username or 'change_' + objected.object_id in all_permissions: + if ( + user.username == objected.owner_user.username + or "change_" + objected.object_id in all_permissions + ): - if 'actions' in permission_object: + if "actions" in permission_object: # Set the working object to the actions. - action_set = permission_object['actions'] + action_set = permission_object["actions"] # Removals are processed first, then additions. # Remove the permissions provided, if any. # TODO: This doesn't look like it would work here. - if 'remove_permissions' in action_set: - for perm, assignee in action_set['remove_permissions']: - if assignee == 'users': + if "remove_permissions" in action_set: + for perm, assignee in action_set["remove_permissions"]: + if assignee == "users": # TODO: if assignee is actually a string users, this will just loop through the characters for u in assignee: if uu.check_user_exists(un=u): remove_perm( - perm=Permission.objects.get(codename=perm + "_" + objected.object_id), - user_or_group=User.objects.get(username=u), - obj=objected - ) - if assignee == 'groups': + perm=Permission.objects.get( + codename=perm + + "_" + + objected.object_id + ), + user_or_group=User.objects.get( + username=u + ), + obj=objected, + ) + if assignee == "groups": for g in assignee: if uu.check_group_exists(n=g): remove_perm( - perm=Permission.objects.get(codename=perm + "_" + objected.object_id), - user_or_group=Group.objects.get(name=g), - obj=objected - ) - - if 'full_permissions' in action_set: - for up, perms in get_users_with_perms(obj=objected, attach_perms=True).items(): + perm=Permission.objects.get( + codename=perm + + "_" + + objected.object_id + ), + user_or_group=Group.objects.get(name=g), + obj=objected, + ) + + if "full_permissions" in action_set: + for up, perms in get_users_with_perms( + obj=objected, attach_perms=True + ).items(): for perm in perms: - remove_perm(perm=perm, user_or_group=up, obj=objected) + remove_perm( + perm=perm, user_or_group=up, obj=objected + ) - for gp, perms in get_groups_with_perms(obj=objected, attach_perms=True).items(): + for gp, perms in get_groups_with_perms( + obj=objected, attach_perms=True + ).items(): for perm in perms: - remove_perm(perm=perm, user_or_group=gp, obj=objected) - - for perm, assignee in action_set['full_permissions'].items(): - if assignee == 'users': + remove_perm( + perm=perm, user_or_group=gp, obj=objected + ) + + for perm, assignee in action_set[ + "full_permissions" + ].items(): + if assignee == "users": for u in assignee: if uu.check_user_exists(un=u): assign_perm( - perm=Permission.objects.get(codename=perm + "_" + objected.object_id), - user_or_group=User.objects.get(username=u), - obj=objected - ) - - if assignee == 'groups': + perm=Permission.objects.get( + codename=perm + + "_" + + objected.object_id + ), + user_or_group=User.objects.get( + username=u + ), + obj=objected, + ) + + if assignee == "groups": for g in assignee: if uu.check_group_exists(n=g): assign_perm( - perm=Permission.objects.get(codename=perm + "_" + objected.object_id), - user_or_group=Group.objects.get(name=g), - obj=objected - ) - - if 'add_permissions' in action_set: - for perm, assignee in action_set['add_permissions'].items(): - if assignee == 'users': + perm=Permission.objects.get( + codename=perm + + "_" + + objected.object_id + ), + user_or_group=Group.objects.get(name=g), + obj=objected, + ) + + if "add_permissions" in action_set: + for perm, assignee in action_set["add_permissions"].items(): + if assignee == "users": for u in assignee: if uu.check_user_exists(un=u): assign_perm( - perm=Permission.objects.get(codename=perm + "_" + objected.object_id), - user_or_group=User.objects.get(username=u), - obj=objected - ) - if assignee == 'groups': + perm=Permission.objects.get( + codename=perm + + "_" + + objected.object_id + ), + user_or_group=User.objects.get( + username=u + ), + obj=objected, + ) + if assignee == "groups": for g in assignee: if uu.check_group_exists(n=g): assign_perm( - perm=Permission.objects.get(codename=perm + "_" + objected.object_id), - user_or_group=Group.objects.get(name=g), - obj=objected - ) - - returning.append(db.messages(parameters={'object_id': objected.object_id})['200_OK_object_permissions_set']) + perm=Permission.objects.get( + codename=perm + + "_" + + objected.object_id + ), + user_or_group=Group.objects.get(name=g), + obj=objected, + ) + + returning.append( + db.messages(parameters={"object_id": objected.object_id})[ + "200_OK_object_permissions_set" + ] + ) else: # Insufficient permissions. - returning.append(db.messages(parameters={ })['403_insufficient_permissions']) + returning.append( + db.messages(parameters={})["403_insufficient_permissions"] + ) else: # Couldn't find the object. - returning.append(db.messages(parameters={'object_id': permission_object['object_id']})['404_object_id']) + returning.append( + db.messages( + parameters={"object_id": permission_object["object_id"]} + )["404_object_id"] + ) else: # Update the request status. - returning.append(db.messages(parameters={'prefix': standardized})['401_prefix_unauthorized']) + returning.append( + db.messages(parameters={"prefix": standardized})[ + "401_prefix_unauthorized" + ] + ) # As this view is for a bulk operation, status 200 # means that the request was successfully processed, diff --git a/bco_api/api/scripts/method_specific/POST_api_objects_drafts_publish.py b/bco_api/api/scripts/method_specific/POST_api_objects_drafts_publish.py index 26f4b46b..c30307df 100755 --- a/bco_api/api/scripts/method_specific/POST_api_objects_drafts_publish.py +++ b/bco_api/api/scripts/method_specific/POST_api_objects_drafts_publish.py @@ -41,52 +41,51 @@ def post_api_objects_drafts_publish(request): prefix_perms = UserUtils.UserUtils().prefix_perms_for_user( flatten=True, user_object=user ) - bulk_request = request.data['POST_api_objects_drafts_publish'] + bulk_request = request.data["POST_api_objects_drafts_publish"] for publish_object in bulk_request: - draft_exists = (BCO.objects.filter( - object_id=publish_object['draft_id'], state='DRAFT').exists() - ) + draft_exists = BCO.objects.filter( + object_id=publish_object["draft_id"], state="DRAFT" + ).exists() if draft_exists is False: - returning.append(db_utils.messages( - parameters={'object_id': publish_object['draft_id'] } - )['404_object_id'] + returning.append( + db_utils.messages(parameters={"object_id": publish_object["draft_id"]})[ + "404_object_id" + ] ) any_failed = True continue - objected = BCO.objects.get(object_id=publish_object['draft_id']) - new_version = objected.contents['provenance_domain']['version'] - prefix = publish_object['prefix'].upper() + objected = BCO.objects.get(object_id=publish_object["draft_id"]) + new_version = objected.contents["provenance_domain"]["version"] + prefix = publish_object["prefix"].upper() prefix_counter = prefix_table.objects.get(prefix=prefix) - draft_id = publish_object['draft_id'] + draft_id = publish_object["draft_id"] - if publish_object.get('delete_draft') is not None: - delete_draft = publish_object['delete_draft'] - else: + if publish_object.get("delete_draft") is not None: + delete_draft = publish_object["delete_draft"] + else: delete_draft = False - if 'object_id' not in publish_object: - object_id = publish_object['draft_id'].split('/')[0:4] + if "object_id" not in publish_object: + object_id = publish_object["draft_id"].split("/")[0:4] object_id.append(new_version) - object_id = '/'.join(object_id) + object_id = "/".join(object_id) else: - object_id = publish_object['object_id'] + object_id = publish_object["object_id"] - versioned = {'published_id': object_id} + versioned = {"published_id": object_id} # versioned = db_utils.check_version_rules( # published_id=object_id # ) - prefix_auth = 'publish_' + prefix in prefix_perms + prefix_auth = "publish_" + prefix in prefix_perms object_exists = BCO.objects.filter(object_id=object_id).exists() if object_exists is True: print(object_id) - parameters = {'object_id': object_id } - returning.append(db_utils.messages(parameters) - ['409_object_conflict'] - ) + parameters = {"object_id": object_id} + returning.append(db_utils.messages(parameters)["409_object_conflict"]) any_failed = True continue @@ -99,54 +98,61 @@ def post_api_objects_drafts_publish(request): # if is_owner is True or can_publish is True: if delete_draft is True: objected.last_update = timezone.now() - objected.state = 'PUBLISHED' + objected.state = "PUBLISHED" objected.owner_group = owner_group - objected.object_id = versioned['published_id'] - objected.contents['object_id'] = versioned['published_id'] + objected.object_id = versioned["published_id"] + objected.contents["object_id"] = versioned["published_id"] objected.save() # Update the request status. - returning.append(db_utils.messages( - parameters=versioned)['200_OK_object_publish_draft_deleted'] + returning.append( + db_utils.messages(parameters=versioned)[ + "200_OK_object_publish_draft_deleted" + ] ) else: new_object = {} - new_object['contents'] = objected.contents - new_object['object_id'] = object_id - new_object['contents']['object_id'] = object_id - new_object['owner_group'] = owner_group - new_object['owner_user'] = objected.owner_user - new_object['prefix'] = objected.prefix - new_object['last_update'] = timezone.now() - new_object['schema'] = 'IEEE' - new_object['state'] = 'PUBLISHED' + new_object["contents"] = objected.contents + new_object["object_id"] = object_id + new_object["contents"]["object_id"] = object_id + new_object["owner_group"] = owner_group + new_object["owner_user"] = objected.owner_user + new_object["prefix"] = objected.prefix + new_object["last_update"] = timezone.now() + new_object["schema"] = "IEEE" + new_object["state"] = "PUBLISHED" # Write to the database. objects_written = db_utils.write_object( - p_app_label = 'api', - p_model_name = 'BCO', - p_fields = [ - 'contents', - 'last_update', - 'object_id', - 'owner_group', - 'owner_user', - 'prefix', - 'schema', - 'state'], - p_data = new_object + p_app_label="api", + p_model_name="BCO", + p_fields=[ + "contents", + "last_update", + "object_id", + "owner_group", + "owner_user", + "prefix", + "schema", + "state", + ], + p_data=new_object, ) prefix_counter.n_objects = prefix_counter.n_objects + 1 prefix_counter.save() if objects_written < 1: # Issue with writing out to DB - returning.append(db_utils.messages(parameters={ })['400_bad_request']) + returning.append( + db_utils.messages(parameters={})["400_bad_request"] + ) any_failed = True else: # Update the request status. - returning.append(db_utils.messages( - parameters=versioned)['200_OK_object_publish_draft_not_deleted'] + returning.append( + db_utils.messages(parameters=versioned)[ + "200_OK_object_publish_draft_not_deleted" + ] ) # else: @@ -157,9 +163,12 @@ def post_api_objects_drafts_publish(request): # any_failed = True else: - # Update the request status. - returning.append(db_utils.messages( - parameters={'prefix': prefix })['401_prefix_unauthorized']) + # Update the request status. + returning.append( + db_utils.messages(parameters={"prefix": prefix})[ + "401_prefix_publish_unauthorized" + ] + ) any_failed = True # published = db_utils.publish( @@ -186,7 +195,6 @@ def post_api_objects_drafts_publish(request): # # Does the requestor have delete permissions on # # the object? - # As this view is for a bulk operation, status 200 # means that the request was successfully processed, # but NOT necessarily each item in the request. diff --git a/bco_api/api/scripts/method_specific/POST_api_objects_drafts_read.py b/bco_api/api/scripts/method_specific/POST_api_objects_drafts_read.py index cb6b14f1..e68a2153 100755 --- a/bco_api/api/scripts/method_specific/POST_api_objects_drafts_read.py +++ b/bco_api/api/scripts/method_specific/POST_api_objects_drafts_read.py @@ -28,19 +28,15 @@ def POST_api_objects_drafts_read(incoming): # so the user is guaranteed to exist. # Get the User object. - user = uu.user_from_request( - rq=incoming - ) + user = uu.user_from_request(rq=incoming) # Get the user's prefix permissions. px_perms = uu.prefix_perms_for_user( - flatten=True, - user_object=user, - specific_permission=['view'] - ) + flatten=True, user_object=user, specific_permission=["view"] + ) # Define the bulk request. - bulk_request = incoming.data['POST_api_objects_drafts_read'] + bulk_request = incoming.data["POST_api_objects_drafts_read"] # Construct an array to return the objects. returning = [] @@ -50,11 +46,11 @@ def POST_api_objects_drafts_read(incoming): # item in the array. for read_object in bulk_request: # Get the prefix for this draft. - standardized = read_object['object_id'].split('/')[-1].split('_')[0].upper() + standardized = read_object["object_id"].split("/")[-1].split("_")[0].upper() # Does the requestor have view permissions for # the *prefix*? - if 'view_' + standardized in px_perms: + if "view_" + standardized in px_perms: # The requestor has view permissions for # the prefix, but do they have object-level @@ -66,61 +62,53 @@ def POST_api_objects_drafts_read(incoming): # group that has object-level view permissions. # To check these options, we need the actual object. - if BCO.objects.filter(object_id=read_object['object_id']).exists(): - objected = BCO.objects.get( - object_id=read_object['object_id'] - ) + if BCO.objects.filter(object_id=read_object["object_id"]).exists(): + objected = BCO.objects.get(object_id=read_object["object_id"]) # We don't care where the view permission comes from, # be it a User permission or a Group permission. - all_permissions = get_perms( - user, - objected - ) + all_permissions = get_perms(user, objected) - if user.username == objected.owner_user.username or 'view_' + standardized in all_permissions: + if ( + user.username == objected.owner_user.username + or "view_" + standardized in all_permissions + ): # Read the object. returning.append( - db.messages( - parameters={ - 'contents' : objected.contents, - 'object_id': read_object['object_id'] - } - )['200_OK_object_delete'] - ) + db.messages( + parameters={ + "contents": objected.contents, + "object_id": read_object["object_id"], + } + )["200_OK_object_delete"] + ) else: # Insufficient permissions. returning.append( - db.messages( - parameters={ } - )['403_insufficient_permissions'] - ) + db.messages(parameters={})["403_insufficient_permissions"] + ) any_failed = True else: # Couldn't find the object. returning.append( - db.messages( - parameters={ - 'object_id': read_object['object_id'] - } - ) - ['404_object_id']) + db.messages(parameters={"object_id": read_object["object_id"]})[ + "404_object_id" + ] + ) any_failed = True else: # Update the request status. returning.append( - db.messages( - parameters={ - 'prefix': standardized - } - )['401_prefix_unauthorized'] - ) + db.messages(parameters={"prefix": standardized})[ + "401_prefix_unauthorized" + ] + ) any_failed = True # As this view is for a bulk operation, status 200 @@ -129,11 +117,5 @@ def POST_api_objects_drafts_read(incoming): # For example, a table may not have been found for the first # requested draft. if any_failed: - return Response( - status=status.HTTP_300_MULTIPLE_CHOICES, - data=returning - ) - return Response( - status=status.HTTP_200_OK, - data=returning - ) + return Response(status=status.HTTP_300_MULTIPLE_CHOICES, data=returning) + return Response(status=status.HTTP_200_OK, data=returning) diff --git a/bco_api/api/scripts/method_specific/POST_api_objects_drafts_token.py b/bco_api/api/scripts/method_specific/POST_api_objects_drafts_token.py index 17e42138..43438e2e 100755 --- a/bco_api/api/scripts/method_specific/POST_api_objects_drafts_token.py +++ b/bco_api/api/scripts/method_specific/POST_api_objects_drafts_token.py @@ -5,11 +5,13 @@ """ import re + # Concatenating QuerySets from itertools import chain from typing import Optional, Tuple from api.models import BCO from api.scripts.utilities import UserUtils + # Object-level permissions from guardian.shortcuts import get_objects_for_user @@ -17,14 +19,14 @@ from rest_framework import status from rest_framework.response import Response -# Below is helper code to deal with how we are allowing non standard +# Below is helper code to deal with how we are allowing non standard # versions (i.e. 1.2 instead of 1.2.0, etc). import semver from semver import VersionInfo as Version BASEVERSION = re.compile( - r"""[vV]? + r"""[vV]? (?P0|[1-9]\d*) (\. (?P0|[1-9]\d*) @@ -33,8 +35,8 @@ )? )? """, - re.VERBOSE, - ) + re.VERBOSE, +) def coerce(version: str) -> Tuple[Version, Optional[str]]: @@ -64,10 +66,10 @@ def coerce(version: str) -> Tuple[Version, Optional[str]]: return (None, version) ver = { - key: 0 if value is None else value for key, value in match.groupdict().items() - } + key: 0 if value is None else value for key, value in match.groupdict().items() + } ver = Version(**ver) - rest = match.string[match.end():] # noqa:E203 + rest = match.string[match.end() :] # noqa:E203 return ver, rest @@ -121,23 +123,25 @@ def POST_api_objects_drafts_token(rqst, internal=False): # Use an empty list of perms to get ANY perm. # Source: https://stackoverflow.com/a/24980558 - user_objects = get_objects_for_user(user=user_info, perms=[], klass=BCO, any_perm=True) + user_objects = get_objects_for_user( + user=user_info, perms=[], klass=BCO, any_perm=True + ) # Now get all objects under these prefixes. - prefix_objects = BCO.objects.filter(prefix__in=user_prefixes, state='DRAFT') + prefix_objects = BCO.objects.filter(prefix__in=user_prefixes, state="DRAFT") # Assume all the values are supposed to be returned. # Source: https://stackoverflow.com/a/51733590 return_values = [ - 'contents', - 'last_update', - 'object_class', - 'object_id', - 'owner_group', - 'owner_user', - 'prefix', - 'schema', - 'state' + "contents", + "last_update", + "object_class", + "object_id", + "owner_group", + "owner_user", + "prefix", + "schema", + "state", ] # If there are any valid keys in the request, @@ -145,14 +149,14 @@ def POST_api_objects_drafts_token(rqst, internal=False): # Redundant logic here since the schema check # would catch this... - if 'fields' in rqst.data['POST_api_objects_drafts_token']: + if "fields" in rqst.data["POST_api_objects_drafts_token"]: # Take the fields and find their intersection with # the available fields. # Source: https://stackoverflow.com/a/3697438 common_fields = list( - set(rqst.data['POST_api_objects_drafts_token']['fields']) & - set(return_values) + set(rqst.data["POST_api_objects_drafts_token"]["fields"]) + & set(return_values) ) if len(common_fields) > 0: @@ -161,13 +165,15 @@ def POST_api_objects_drafts_token(rqst, internal=False): # Return based on whether or not we're using an internal # call. if not internal: - print(" Not Internal, user response: {}"\ - .format(user_objects.intersection(prefix_objects).\ - values(*return_values))) + print( + " Not Internal, user response: {}".format( + user_objects.intersection(prefix_objects).values(*return_values) + ) + ) # Get the user's DRAFT objects. return Response( data=user_objects.intersection(prefix_objects).values(*return_values), - status=status.HTTP_200_OK + status=status.HTTP_200_OK, ) elif internal: @@ -178,47 +184,55 @@ def POST_api_objects_drafts_token(rqst, internal=False): # add in the published objects. # TODO: This needs to only return the most recent PUBLISHED objects not all of the versions - published = BCO.objects.filter(state='PUBLISHED').values() + published = BCO.objects.filter(state="PUBLISHED").values() # unique_published = [] unique_published = set() # E.g. # published[0]["contents"]["object_id"] = 'http://127.0.0.1:8000/BCO_000010/1.0' - bcos = { } + bcos = {} for pub in published: # TODO: We should move this out of a try except and try to handle various situations, # this is currently assuming that the format is # http://URL:PORT/BCO ACCESSION/BCO VERSION - this may not always be true try: - bco_url, bco_id_accession, bco_id_version = pub['object_id'].rsplit("/", 2) - bco_id_name = bco_url + '/' + bco_id_accession + bco_url, bco_id_accession, bco_id_version = pub["object_id"].rsplit( + "/", 2 + ) + bco_id_name = bco_url + "/" + bco_id_accession except Exception as error: - print("Biocompute Name, Version, and URL not formatted as expected: {}".format(error)) + print( + "Biocompute Name, Version, and URL not formatted as expected: {}".format( + error + ) + ) return Response(status=status.HTTP_400_BAD_REQUEST) if bco_id_name in bcos: # Other version of this BCO object exists - current_version = bcos[bco_id_name]['bco_version'] + current_version = bcos[bco_id_name]["bco_version"] # if semver.compare(bco_id_version, current_version, key=coerce): # # New one is newer version, set: if float(current_version) < float(bco_id_version): bcos[bco_id_name] = { - 'bco_name' : bco_id_name, - 'bco_version': current_version, - 'bco_object' : pub + "bco_name": bco_id_name, + "bco_version": current_version, + "bco_object": pub, } else: pass else: # Not in dictionary yet bcos[bco_id_name] = { - 'bco_name' : bco_id_name, - 'bco_version': bco_id_version, - 'bco_object' : pub + "bco_name": bco_id_name, + "bco_version": bco_id_version, + "bco_object": pub, } for key, value in bcos.items(): - unique_published.add(value['bco_object']['id']) + unique_published.add(value["bco_object"]["id"]) unique_published = bco.objects.filter(id__in=unique_published) - result_list = chain(unique_published.values(*return_values), prefix_objects.values(*return_values)) + result_list = chain( + unique_published.values(*return_values), + prefix_objects.values(*return_values), + ) return Response(data=result_list, status=status.HTTP_200_OK) - diff --git a/bco_api/api/scripts/method_specific/POST_api_objects_publish.py b/bco_api/api/scripts/method_specific/POST_api_objects_publish.py index 5be622b5..ca783eb6 100755 --- a/bco_api/api/scripts/method_specific/POST_api_objects_publish.py +++ b/bco_api/api/scripts/method_specific/POST_api_objects_publish.py @@ -1,211 +1,172 @@ #!/usr/bin/env python3 -"""publish +"""Bulk Publish -------------------- Take the bulk request and publish objects directly. """ -from ...models import BCO -# For getting objects out of the database. -from ..utilities import DbUtils - -# User information -from ..utilities import UserUtils - -# For the owner group -from django.contrib.auth.models import Group - -# Permissions for objects -from guardian.shortcuts import get_perms - -# Responses +from api.models import BCO +from api.model.prefix import prefix_table, Prefix +from api.scripts.utilities.DbUtils import DbUtils as db_utils +from api.scripts.utilities.UserUtils import UserUtils as user_utils +from api.scripts.utilities.JsonUtils import parse_bco +from django.conf import settings +from django.utils import timezone from rest_framework import status from rest_framework.response import Response -def POST_api_objects_publish(incoming): + +def post_api_objects_publish(incoming): """ Take the bulk request and publish objects directly. """ - # Instantiate any necessary imports. - db = DbUtils.DbUtils() - uu = UserUtils.UserUtils() - - # The token has already been validated, - # so the user is guaranteed to exist. - - # Get the User object. - user = uu.user_from_request( - rq = incoming - ) - - # Get the user's prefix permissions. - px_perms = uu.prefix_perms_for_user( - flatten = True, - user_object = user - ) - - # Define the bulk request. - bulk_request = incoming.data['POST_api_objects_publish'] - - # Construct an array to return the objects. + root_uri = settings.OBJECT_NAMING["root_uri"] + user = user_utils().user_from_request(request=incoming) + px_perms = user_utils().prefix_perms_for_user(flatten=True, user_object=user) + bulk_request = incoming.data["POST_api_objects_publish"] returning = [] - - # Since bulk_request is an array, go over each - # item in the array. + any_failed = False + results = {} for publish_object in bulk_request: - - # Attempting to publish from a draft ID. - - # Get the prefix *that we are publishing under* - # (this prefix is not necessarily the same one - # as the draft was created under). - standardized = publish_object['prefix'] - - # Does the requestor have publish permissions for - # the *prefix*? - if 'publish_' + standardized in px_perms: - - # The requestor has publish permissions for - # the prefix. If no object ID is provided, - # proceed straight to the publish attempt. - - # Attempt to publish, but first, verify - # that the object is IEEE-compliant. - # schema_check = ju.check_object_against_schema( - # object_pass = objected, - # schema_pass = - # ) - # TODO: fix the schema check... - schema_check = None - - if schema_check is None: - - # If an object_id is given with the request, - # it means that we are trying to publish - # a new version of an existing published object (on this server). - - # Go straight to the publish attempt if there is no - # object_id key given with the request. - if 'object_id' not in publish_object: - - # Object is compliant, so kick it off to - # be published. - - # For publishing, the owner group and the - # owner user are "the same". That is, the - # owner group is the one derived from the owner user. - published = db.publish( - og = Group.objects.get(name = user.username).name, - ou = user.username, - prfx = standardized, - publishable = publish_object["contents"], - publishable_id = 'new' + results = parse_bco(publish_object["contents"], results) + object_key = publish_object["contents"]["object_id"] + if results[object_key]["number_of_errors"] > 0: + returning.append( + db_utils().messages(parameters={"errors": results})[ + "400_non_publishable_object" + ] + ) + any_failed = True + continue + + prefix = publish_object["prefix"].upper() + if Prefix.objects.filter(prefix=prefix).exists(): + prefix_counter = prefix_table.objects.get(prefix=prefix) + + if "publish_" + prefix in px_perms: + if "object_id" in publish_object: + accession = publish_object["object_id"].split("/")[-2] + object_num = int( + publish_object["object_id"].split("_")[1].split("/")[0] ) - - - # Did the publishing go well? - if isinstance(published, dict): - - # Update the request status. + constructed_obj_id = ( + root_uri + + "/" + + accession + + "/" + + publish_object["contents"]["provenance_domain"]["version"] + ) + if BCO.objects.filter(object_id__contains=accession).exists(): returning.append( - db.messages( - parameters = { - 'published_id': published['published_id'] + db_utils().messages(parameters={"object_id": accession})[ + "409_object_conflict" + ] + ) + any_failed = True + continue + if publish_object["object_id"] != constructed_obj_id: + returning.append( + db_utils().messages( + parameters={ + "object_id": publish_object["object_id"], + "constructed_obj_id": constructed_obj_id, } - )['200_OK_object_publish'] + )["409_object_id_conflict"] ) - + any_failed = True + continue + new_object = {} + new_object["contents"] = publish_object["contents"] + new_object["object_id"] = constructed_obj_id + new_object["contents"]["object_id"] = constructed_obj_id + new_object["owner_group"] = publish_object["owner_group"] + new_object["owner_user"] = user.username + new_object["prefix"] = prefix + new_object["last_update"] = timezone.now() + new_object["schema"] = "IEEE" + new_object["state"] = "PUBLISHED" + + objects_written = db_utils().write_object( + p_app_label="api", + p_model_name="BCO", + p_fields=[ + "contents", + "last_update", + "object_id", + "owner_group", + "owner_user", + "prefix", + "schema", + "state", + ], + p_data=new_object, + ) + if prefix_counter.n_objects < object_num: + prefix_counter.n_objects = object_num + 1 + prefix_counter.save() + returning.append( + db_utils().messages( + parameters={"object_id": constructed_obj_id} + )["201_create"] + ) else: - - # When an object ID is provided, the requestor must - # have publish permissions for the published object. - objected = BCO.objects.get( - object_id = publish_object['object_id'] + object_num = format(prefix_counter.n_objects, "06d") + version = publish_object["contents"]["provenance_domain"]["version"] + constructed_obj_id = ( + root_uri + "/" + prefix + "_" + object_num + "/" + version ) - # We don't care where the publish permission comes from, - # be it a User permission or a Group permission. - all_permissions = get_perms(user,objected) - # Published object owner automatically has publish - # permissions, but we need to check for the publish - # permission otherwise. - if user.username == objected.owner_user.username or 'publish_new_version_' + publish_object['object_id'] in all_permissions: - - # We need to check that the provided object ID - # complies with the versioning rules. - versioned = db.check_version_rules( - published_id = publish_object['object_id'] - ) - - # If we get a dictionary back, that means we have - # a usable object ID. Otherwise, something went wrong - # with trying to use the provided object ID. - if isinstance(versioned, dict): - - # We now have the published_id to write with. - - # For publishing, the owner group and the - # owner user are "the same". That is, the - # owner group is the one derived from the owner user. - published = db.publish( - og = Group.objects.get(name = user.username).name, - ou = user.username, - prfx = standardized, - publishable = publish_object['contents'], - publishable_id = versioned - ) - - # Did the publishing go well? - if isinstance(published, dict): - - # Update the request status. - returning.append( - db.messages( - parameters = { - 'published_id': versioned - } - )['200_OK_object_publish'] - ) - else: - # Either the object wasn't found - # or an invalid version number was provided. - if versioned == 'bad_version_number': - returning.append( - db.messages( - parameters = {} - )['400_bad_version_number'] - ) - elif versioned == 'non_root_id': - returning.append( - db.messages( - parameters = {} - )['400_non_root_id'] - ) - - else: + new_object = {} + new_object["contents"] = publish_object["contents"] + new_object["object_id"] = constructed_obj_id + new_object["contents"]["object_id"] = constructed_obj_id + new_object["owner_group"] = publish_object["owner_group"] + new_object["owner_user"] = user.username + new_object["prefix"] = prefix + new_object["last_update"] = timezone.now() + new_object["schema"] = "IEEE" + new_object["state"] = "PUBLISHED" + + objects_written = db_utils().write_object( + p_app_label="api", + p_model_name="BCO", + p_fields=[ + "contents", + "last_update", + "object_id", + "owner_group", + "owner_user", + "prefix", + "schema", + "state", + ], + p_data=new_object, + ) - # Insufficient permissions. - returning.append(db.messages(parameters = {} - )['403_insufficient_permissions'] - ) + prefix_counter.n_objects = prefix_counter.n_objects + 1 + prefix_counter.save() + returning.append( + db_utils().messages( + parameters={"object_id": constructed_obj_id} + )["201_create"] + ) else: - # Object provided is not schema-compliant. - returning.append(db.messages( - parameters = {'errors': schema_check} - )['400_non_publishable_object'] + returning.append( + db_utils().messages(parameters={"prefix": prefix})[ + "401_prefix_unauthorized" + ] ) + any_failed = True + else: - # Update the request status. - returning.append(db.messages( - parameters = {'prefix': standardized} - )['401_prefix_unauthorized'] + returning.append( + db_utils().messages(parameters={"prefix": prefix})["404_missing_prefix"] ) + any_failed = True + + if any_failed: + return Response(status=status.HTTP_207_MULTI_STATUS, data=returning) - # As this view is for a bulk operation, status 200 - # means that the request was successfully processed, - # but NOT necessarily each item in the request. - # For example, a table may not have been found for the first - # requested draft. - return Response(status = status.HTTP_200_OK,data = returning) + return Response(status=status.HTTP_200_OK, data=returning) diff --git a/bco_api/api/scripts/method_specific/POST_api_objects_published.py b/bco_api/api/scripts/method_specific/POST_api_objects_published.py index 6ae5bb16..3e9e8f30 100644 --- a/bco_api/api/scripts/method_specific/POST_api_objects_published.py +++ b/bco_api/api/scripts/method_specific/POST_api_objects_published.py @@ -22,7 +22,7 @@ # TODO: This is repeated code, should consolidate BASEVERSION = re.compile( - r"""[vV]? + r"""[vV]? (?P0|[1-9]\d*) (\. (?P0|[1-9]\d*) @@ -31,8 +31,8 @@ )? )? """, - re.VERBOSE, - ) + re.VERBOSE, +) def coerce(version: str) -> Tuple[Version, Optional[str]]: @@ -53,10 +53,10 @@ def coerce(version: str) -> Tuple[Version, Optional[str]]: return (None, version) ver = { - key: 0 if value is None else value for key, value in match.groupdict().items() - } + key: 0 if value is None else value for key, value in match.groupdict().items() + } ver = Version(**ver) - rest = match.string[match.end():] # noqa:E203 + rest = match.string[match.end() :] # noqa:E203 return ver, rest @@ -65,20 +65,26 @@ def POST_api_objects_published(): Get All published objects (publicly available) """ - published = BCO.objects.filter(state='PUBLISHED').values() + published = BCO.objects.filter(state="PUBLISHED").values() unique_published = [] # E.g. # published[0]["contents"]["object_id"] = 'http://127.0.0.1:8000/BCO_000010/1.0' - bcos = { } + bcos = {} for p in published: # TODO: We should move this out of a try except and try to handle various situations, this is currently # assuming that the format is http://URL:PORT/BCO NAME/BCO VERSION - this may not always be true try: - bco_url, bco_id_name, bco_id_version = p["contents"]["object_id"].rsplit("/", 2) + bco_url, bco_id_name, bco_id_version = p["contents"]["object_id"].rsplit( + "/", 2 + ) except Exception as e: - print("Biocompute Name, Version, and URL not formatted as expected: {}".format(e)) + print( + "Biocompute Name, Version, and URL not formatted as expected: {}".format( + e + ) + ) return Response(status=status.HTTP_400_BAD_REQUEST) if bco_url in bcos: @@ -88,10 +94,10 @@ def POST_api_objects_published(): if semver.compare(bco_id_version, current_version, key=coerce): # New one is newer version, set: bcos[bco_url] = { - "bco_name" : bco_id_name, - "bco_version": bco_id_version, - "bco_object" : p - } + "bco_name": bco_id_name, + "bco_version": bco_id_version, + "bco_object": p, + } else: # Do nothing @@ -99,14 +105,11 @@ def POST_api_objects_published(): else: # Not in dictionary yet bcos[bco_url] = { - "bco_name" : bco_id_name, - "bco_version": bco_id_version, - "bco_object" : p - } + "bco_name": bco_id_name, + "bco_version": bco_id_version, + "bco_object": p, + } for key, value in bcos.items(): unique_published.append(value["bco_object"]) - return Response( - data=unique_published, - status=status.HTTP_200_OK - ) + return Response(data=unique_published, status=status.HTTP_200_OK) diff --git a/bco_api/api/scripts/method_specific/POST_api_objects_search.py b/bco_api/api/scripts/method_specific/POST_api_objects_search.py index c92ff8b3..6b3cfe27 100755 --- a/bco_api/api/scripts/method_specific/POST_api_objects_search.py +++ b/bco_api/api/scripts/method_specific/POST_api_objects_search.py @@ -4,6 +4,7 @@ """ from itertools import chain + from api.models import BCO from api.model.prefix import Prefix from api.scripts.utilities import UserUtils @@ -11,6 +12,7 @@ from rest_framework import status from rest_framework.response import Response + def post_api_objects_search(request): """Search for BCOs @@ -26,71 +28,93 @@ def post_api_objects_search(request): """ return_values = [ - 'contents', - 'last_update', - 'object_class', - 'object_id', - 'owner_group', - 'owner_user', - 'prefix', - 'schema', - 'state' + "contents", + "last_update", + "object_class", + "object_id", + "owner_group", + "owner_user", + "prefix", + "schema", + "state", ] - query = request.data['POST_api_objects_search'][0] - search_type = query['type'] + query = request.data["POST_api_objects_search"][0] + search_type = query["type"] try: - search_value = query['search'] + search_value = query["search"] except KeyError: - search_value = '' + search_value = "" user_utils = UserUtils.UserUtils() user_info = request._user - - if search_type == 'bco_id': - if user_info.username == 'anon': - bco_list = BCO.objects.filter(object_id__icontains=search_value, state='PUBLISHED') - result_list = chain(bco_list.values(*return_values)) + user_prefixes = user_utils.prefixes_for_user(user_object=user_info) + + prefix_perms = user_utils.prefix_perms_for_user( + flatten=True, user_object=user_info, specific_permission=["add"] + ) + + if search_type == "bco_id": + publish_list = BCO.objects.filter( + object_id__icontains=search_value, state="PUBLISHED" + ) + if user_info.username == "anon": + result_list = chain(publish_list.values(*return_values)) else: - user_objects = get_objects_for_user(user=user_info, perms=[], klass=BCO, any_perm=True) - bco_list = BCO.objects.filter(object_id__icontains=search_value).exclude(state='DELETE') - for i in bco_list: - if i not in user_objects: - bco_list.exclude(pk=i.id) + user_objects = get_objects_for_user( + user=user_info, perms=[], klass=BCO, any_perm=True + ) + draft_list = BCO.objects.filter( + object_id__icontains=search_value, + prefix__in=user_prefixes, + state="DRAFT", + ).exclude(state="DELETE") + bco_list = draft_list.union(publish_list) result_list = chain(bco_list.values(*return_values)) - if search_type == 'prefix': + if search_type == "prefix": search_value = search_value.upper() - user_prefixes = user_utils.prefixes_for_user(user_object=user_info) try: prefix = Prefix.objects.get(prefix=search_value).prefix except Prefix.DoesNotExist: - return Response(status=status.HTTP_404_NOT_FOUND, data={ - 'request_status': 'FAILURE', - 'status_code' : '404', - 'message' : 'The prefix was not found on the server.' - }) + return Response( + status=status.HTTP_404_NOT_FOUND, + data={ + "request_status": "FAILURE", + "status_code": "404", + "message": "That prefix was not found on this server.", + }, + ) if prefix in user_prefixes: - bco_list = BCO.objects.filter(prefix=prefix).values().exclude(state='DELETE') + bco_list = ( + BCO.objects.filter(prefix=prefix).values().exclude(state="DELETE") + ) result_list = chain(bco_list.values(*return_values)) else: - return Response(status=status.HTTP_403_FORBIDDEN, data={ - 'request_status': 'FAILURE', - 'status_code' : '403', - 'message' : 'The token provided does not have sufficient'\ - ' permissions for the requested object.' - } + return Response( + status=status.HTTP_403_FORBIDDEN, + data={ + "request_status": "FAILURE", + "status_code": "403", + "message": "The token provided does not have sufficient" + " permissions for the requested prefix.", + }, ) - if search_type == 'mine': - if user_info.username == 'anon': - result_list = chain(BCO.objects.filter(state='PUBLISHED').values(*return_values)) + if search_type == "mine": + if user_info.username == "anon": + result_list = chain( + BCO.objects.filter(state="PUBLISHED").values(*return_values) + ) else: - result_list = chain(BCO.objects.filter( - owner_user=user_info).exclude(state='DELETE' - ).values(*return_values)) + result_list = chain( + BCO.objects.filter(owner_user=user_info) + .exclude(state="DELETE") + .values(*return_values) + ) + # print(len(list(result_list))) return Response(status=status.HTTP_200_OK, data=result_list) diff --git a/bco_api/api/scripts/method_specific/POST_api_objects_token.py b/bco_api/api/scripts/method_specific/POST_api_objects_token.py index ac3714ac..a0a8a33e 100755 --- a/bco_api/api/scripts/method_specific/POST_api_objects_token.py +++ b/bco_api/api/scripts/method_specific/POST_api_objects_token.py @@ -19,10 +19,7 @@ def POST_api_objects_token(rqst): flag as True so that we can get published objects. """ - rqst.data['POST_api_objects_drafts_token'] = rqst.data.pop('POST_api_objects_token') + rqst.data["POST_api_objects_drafts_token"] = rqst.data.pop("POST_api_objects_token") # Get the user's objects. - return POST_api_objects_drafts_token( - rqst=rqst, - internal=True - ) + return POST_api_objects_drafts_token(rqst=rqst, internal=True) diff --git a/bco_api/api/scripts/method_specific/POST_validate_payload_against_schema.py b/bco_api/api/scripts/method_specific/POST_validate_payload_against_schema.py index c6508dac..4d44f9e5 100755 --- a/bco_api/api/scripts/method_specific/POST_validate_payload_against_schema.py +++ b/bco_api/api/scripts/method_specific/POST_validate_payload_against_schema.py @@ -1,47 +1,43 @@ -import json -from ..utilities import JsonUtils - -def POST_validate_payload_against_schema( - bulk_request -): - - # Take the bulk request and determine which - # kind of schema we're looking for. - - # Since bulk_request is an array, go over each - # item in the array, stopping if we have a failure. - for validation_object in bulk_request: - - # First, get the object to be validated. - to_be_validated = validation_object['payload'] - - # Is the schema on the server or was it provided? - if 'schema_server' in validation_object: - - # Load the schema, then pass it along for validation. - # Check to make sure schema file exists... - with open('api/validation_definitions/' + validation_object['schema_server'], 'r') as f: - schema_helper = json.load(f) - - return( - { - 'request_status': 'success', - 'contents': JsonUtils.JsonUtils().check_object_against_schema( - object_pass = to_be_validated, - schema_pass = schema_helper - ) - } - ) - - elif 'schema_own' in bulk_request: - - # Use the provided schema. - return( - { - 'request_status': 'success', - 'contents': JsonUtils.JsonUtils().check_object_against_schema( - object_pass = to_be_validated, - schema_pass = validation_object['schema_own'] - ) - } - ) \ No newline at end of file +#!/usr/bin/env python3 +"""Bulk Validate BioCompute Objects +""" + +from rest_framework import status +from rest_framework.response import Response +from api.scripts.utilities.JsonUtils import parse_bco + + +def post_validate_bco(request): + """Bulk BCO Validation + + Take the bulk request and validate each BCO. + + Parameters + ---------- + request : rest_framework.request.Request + The bulk request object. + + Returns + ------- + Response : dict + A rest framework response object. The response data is a list of + dictionaries, each of which corisponding to one of the BCOs submitted + for validation. + """ + + bco_list = request.data["POST_validate_bco"] + results = {} + any_failed = False + for bco in bco_list: + results = parse_bco(bco, results) + identifier = bco["object_id"] + + if results[identifier]["number_of_errors"] == 0: + results[identifier]["error_detail"] = ["BCO Valid"] + else: + any_failed = True + + if any_failed is True: + return Response(status=status.HTTP_207_MULTI_STATUS, data=results) + + return Response(status=status.HTTP_200_OK, data=results) diff --git a/bco_api/api/scripts/utilities/DbUtils.py b/bco_api/api/scripts/utilities/DbUtils.py index 2e594b4c..041386fe 100755 --- a/bco_api/api/scripts/utilities/DbUtils.py +++ b/bco_api/api/scripts/utilities/DbUtils.py @@ -1,52 +1,30 @@ -# Utilities -# from api.model.prefix import prefix_table +#!/usr/bin/env python3 +"""DB Utilities +Functions for operations with DB +""" -# Checking versioning rules -from api.models import BCO - -# For writing objects to the database. -from api.serializers import getGenericSerializer - -# (OPTIONAL) For sending user information to userdb. +import random +import re +import uuid import json +import datetime import requests +from api.models import BCO +from api.serializers import getGenericSerializer from api.scripts.utilities import UserUtils - -# For checking datetimes -import datetime - -# For getting the model. from django.apps import apps - -# For getting object naming information. from django.conf import settings - -# For getting the API models. -from django.contrib.contenttypes.models import ContentType - -# For checking for and creating users. from django.contrib.auth.models import Group, User - -# For recording the creation time. +from django.contrib.contenttypes.models import ContentType from django.utils import timezone -# For user IDs. -import random - -# Regular expressions -import re - -# For user passwords. -import uuid - - class DbUtils: """Class Description - ----------------- - These methods are for interacting with our sqlite database. - Checking whether or not an object exists. + ----------------- + These methods are for interacting with our sqlite database. + Checking whether or not an object exists. """ def check_object_id_exists(self, p_app_label, p_model_name, p_object_id): @@ -55,12 +33,11 @@ def check_object_id_exists(self, p_app_label, p_model_name, p_object_id): Source: https://docs.djangoproject.com/en/3.1/ref/models/querysets/#exists """ - if apps.get_model( - app_label=p_app_label, - model_name=p_model_name - ).objects.filter( - object_id=p_object_id - ).exists(): + if ( + apps.get_model(app_label=p_app_label, model_name=p_model_name) + .objects.filter(object_id=p_object_id) + .exists() + ): return None else: return 1 @@ -68,16 +45,15 @@ def check_object_id_exists(self, p_app_label, p_model_name, p_object_id): # Checking whether or not a user exists. def check_user_exists(self, p_app_label, p_model_name, p_email): """Simple existence check. - Source: https://stackoverflow.com/a/9089028 - Source: https://docs.djangoproject.com/en/3.1/ref/models/querysets/#exists + Source: https://stackoverflow.com/a/9089028 + Source: https://docs.djangoproject.com/en/3.1/ref/models/querysets/#exists """ - if apps.get_model( - app_label=p_app_label, - model_name=p_model_name - ).objects.filter( - email=p_email - ).exists(): + if ( + apps.get_model(app_label=p_app_label, model_name=p_model_name) + .objects.filter(email=p_email) + .exists() + ): return 1 @@ -89,12 +65,12 @@ def check_user_exists(self, p_app_label, p_model_name, p_email): def check_version_rules(self, published_id): """BCO Version Check Potentially publishing a new version - of a published object, but we have to check to + of a published object, but we have to check to see if the provided URI exists in the publishing table. We can take the exact version of the object ID OR only the root version. For example, - 'http://hostname/some/other/paths/BCO_5' and + 'http://hostname/some/other/paths/BCO_5' and 'http://hostname/some/other/paths/BCO_5/3.4' would invoke the same logic here, assuming that version 3.4 of BCO_5 is the latest version. """ @@ -102,24 +78,24 @@ def check_version_rules(self, published_id): # Does the provided object ID exist? if BCO.objects.filter(object_id=published_id).exists(): - split_up = published_id.split('/') + split_up = published_id.split("/") # Get the version. version = split_up[-1:][0] - if version == 'DRAFT': - split_up[len(split_up) - 1] = '1.0' - return {'published_id': '/'.join(split_up)} + if version == "DRAFT": + split_up[len(split_up) - 1] = "1.0" + return {"published_id": "/".join(split_up)} else: # Increment the minor version. - incremented = version.split('.') + incremented = version.split(".") incremented[1] = int(incremented[1]) + 1 - incremented = incremented[0] + '.' + str(incremented[1]) + incremented = incremented[0] + "." + str(incremented[1]) # Create the object ID. split_up[len(split_up) - 1] = incremented # Kick back the minor-incremented object ID. - return {'published_id': '/'.join(split_up)} + return {"published_id": "/".join(split_up)} else: @@ -138,28 +114,20 @@ def check_version_rules(self, published_id): # information... # Split up the URI into the root ID and the version. - root_uri = '' - version = '' + root_uri = "" + version = "" - if re.match( - r"(.*?)/[A-Z]+_(\d+)$", - published_id - ): + if re.match(r"(.*?)/[A-Z]+_(\d+)$", published_id): # Only the root ID was passed. root_uri = published_id - elif re.match( - r"(.*?)/[A-Z]+_(\d+)/(\d+)\.(\d+)$", - published_id - ): + elif re.match(r"(.*?)/[A-Z]+_(\d+)/(\d+)\.(\d+)$", published_id): # The root ID and the version were passed. - split_up = published_id.split('/') + split_up = published_id.split("/") - root_uri = '/'.join( - split_up[:-1] - ) + root_uri = "/".join(split_up[:-1]) version = split_up[-1:] @@ -171,14 +139,10 @@ def check_version_rules(self, published_id): # http://127.0.0.1:8000/BCO_5 if we did not have the trailing # slash). all_versions = list( - BCO.objects.filter( - object_id__regex=rf'{root_uri}/', - state='PUBLISHED' - ).values_list( - 'object_id', - flat=True - ) - ) + BCO.objects.filter( + object_id__regex=rf"{root_uri}/", state="PUBLISHED" + ).values_list("object_id", flat=True) + ) # Get the latest version for this object if we have any. if len(all_versions) > 0: @@ -191,13 +155,11 @@ def check_version_rules(self, published_id): latest_major = 0 latest_minor = 0 - latest_version = [ - i.split('/')[-1:][0] for i in all_versions - ] + latest_version = [i.split("/")[-1:][0] for i in all_versions] for i in latest_version: - major_minor_split = i.split('.') + major_minor_split = i.split(".") if int(major_minor_split[0]) >= latest_major: if int(major_minor_split[1]) >= latest_minor: @@ -209,20 +171,30 @@ def check_version_rules(self, published_id): failed_version = False # If the root ID and the version were passed, check - # to see if the version given is greater than that which would + # to see if the version given is greater than that which would # be generated automatically. - if version != '': + if version != "": # We already have the automatically generated version # number. Now we just need to compare it with the # number that was provided. - if int(version[0].split('.')[0]) > latest_major & int(version[0].split('.')[1]) > latest_minor: + if ( + int(version[0].split(".")[0]) + > latest_major & int(version[0].split(".")[1]) + > latest_minor + ): - latest_major = int(version[0].split('.')[0]) - latest_minor = int(version[0].split('.')[1]) + latest_major = int(version[0].split(".")[0]) + latest_minor = int(version[0].split(".")[1]) # Write with the version provided. - published_id = published_id + '/' + str(latest_major) + '.' + str(latest_minor) + published_id = ( + published_id + + "/" + + str(latest_major) + + "." + + str(latest_minor) + ) else: @@ -235,20 +207,24 @@ def check_version_rules(self, published_id): # version in the database, then increment the version. # Write with the minor version incremented. - published_id = published_id + '/' + str(latest_major) + '.' + str(latest_minor + 1) + published_id = ( + published_id + + "/" + + str(latest_major) + + "." + + str(latest_minor + 1) + ) # Did everything go properly with the version provided? if failed_version is False: # The version was valid. - return { - 'published_id': published_id - } + return {"published_id": published_id} else: # Bad request. - return 'bad_version_number' + return "bad_version_number" else: @@ -257,31 +233,22 @@ def check_version_rules(self, published_id): # In this case, we have to return a failure flag # because we cannot create a version for # a root ID that does not exist. - return 'non_root_id' + return "non_root_id" - # Checking whether or not a user exists and their - # temp identifier matches. def check_activation_credentials( - self, - p_app_label, - p_model_name, - p_email, - p_temp_identifier - ) -> bool: + self, p_app_label, p_model_name, p_email, p_temp_identifier + ) -> bool: """ Simple existence check. - + Checking whether or not a user exists and their + temp identifier matches. Source: https://stackoverflow.com/a/9089028 Source: https://docs.djangoproject.com/en/3.1/ref/models/querysets/#exists """ user_info = apps.get_model( - app_label=p_app_label, - model_name=p_model_name - ).objects.filter( - email=p_email, - temp_identifier=p_temp_identifier - ) + app_label=p_app_label, model_name=p_model_name + ).objects.filter(email=p_email, temp_identifier=p_temp_identifier) if user_info.exists(): @@ -289,17 +256,9 @@ def check_activation_credentials( # Source: https://stackoverflow.com/a/7503368 # Take the time and add 2 days. - time_check = list( - user_info.values_list( - 'created', - flat=True - ) - )[0] - - # Source: https://www.kite.com/python/answers/how-to-add-hours-to-the-current-time-in-python - time_check = time_check + datetime.timedelta( - hours=48 - ) + time_check = list(user_info.values_list("created", flat=True))[0] + + time_check = time_check + datetime.timedelta(hours=48) # Crappy timezone problems. # Source: https://stackoverflow.com/a/25662061 @@ -327,49 +286,54 @@ def check_activation_credentials( def check_expiration(self, dt_string): """Split the string first.""" try: - split_up = dt_string.split('-') + split_up = dt_string.split("-") - if len(split_up) == 6: - - try: - - # Convert everything to integers. - split_up = [int(x) for x in split_up] + if len(split_up) == 6: - exp_date = datetime.datetime(split_up[0], split_up[1], split_up[2], split_up[3], split_up[4], split_up[5]) - - if exp_date <= datetime.datetime.now(): + try: - return False - - except TypeError: - - return False + # Convert everything to integers. + split_up = [int(x) for x in split_up] + + exp_date = datetime.datetime( + split_up[0], + split_up[1], + split_up[2], + split_up[3], + split_up[4], + split_up[5], + ) + + if exp_date <= datetime.datetime.now(): - else: - return False - - except AttributeError: - + + except TypeError: + + return False + + else: + return False - def get_api_models( - self - ): + except AttributeError: + + return False - # Get all the ACCESSIBLE models in the API. - # Source: https://stackoverflow.com/a/9407979 + def get_api_models(self): + """Get all the ACCESSIBLE models in the API. + Source: https://stackoverflow.com/a/9407979 + """ api_models = [] # Define any tables to exclude here. - exclude = ['meta', 'new_users'] + exclude = ["meta", "new_users"] for ct in ContentType.objects.all(): m = ct.model_class() - if m.__module__ == 'api.models': + if m.__module__ == "api.models": if m.__name__ not in exclude: api_models.append(m.__name__) @@ -395,7 +359,7 @@ def activate_account(self, p_email): # email_base = p_email.split('@')[0] # user_hash = hashlib.md5(b'{}'.format(email_base)) # new_username = email_base + "_" + user_hash.hexdigest() - new_username = p_email.split('@')[0] + str(random.randrange(1, 100)) + new_username = p_email.split("@")[0] + str(random.randrange(1, 100)) # Does this username exist (not likely)? if User.objects.filter(username=new_username): valid_username = False @@ -413,7 +377,7 @@ def activate_account(self, p_email): user = User.objects.create_user(new_username) - # Setting the password has to be done manually in + # Setting the password has to be done manually in # order to encrypt it. # Source: https://stackoverflow.com/a/39211961 # Source: https://stackoverflow.com/questions/28347200/django-rest-http-400-error-on-getting-token-authentication-view @@ -423,16 +387,19 @@ def activate_account(self, p_email): user.save() # Automatically add the user to the bco_drafter and bco_publisher groups. - user.groups.add(Group.objects.get(name='bco_drafter')) - user.groups.add(Group.objects.get(name='bco_publisher')) + user.groups.add(Group.objects.get(name="bco_drafter")) + user.groups.add(Group.objects.get(name="bco_publisher")) # (OPTIONAL) Make a request to userdb on the portal so that # the user's information can be stored there. # If a token was provided with the initial request, # use it to make the update call to userdb. - token = apps.get_model(app_label='api', model_name='new_users' - ).objects.get(email=p_email).token + token = ( + apps.get_model(app_label="api", model_name="new_users") + .objects.get(email=p_email) + .token + ) if token is not None: # Send the new information to userdb. @@ -441,25 +408,22 @@ def activate_account(self, p_email): # Set the headers. # Source: https://docs.python-requests.org/en/master/user/quickstart/#custom-headers headers = { - 'Authorization': 'JWT ' + token, - 'Content-type' : 'application/json; charset=UTF-8' - } + "Authorization": "JWT " + token, + "Content-type": "application/json; charset=UTF-8", + } # Set the data properly. # Source: https://stackoverflow.com/a/56562567 - r = requests.post(data=json.dumps( - uu.get_user_info(username=new_username),default=str), + r = requests.post( + data=json.dumps(uu.get_user_info(username=new_username), default=str), headers=headers, - url='http://127.0.0.1:8181/users/add_api/' - ) + url="http://127.0.0.1:8181/users/add_api/", + ) # Delete the record in the temporary table. - apps.get_model( - app_label='api', - model_name='new_users' - ).objects.filter( - email=p_email - ).delete() + apps.get_model(app_label="api", model_name="new_users").objects.filter( + email=p_email + ).delete() # Return the username in a list, as this is # easily checked for upstream (as opposed to @@ -470,239 +434,327 @@ def activate_account(self, p_email): # Messages associated with results from sub-requests. def messages(self, parameters, p_content=False): """TODO: abstract all of this up into the top level of the class. - + Define the return messages, if they don't come in defined. """ - definable = ['errors', 'expiration_date', 'group', 'object_id', 'object_perms', 'prefix', 'published_id', 'table', 'username', 'contents', 'users_excluded'] + definable = [ + "errors", + "expiration_date", + "group", + "object_id", + "constructed_obj_id", + "object_perms", + "prefix", + "published_id", + "table", + "username", + "contents", + "users_excluded", + ] for i in definable: if i not in parameters: - parameters[i] = '' + parameters[i] = "" return { - '200_found' : { - 'request_status': 'SUCCESS', - 'status_code' : '200', - 'message' : 'The object with ID \'' + parameters['object_id'] + '\' was found on table \'' + parameters['table'] + '\'.', - 'content' : p_content - }, - '200_OK_group_delete' : { - 'request_status': 'SUCCESS', - 'status_code' : '200', - 'message' : 'The group \'' + parameters['group'] + '\' was deleted.' - }, - '200_OK_group_modify' : { - 'request_status': 'SUCCESS', - 'status_code' : '200', - 'message' : 'The group \'' + parameters['group'] + '\' was succesfully modified.' - }, - '200_OK_object_delete' : { - 'request_status': 'SUCCESS', - 'status_code' : '200', - 'message' : 'The object with ID \'' + parameters['object_id'] + '\' was deleted.' - }, - '200_OK_object_read' : { - 'request_status': 'SUCCESS', - 'status_code' : '200', - 'contents' : parameters['contents'], - 'message' : 'The object with ID \'' + parameters['object_id'] + '\' was found on the server.' - }, - '200_OK' : { - 'request_status': 'SUCCESS', - 'status_code' : '200', - 'message' : 'The prefix \'' + parameters['prefix'] + '\' was deleted.' - }, - '200_OK_object_permissions' : { - 'request_status': 'SUCCESS', - 'status_code' : '200', - 'message' : 'Permissions for the object with ID \'' + parameters['object_id'] + '\' were found on the server.', - 'object_id' : parameters['object_id'], - 'permissions' : parameters['object_perms'] - }, - '200_OK_object_permissions_set' : { - 'request_status': 'SUCCESS', - 'status_code' : '200', - 'message' : 'Permissions for the object with ID \'' + parameters['object_id'] + '\' were set on the server.', - 'object_id' : parameters['object_id'] - }, - '200_OK_object_publish' : { - 'request_status': 'SUCCESS', - 'status_code' : '200', - 'message' : 'Successfully published \'' + parameters['published_id'] + '\' on the server.', - 'published_id' : parameters['published_id'] - }, - '200_OK_object_publish_draft_deleted' : { - 'request_status': 'SUCCESS', - 'status_code' : '200', - 'message' : 'Successfully published \'' + parameters['published_id'] + '\' on the server and the draft was deleted.', - 'published_id' : parameters['published_id'] - }, - '200_OK_object_publish_draft_not_deleted': { - 'request_status': 'SUCCESS', - 'status_code' : '200', - 'message' : 'Successfully published \'' + parameters['published_id'] + '\' on the server and the draft was not deleted.', - 'published_id' : parameters['published_id'] - }, - '200_OK_prefix_delete': { - 'request_status': 'SUCCESS', - 'status_code' : '200', - 'message' : 'Successfully deleted prefix \'' + parameters['prefix'] + '\'.' - }, - '200_OK_prefix_permissions_update': { - 'request_status': 'SUCCESS', - 'status_code' : '200', - 'message' : 'Successfully updated prefix permissions on prefix \'' + parameters['prefix'] + '\'.' - }, - '200_update' : { - 'request_status': 'SUCCESS', - 'status_code' : '200', - 'message' : 'The object with ID \'' + parameters['object_id'] + '\' was updated.' - }, - '201_create' : { - 'request_status': 'SUCCESS', - 'status_code' : '201', - 'message' : 'The object with ID \'' + parameters['object_id'] + '\' was created on the server.', - 'object_id' : parameters['object_id'] - }, - '201_prefix_modify' : { - 'request_status': 'SUCCESS', - 'status_code' : '200', - 'message' : 'The prefix \'' + parameters['prefix'] + '\' was updated.' - }, - '201_group_create' : { - 'request_status': 'SUCCESS', - 'status_code' : '201', - 'message' : 'The group \'' + parameters['group'] + '\' was successfully created.' - }, - '201_group_users_excluded' : { - 'request_status': 'SUCCESS', - 'status_code' : '201', - 'message' : 'The group \'' + parameters['group'] + '\' was successfully created, but the following users were excluded: ' + str(parameters['users_excluded']) - }, - '201_prefix_create' : { - 'request_status': 'SUCCESS', - 'status_code' : '201', - 'message' : 'The prefix \'' + parameters['prefix'] + '\' was successfully created.' - }, - '202_Accepted' : { - 'request_status': 'SUCCESS', - 'status_code' : '202', - 'message' : 'The request you performed has been accepted.' - }, - '204_no_content' : { - 'request_status': 'SUCCESS', - 'status_code' : '204', - 'message' : 'The search you performed returned ZERO results.' - }, - '400_bad_request' : { - 'request_status': 'FAILURE', - 'status_code' : '400', - 'message' : 'The request could not be processed with the parameters provided.' - }, - '400_bad_request_malformed_prefix' : { - 'request_status': 'FAILURE', - 'status_code' : '400', - 'message' : 'The prefix \'' + parameters['prefix'] + '\' does not follow the naming rules for a prefix.' - }, - '400_bad_version_number' : { - 'request_status': 'FAILURE', - 'status_code' : '400', - 'message' : 'The provided version number for this object is not greater than the number that would be generated automatically and therefore the request to publish was denied.' - }, - '400_invalid_expiration_date' : { - 'request_status': 'FAILURE', - 'status_code' : '400', - 'message' : 'The expiration date \'' + parameters['expiration_date'] + '\' is not valid either because it does not match the required format \'YYYY-MM-DD-HH-MM-SS\' or because it falls before the current time.' - }, - '400_non_publishable_object' : { - 'request_status': 'FAILURE', - 'status_code' : '400', - 'message' : 'The object provided was not valid against the schema provided. See key \'errors\' for specifics of the non-compliance.', - 'errors' : parameters['errors'] - }, - '400_non_root_id' : { - 'request_status': 'FAILURE', - 'status_code' : '400', - 'message' : 'The provided object ID does not contain a URI with a valid prefix.' - }, - '400_unspecified_error' : { - 'request_status': 'FAILURE', - 'status_code' : '400', - 'message' : 'An unspecified error occurred.' - }, - '401_prefix_unauthorized' : { - 'request_status': 'FAILURE', - 'status_code' : '401', - 'message' : 'The token provided does not have draft permissions for prefix \'' + parameters['prefix'] + '\'.' - }, - '403_insufficient_permissions' : { - 'request_status': 'FAILURE', - 'status_code' : '403', - 'message' : 'The token provided does not have sufficient permissions for the requested object.' - }, - '403_requestor_is_not_prefix_owner' : { - 'request_status': 'FAILURE', - 'status_code' : '403', - 'message' : 'The token provided is not the owner of the prefix \'' + parameters['prefix'] + '\' and therefore permissions for the prefix cannot be changed in this request.' - }, - '403_invalid_token' : { - 'request_status': 'FAILURE', - 'status_code' : '403', - 'message' : 'The token provided was not able to be used on this object.' - }, - '404_group_not_found' : { - 'request_status': 'FAILURE', - 'status_code' : '404', - 'message' : 'The group \'' + parameters['group'] + '\' was not found on the server.' - }, - '404_missing_bulk_parameters' : { - 'request_status': 'FAILURE', - 'status_code' : '404', - 'message' : 'One or more missing optional parameters are required for this call to have an effect.' - }, - '404_missing_prefix' : { - 'request_status': 'FAILURE', - 'status_code' : '404', - 'message' : 'The prefix \'' + parameters['prefix'] + '\' was not found on the server.' - }, - '404_object_id' : { - 'request_status': 'FAILURE', - 'status_code' : '404', - 'message' : 'The object ID \'' + parameters['object_id'] + '\' was not found on the server.' - }, - '404_table' : { - 'request_status': 'FAILURE', - 'status_code' : '404', - 'message' : 'The table with name \'' + parameters['table'] + '\' was not found on the server.' - }, - '404_user_not_found' : { - 'request_status': 'FAILURE', - 'status_code' : '404', - 'message' : 'The user \'' + parameters['username'] + '\' was not found on the server.' - }, - '409_group_conflict' : { - 'request_status': 'FAILURE', - 'status_code' : '409', - 'message' : 'The provided group \'' + parameters['group'] + '\' has already been created on this server.' - }, - '409_prefix_conflict' : { - 'request_status': 'FAILURE', - 'status_code' : '409', - 'message' : 'The provided prefix \'' + parameters['prefix'] + '\' has already been created on this server.' - }, - '409_object_conflict' : { - 'request_status': 'FAILURE', - 'status_code' : '409', - 'message' : 'The provided object ' + parameters['object_id'] + ' has already been created on this server.' - }, - '418_too_many_deleted' : { - 'request_status': 'FAILURE', - 'status_code' : '418', - 'message' : 'Only one object was expected to be deleted, but multiple were removed.' - }, - } + "200_found": { + "request_status": "SUCCESS", + "status_code": "200", + "message": "The object with ID '" + + parameters["object_id"] + + "' was found on table '" + + parameters["table"] + + "'.", + "content": p_content, + }, + "200_OK_group_delete": { + "request_status": "SUCCESS", + "status_code": "200", + "message": "The group '" + parameters["group"] + "' was deleted.", + }, + "200_OK_group_modify": { + "request_status": "SUCCESS", + "status_code": "200", + "message": "The group '" + + parameters["group"] + + "' was succesfully modified.", + }, + "200_OK_object_delete": { + "request_status": "SUCCESS", + "status_code": "200", + "message": "The object with ID '" + + parameters["object_id"] + + "' was deleted.", + }, + "200_OK_object_read": { + "request_status": "SUCCESS", + "status_code": "200", + "contents": parameters["contents"], + "message": "The object with ID '" + + parameters["object_id"] + + "' was found on the server.", + }, + "200_OK": { + "request_status": "SUCCESS", + "status_code": "200", + "message": "The prefix '" + parameters["prefix"] + "' was deleted.", + }, + "200_OK_object_permissions": { + "request_status": "SUCCESS", + "status_code": "200", + "message": "Permissions for the object with ID '" + + parameters["object_id"] + + "' were found on the server.", + "object_id": parameters["object_id"], + "permissions": parameters["object_perms"], + }, + "200_OK_object_permissions_set": { + "request_status": "SUCCESS", + "status_code": "200", + "message": "Permissions for the object with ID '" + + parameters["object_id"] + + "' were set on the server.", + "object_id": parameters["object_id"], + }, + "200_OK_object_publish": { + "request_status": "SUCCESS", + "status_code": "200", + "message": "Successfully published '" + + parameters["published_id"] + + "' on the server.", + "published_id": parameters["published_id"], + }, + "200_OK_object_publish_draft_deleted": { + "request_status": "SUCCESS", + "status_code": "200", + "message": "Successfully published '" + + parameters["published_id"] + + "' on the server and the draft was deleted.", + "published_id": parameters["published_id"], + }, + "200_OK_object_publish_draft_not_deleted": { + "request_status": "SUCCESS", + "status_code": "200", + "message": "Successfully published '" + + parameters["published_id"] + + "' on the server and the draft was not deleted.", + "published_id": parameters["published_id"], + }, + "200_OK_prefix_delete": { + "request_status": "SUCCESS", + "status_code": "200", + "message": "Successfully deleted prefix '" + + parameters["prefix"] + + "'.", + }, + "200_OK_prefix_permissions_update": { + "request_status": "SUCCESS", + "status_code": "200", + "message": "Successfully updated prefix permissions on prefix '" + + parameters["prefix"] + + "'.", + }, + "200_update": { + "request_status": "SUCCESS", + "status_code": "200", + "message": "The object with ID '" + + parameters["object_id"] + + "' was updated.", + }, + "201_create": { + "request_status": "SUCCESS", + "status_code": "201", + "message": "The object with ID '" + + parameters["object_id"] + + "' was created on the server.", + "object_id": parameters["object_id"], + }, + "201_prefix_modify": { + "request_status": "SUCCESS", + "status_code": "200", + "message": "The prefix '" + parameters["prefix"] + "' was updated.", + }, + "201_group_create": { + "request_status": "SUCCESS", + "status_code": "201", + "message": "The group '" + + parameters["group"] + + "' was successfully created.", + }, + "201_group_users_excluded": { + "request_status": "SUCCESS", + "status_code": "201", + "message": "The group '" + + parameters["group"] + + "' was successfully created, but the following users were excluded: " + + str(parameters["users_excluded"]), + }, + "201_prefix_create": { + "request_status": "SUCCESS", + "status_code": "201", + "message": "The prefix '" + + parameters["prefix"] + + "' was successfully created.", + }, + "202_Accepted": { + "request_status": "SUCCESS", + "status_code": "202", + "message": "The request you performed has been accepted.", + }, + "204_no_content": { + "request_status": "SUCCESS", + "status_code": "204", + "message": "The search you performed returned ZERO results.", + }, + "400_bad_request": { + "request_status": "FAILURE", + "status_code": "400", + "message": "The request could not be processed with the parameters provided.", + }, + "400_bad_request_malformed_prefix": { + "request_status": "FAILURE", + "status_code": "400", + "message": "The prefix '" + + parameters["prefix"] + + "' does not follow the naming rules for a prefix.", + }, + "400_bad_version_number": { + "request_status": "FAILURE", + "status_code": "400", + "message": "The provided version number for this object is not greater than the number that would be generated automatically and therefore the request to publish was denied.", + }, + "400_invalid_expiration_date": { + "request_status": "FAILURE", + "status_code": "400", + "message": "The expiration date '" + + parameters["expiration_date"] + + "' is not valid either because it does not match the required format 'YYYY-MM-DD-HH-MM-SS' or because it falls before the current time.", + }, + "400_non_publishable_object": { + "request_status": "FAILURE", + "status_code": "400", + "message": "The object provided was not valid against the schema provided. See key 'errors' for specifics of the non-compliance.", + "errors": parameters["errors"], + }, + "400_non_root_id": { + "request_status": "FAILURE", + "status_code": "400", + "message": "The provided object ID does not contain a URI with a valid prefix.", + }, + "400_unspecified_error": { + "request_status": "FAILURE", + "status_code": "400", + "message": "An unspecified error occurred.", + }, + "401_prefix_unauthorized": { + "request_status": "FAILURE", + "status_code": "401", + "message": "The token provided does not have draft permissions for this prefix '" + + parameters["prefix"] + + "'.", + }, + "401_prefix_publish_unauthorized": { + "request_status": "FAILURE", + "status_code": "401", + "message": "The token provided does not have publish permissions for this prefix '" + + parameters["prefix"] + + "'.", + }, + "403_insufficient_permissions": { + "request_status": "FAILURE", + "status_code": "403", + "message": "The token provided does not have sufficient permissions for the requested object.", + }, + "403_requestor_is_not_prefix_owner": { + "request_status": "FAILURE", + "status_code": "403", + "message": "The token provided is not the owner of the prefix '" + + parameters["prefix"] + + "' and therefore permissions for the prefix cannot be changed in this request.", + }, + "403_invalid_token": { + "request_status": "FAILURE", + "status_code": "403", + "message": "The token provided was not able to be used on this object.", + }, + "404_group_not_found": { + "request_status": "FAILURE", + "status_code": "404", + "message": "The group '" + + parameters["group"] + + "' was not found on the server.", + }, + "404_missing_bulk_parameters": { + "request_status": "FAILURE", + "status_code": "404", + "message": "One or more missing optional parameters are required for this call to have an effect.", + }, + "404_missing_prefix": { + "request_status": "FAILURE", + "status_code": "404", + "message": "The prefix '" + + parameters["prefix"] + + "' was not found on the server.", + }, + "404_object_id": { + "request_status": "FAILURE", + "status_code": "404", + "message": "The object ID '" + + parameters["object_id"] + + "' was not found on the server.", + }, + "404_table": { + "request_status": "FAILURE", + "status_code": "404", + "message": "The table with name '" + + parameters["table"] + + "' was not found on the server.", + }, + "404_user_not_found": { + "request_status": "FAILURE", + "status_code": "404", + "message": "The user '" + + parameters["username"] + + "' was not found on the server.", + }, + "409_group_conflict": { + "request_status": "FAILURE", + "status_code": "409", + "message": "The provided group '" + + parameters["group"] + + "' has already been created on this server.", + }, + "409_prefix_conflict": { + "request_status": "FAILURE", + "status_code": "409", + "message": "The provided prefix '" + + parameters["prefix"] + + "' has already been created on this server.", + }, + "409_object_conflict": { + "request_status": "FAILURE", + "status_code": "409", + "message": "The provided object " + + parameters["object_id"] + + " has already been created on this server.", + }, + "409_object_id_conflict": { + "request_status": "FAILURE", + "status_code": "409", + "message": "The provided object_id " + + parameters["object_id"] + + " does not match the constructed object_id " + + parameters["constructed_obj_id"] + + ".", + }, + "418_too_many_deleted": { + "request_status": "FAILURE", + "status_code": "418", + "message": "Only one object was expected to be deleted, but multiple were removed.", + }, + } # Publish an object. def publish(self, owner_group, owner_user, prefix, publishable, publishable_id): @@ -728,20 +780,20 @@ def publish(self, owner_group, owner_user, prefix, publishable, publishable_id): # Define a variable to hold all information # about the published object. - published = { } + published = {} # A new published object or an existing one? - if publishable_id == 'new': + if publishable_id == "new": # TODO: put new object ID logic in its own function # like check_version_rules()... # Define a variable which will hold the constructed name. - constructed_name = '' + constructed_name = "" # This section was breaking the production/test Db. The contents of `object_naming_info` - # are modifies somewhere else before here so that this IF/ELSE is not needed and causes - # a break in the code. + # are modifies somewhere else before here so that this IF/ELSE is not needed and causes + # a break in the code. # Create the constructed name based on whether or not # we're on a production server. @@ -754,136 +806,146 @@ def publish(self, owner_group, owner_user, prefix, publishable, publishable_id): # elif settings.PRODUCTION == 'False': - constructed_name = object_naming_info['uri_regex'].replace( - 'root_uri', - object_naming_info['root_uri'] - ) + constructed_name = object_naming_info["uri_regex"].replace( + "root_uri", object_naming_info["root_uri"] + ) - constructed_name = constructed_name.replace( - 'prefix', - prfx - ) + constructed_name = constructed_name.replace("prefix", prefix) # Get rid of the rest of the regex for the name. - prefix_location = constructed_name.index( - prefix - ) - prefix_length = len( - prefix - ) - constructed_name = constructed_name[0:prefix_location + prefix_length] + prefix_location = constructed_name.index(prefix) + prefix_length = len(prefix) + constructed_name = constructed_name[0 : prefix_location + prefix_length] # Get the object number counter from meta information about the prefix. prefix_counter = prefix_table.objects.get(prefix=prefix) # Create the contents field. - published['contents'] = publishable + published["contents"] = publishable # Create a new ID based on the prefix counter. - published['object_id'] = constructed_name + '_' + '{:06d}'.format(prefix_counter.n_objects) + '/1.0' + published["object_id"] = ( + constructed_name + + "_" + + "{:06d}".format(prefix_counter.n_objects) + + "/1.0" + ) # Make sure to create the object ID field in our draft. - published['contents']['object_id'] = published['object_id'] + published["contents"]["object_id"] = published["object_id"] # Django wants a primary key for the Group... - published['owner_group'] = owner_group + published["owner_group"] = owner_group # Django wants a primary key for the User... - published['owner_user'] = owner_user + published["owner_user"] = owner_user # The prefix is passed through. - published['prefix'] = prefix + published["prefix"] = prefix # Schema is hard-coded for now... - published['schema'] = 'IEEE' + published["schema"] = "IEEE" # This is PUBLISHED. - published['state'] = 'PUBLISHED' + published["state"] = "PUBLISHED" # Set the datetime properly. - published['last_update'] = timezone.now() + published["last_update"] = timezone.now() # Publish. self.write_object( - p_app_label='api', - p_model_name='BCO', - p_fields=['contents', 'last_update', 'object_id', 'owner_group', 'owner_user', 'prefix', 'schema', 'state'], - p_data=published - ) + p_app_label="api", + p_model_name="BCO", + p_fields=[ + "contents", + "last_update", + "object_id", + "owner_group", + "owner_user", + "prefix", + "schema", + "state", + ], + p_data=published, + ) # Update the meta information about the prefix. prefix_counter.n_objects = prefix_counter.n_objects + 1 prefix_counter.save() # Successfuly saved the object. - return { - 'published_id': published['object_id'] - } + return {"published_id": published["object_id"]} else: # An object ID was provided, so go straight to publishing. # Create the contents field. - published['contents'] = publishable.contents + published["contents"] = publishable.contents # Set the object ID. - published['object_id'] = publishable_id + published["object_id"] = publishable_id # Make sure to create the object ID field in the BCO. - published['contents']['object_id'] = publishable_id + published["contents"]["object_id"] = publishable_id # Django wants a primary key for the Group... - published['owner_group'] = owner_group + published["owner_group"] = owner_group # Django wants a primary key for the User... - published['owner_user'] = owner_user + published["owner_user"] = owner_user # The prefix is passed through. - published['prefix'] = prefix + published["prefix"] = prefix # Schema is hard-coded for now... - published['schema'] = 'IEEE' + published["schema"] = "IEEE" # Mark the object as published. - published['state'] = 'PUBLISHED' + published["state"] = "PUBLISHED" # Set the datetime properly. - published['last_update'] = timezone.now() + published["last_update"] = timezone.now() # Publish. self.write_object( - p_app_label='api', - p_model_name='BCO', - p_fields=['contents', 'last_update', 'object_id', 'owner_group', 'owner_user', 'prefix', 'schema', 'state'], - p_data=publishable.contents - ) + p_app_label="api", + p_model_name="BCO", + p_fields=[ + "contents", + "last_update", + "object_id", + "owner_group", + "owner_user", + "prefix", + "schema", + "state", + ], + p_data=publishable.contents, + ) # Successfully saved the object. - return { - 'published_id': published['object_id'] - } + return {"published_id": published["object_id"]} # Write (update) either a draft or a published object to the database. def write_object( - self, - p_app_label, - p_model_name, - p_fields, - p_data, - p_update=False, - p_update_field=False - ): - - # Source: https://docs.djangoproject.com/en/3.1/topics/db/queries/#topics-db-queries-update - - # Serialize our data. + self, + p_app_label, + p_model_name, + p_fields, + p_data, + p_update=False, + p_update_field=False, + ): + + """Source: https://docs.djangoproject.com/en/3.1/topics/db/queries/#topics-db-queries-update + + Serialize our data.""" serializer = getGenericSerializer( - incoming_model=apps.get_model( - app_label=p_app_label, - model_name=p_model_name - ), - incoming_fields=p_fields - ) + incoming_model=apps.get_model( + app_label=p_app_label, model_name=p_model_name + ), + incoming_fields=p_fields, + ) serialized = serializer(data=p_data) @@ -899,7 +961,7 @@ def write_object( else: # Update an existing object. # apps.get_model( - # app_label = p_app_label, + # app_label = p_app_label, # model_name = p_model_name # ).objects.filter( # object_id = p_data['object_id'] @@ -907,16 +969,13 @@ def write_object( # contents = p_data['contents'] # ) - objects_modified = apps.get_model( - app_label=p_app_label, - model_name=p_model_name - ).objects.filter( - object_id=p_data['object_id'] - ).update( - contents=p_data['contents'] - ) + objects_modified = ( + apps.get_model(app_label=p_app_label, model_name=p_model_name) + .objects.filter(object_id=p_data["object_id"]) + .update(contents=p_data["contents"]) + ) return objects_modified - def convert_id_form(oi_root): - return oi_root.split("_")[0] + '{:06d}'.format(int(oi_root.split("_")[1])) + def convert_id_form(self, oi_root): + return oi_root.split("_")[0] + "{:06d}".format(int(oi_root.split("_")[1])) diff --git a/bco_api/api/scripts/utilities/FileUtils.py b/bco_api/api/scripts/utilities/FileUtils.py index 009d9bb3..2fbb32a1 100755 --- a/bco_api/api/scripts/utilities/FileUtils.py +++ b/bco_api/api/scripts/utilities/FileUtils.py @@ -9,15 +9,9 @@ # --- MAIN --- # -class FileUtils: - - - def pathalizer( - self, - directory, - pattern - ): +class FileUtils: + def pathalizer(self, directory, pattern): # Description # ----------- @@ -43,25 +37,14 @@ def pathalizer( # ------- # A directory + pattern string. - + # Kick back the string. return os.path.join( - os.path.dirname( - os.path.dirname( - os.path.abspath(__file__) - ) - ), - directory + pattern - ) - - + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + directory + pattern, + ) - def create_files( - self, - payload, - output_directory, - file_extension - ): + def create_files(self, payload, output_directory, file_extension): # Description @@ -94,19 +77,12 @@ def create_files( # Construct the output path for each file and write. for original_filename, contents in payload.items(): with open( - self.pathalizer(output_directory, original_filename + file_extension), mode = 'w' - ) as f: - f.write( - contents - ) - - + self.pathalizer(output_directory, original_filename + file_extension), + mode="w", + ) as f: + f.write(contents) - def find_files( - self, - input_directory, - regex - ): + def find_files(self, input_directory, regex): # Description @@ -135,20 +111,10 @@ def find_files( # Source: https://stackoverflow.com/questions/39293968/python-how-do-i-search-directories-and-find-files-that-match-regex # Source: https://stackoverflow.com/questions/30218802/get-parent-of-current-directory-from-python-script - return glob.glob( - self.pathalizer( - input_directory, - regex - ) - ) - + return glob.glob(self.pathalizer(input_directory, regex)) # Find the entire tree of a folder based on an extension. - def get_folder_tree_by_extension( - self, - search_folder, - search_extension - ): + def get_folder_tree_by_extension(self, search_folder, search_extension): # search_folder: where we're looking. # search_extension: the extension we're looking for. @@ -157,46 +123,26 @@ def get_folder_tree_by_extension( # Set the root directory. root_directory = os.path.join( - os.path.dirname( - os.path.dirname( - os.path.abspath(__file__) - ) - ), - search_folder + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), search_folder ) # Create a dictionary to return. - returning = { - 'root_directory': root_directory, - 'paths': [] - } - - for root, dirs, files in os.walk( - root_directory - ): + returning = {"root_directory": root_directory, "paths": []} + + for root, dirs, files in os.walk(root_directory): for name in files: - returning['paths'].append( - os.path.join(root, name) - ) + returning["paths"].append(os.path.join(root, name)) for name in dirs: - returning['paths'].append( - os.path.join(root, name) - ) - - returning['paths'] = [ - x for x in returning['paths'] if x.find( - search_extension - ) != -1 + returning["paths"].append(os.path.join(root, name)) + + returning["paths"] = [ + x for x in returning["paths"] if x.find(search_extension) != -1 ] return returning - # Find the entire tree of a folder, regardless of extension. - def get_folder_tree( - self, - search_folder - ): + def get_folder_tree(self, search_folder): # search_folder: where we're looking. @@ -204,40 +150,18 @@ def get_folder_tree( # Set the root directory. root_directory = os.path.join( - os.path.dirname( - os.path.dirname( - os.path.abspath(__file__) - ) - ), - search_folder + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), search_folder ) # Create a dictionary to return. - returning = { - 'root_directory': root_directory, - 'paths': [] - } - - for root, dirs, files in os.walk( - root_directory - ): + returning = {"root_directory": root_directory, "paths": []} + + for root, dirs, files in os.walk(root_directory): for name in files: - returning['paths'].append( - os.path.join( - root, - name - ) - ) + returning["paths"].append(os.path.join(root, name)) for name in dirs: - returning['paths'].append( - os.path.join( - root, - name - ) - ) - - returning['paths'] = [ - x for x in returning['paths'] if 1 - ] + returning["paths"].append(os.path.join(root, name)) - return returning \ No newline at end of file + returning["paths"] = [x for x in returning["paths"] if 1] + + return returning diff --git a/bco_api/api/scripts/utilities/JsonUtils.py b/bco_api/api/scripts/utilities/JsonUtils.py index 4a8c1899..a8dad1d4 100755 --- a/bco_api/api/scripts/utilities/JsonUtils.py +++ b/bco_api/api/scripts/utilities/JsonUtils.py @@ -1,44 +1,170 @@ -# For JSON parsing and schema validation. +#!/usr/bin/env python3 +"""JSON Utils + +For JSON parsing and schema validation. +""" + +import os +import sys import json import jsonref import jsonschema +from simplejson.errors import JSONDecodeError +from requests.exceptions import ConnectionError as ErrorConnecting + + +def get_schema(schema_uri): + """Retrieve JSON Schema + + Parameters + ---------- + schema_uri : str + A URI that is used to pull the JSON schema for validation. + + Returns + ------- + schema : dict + A dictionary of the JSON schema definition, or detail on the error loading the schema. + """ + + try: + schema = jsonref.load_uri(schema_uri) + return schema + + except JSONDecodeError: + return {schema_uri: ["Failed to load extension schema. JSON Decode Error."]} + + except TypeError: + return {schema_uri: ["Failed to load extension schema. Invalid format."]} + + except ErrorConnecting: + return {schema_uri: ["Failed to load extension schema. Connection Error."]} + + +def validate(schema, json_object, results): + """BCO/extension Validator + + Parameters + ---------- + schema : dict + A dictionary of the JSON schema definition. + json_object : dict + A dictionary of the BCO/extension JSON for validation. + results : dict + A dictionary that is used to collect the validation results. + + Returns + ------- + results : dict + A dictionary that is used to collect the validation results. + """ + + if "object_id" in json_object: + identifier = json_object["object_id"] + + if "extension_schema" in json_object: + identifier = json_object["extension_schema"] + + validator = jsonschema.Draft7Validator(schema) + errors = validator.iter_errors(json_object) + for error in errors: + values = "".join(f"[{v}]" for v in error.path) + results[identifier]["number_of_errors"] += 1 + if len(values) == 0: + error_string = {"top_level": error.message} + else: + error_string = {values: error.message} + results[identifier]["error_detail"].append(error_string) + + return results + + +def parse_bco(bco, results): + """BCO Parsing for Validation + + Parameters + ---------- + bco : JSON + The BCO JSON to be processed for validation. + results : dict + A dictionary to be populated with the BCO validation results + + Returns + ------- + results : dict + A dictionary with the BCO validation results + """ + + identifier = bco["object_id"] + results[identifier] = {"number_of_errors": 0, "error_detail": []} + try: + spec_version = get_schema(bco["spec_version"]) + except ErrorConnecting: + file_path = os.path.dirname( + os.path.abspath("api/validation_definitions/IEEE/2791object.json") + ) -# For catching print output. -import io -import sys + ieee = "api/validation_definitions/IEEE/2791object.json" + with open(ieee, "r", encoding="utf-8") as file: + spec_version = jsonref.load( + file, base_uri=f"file://{file_path}/", jsonschema=True + ) + results = validate(spec_version, bco, results) + if "extension_domain" in bco.keys(): + for extension in bco["extension_domain"]: + extension_id = extension["extension_schema"] + results[identifier][extension_id] = { + "number_of_errors": 0, + "error_detail": [], + } + extension_schema = get_schema(extension_id) + if extension_id in extension_schema: + results[identifier][extension_id] = { + "number_of_errors": 1, + "error_detail": extension_schema, + } + else: + results[identifier] = validate( + extension_schema, extension, results[identifier] + ) + if results[identifier][extension_id]["number_of_errors"] == 0: + results[identifier][extension_id]["error_detail"] = ["Extension Valid"] + results[identifier]["number_of_errors"] += results[identifier][ + extension_id + ]["number_of_errors"] -class JsonUtils: + return results - # Class Description - # ----------------- - # These are methods for checking for valid JSON objects. +class JsonUtils: + """Class Description + ----------------- - # Check for a set of keys. - def check_key_set_exists( - self, - data_pass, - key_set - ): + These are methods for checking for valid JSON objects. + """ - # Arguments - # --------- + # Check for a set of keys. + def check_key_set_exists(self, data_pass, key_set): + """ + Arguments + --------- - # data_pass: the 'raw' data. + data_pass: the 'raw' data. - # Go over each key in the key set and see if it exists - # the in request data. + Go over each key in the key set and see if it exists + the in request data. - # Returns - # ------- + Returns + ------- - # None: all keys were present - # dict: items 'error' and 'associated_key' + None: all keys were present + dict: items 'error' and 'associated_key' - # Assume all keys are present. + Assume all keys are present. + """ missing_keys = [] - + for current_key in key_set: # Was this key found? @@ -51,9 +177,9 @@ def check_key_set_exists( # Append the error. missing_keys.append( { - 'error': 'INVALID_' + current_key.upper() + '_FAILURE', - 'associated_key': current_key, - 'error_message': 'Key ' + current_key + ' not found.' + "error": "INVALID_" + current_key.upper() + "_FAILURE", + "associated_key": current_key, + "error_message": "Key " + current_key + " not found.", } ) @@ -61,13 +187,8 @@ def check_key_set_exists( if not missing_keys: return missing_keys - # Check that what was provided was JSON. - def check_json_exists( - self, - data_pass, - key_set - ): + def check_json_exists(self, data_pass, key_set): # Arguments # -------- @@ -92,31 +213,20 @@ def check_json_exists( try: # First, try to convert the payload string into a JSON object. - json.loads( - s = data_pass[ - current_key - ] - ) + json.loads(s=data_pass[current_key]) except: # Append the error. not_json.append( - { - 'error': 'JSON_CONVERSION_ERROR', - 'associated_key': current_key - } + {"error": "JSON_CONVERSION_ERROR", "associated_key": current_key} ) # Return value is based on whether or not there were errors. if not_json is not []: return not_json - - def load_schema_refs( - self, - schema_pass - ): + def load_schema_refs(self, schema_pass): # Load the references for a given schema. @@ -129,17 +239,9 @@ def load_schema_refs( # Source: https://www.programcreek.com/python/example/83374/jsonschema.RefResolver # Define the resolver. - resolver = jsonschema.RefResolver( - referrer = schema_pass, - base_uri = './' - ) - + resolver = jsonschema.RefResolver(referrer=schema_pass, base_uri="./") - def check_object_against_schema( - self, - object_pass, - schema_pass - ): + def check_object_against_schema(self, object_pass, schema_pass): # Check for schema compliance. @@ -148,19 +250,15 @@ def check_object_against_schema( # object_pass: the object being checked. # schema_pass: the schema to check against. - + # Check the object against the provided schema. # Define a validator. - validator = jsonschema.Draft7Validator( - schema_pass - ) + validator = jsonschema.Draft7Validator(schema_pass) # Define the errors list. - errors = validator.iter_errors( - object_pass - ) - error_string = '' + errors = validator.iter_errors(object_pass) + error_string = "" # We have to use a bit of tricky output re-direction, see https://www.kite.com/python/answers/how-to-redirect-print-output-to-a-variable-in-python @@ -179,13 +277,13 @@ def check_object_against_schema( # These aren't deleted when preparing the code for production... print(e) - print('=================') + print("=================") error_string = error_string + new_stdout.getvalue() sys.stdout = old_stdout # Return based on whether or not there were any errors. if error_flag != 0: - + # Collapse and return the errors. - return error_string \ No newline at end of file + return error_string diff --git a/bco_api/api/scripts/utilities/RequestUtils.py b/bco_api/api/scripts/utilities/RequestUtils.py index e3ce0428..be305c58 100755 --- a/bco_api/api/scripts/utilities/RequestUtils.py +++ b/bco_api/api/scripts/utilities/RequestUtils.py @@ -9,11 +9,7 @@ class RequestUtils: # Check for a valid template. - def check_request_templates( - self, - method, - request - ): + def check_request_templates(self, method, request): # Arguments @@ -26,12 +22,9 @@ def check_request_templates( request_templates = settings.REQUEST_TEMPLATES # Subset the templates to the ones for this request method. - request_templates = request_templates[ - method - ] + request_templates = request_templates[method] # Validate against the templates. return JsonUtils.JsonUtils().check_object_against_schema( - object_pass = request, - schema_pass = request_templates - ) \ No newline at end of file + object_pass=request, schema_pass=request_templates + ) diff --git a/bco_api/api/scripts/utilities/ResponseUtils.py b/bco_api/api/scripts/utilities/ResponseUtils.py index 890a74d7..74b36a87 100755 --- a/bco_api/api/scripts/utilities/ResponseUtils.py +++ b/bco_api/api/scripts/utilities/ResponseUtils.py @@ -6,10 +6,7 @@ class ResponseUtils: # These are methods to help with sending back a (formatted) response. # Clean up the response string. - def beautify_error_set( - self, - errors - ): + def beautify_error_set(self, errors): # Arguments # --------- @@ -26,14 +23,14 @@ def beautify_error_set( error_string = [] # Go through each error set. - for item_index in range( - 0, len( - errors - ) - ): + for item_index in range(0, len(errors)): # Create the error header for ID. - string_helper = 'Errors for item ID: ' + str(item_index) + '\n-------------------------\n' + string_helper = ( + "Errors for item ID: " + + str(item_index) + + "\n-------------------------\n" + ) # Define a list of all errors which will be collapsed. all_errors = [] @@ -43,20 +40,14 @@ def beautify_error_set( # Append this error. all_errors.append( - error_subset['error'] + ': ' + error_subset['error_message'] + error_subset["error"] + ": " + error_subset["error_message"] ) # Collapse the errors into new lines. - string_helper = string_helper + '\n'.join( - all_errors - ) + string_helper = string_helper + "\n".join(all_errors) # Append to the error string. - error_string.append( - string_helper - ) + error_string.append(string_helper) # Collapse all errors for all items and return. - return '\n'.join( - error_string - ) \ No newline at end of file + return "\n".join(error_string) diff --git a/bco_api/api/scripts/utilities/SettingsUtils.py b/bco_api/api/scripts/utilities/SettingsUtils.py index db5830b7..83245feb 100755 --- a/bco_api/api/scripts/utilities/SettingsUtils.py +++ b/bco_api/api/scripts/utilities/SettingsUtils.py @@ -8,6 +8,7 @@ # For loading schema. import jsonref + class SettingsUtils: # Class Description @@ -16,11 +17,7 @@ class SettingsUtils: # These are methods for initializing the program. # Create a dictionary to hold schema information. - def load_schema_local( - self, - search_parameters, - mode - ): + def load_schema_local(self, search_parameters, mode): # search_parameters: dictionary of file search locations and file endings. @@ -35,8 +32,7 @@ def load_schema_local( # Iterate over the search parameters. for folder, extension in search_parameters.items(): raw_files = FileUtils.FileUtils().get_folder_tree_by_extension( - search_folder = folder, - search_extension = extension + search_folder=folder, search_extension=extension ) # We now have the files, so load the schema. @@ -45,19 +41,16 @@ def load_schema_local( schema[folder] = {} # Now go over each path. - for current_file in raw_files['paths']: + for current_file in raw_files["paths"]: # We can now set keys. - with open( - current_file, - mode='r' - ) as f: + with open(current_file, mode="r") as f: schema[folder][current_file] = json.load(f) - + # Set the id. - schema[folder][current_file]['$id'] = 'file:' + current_file - + schema[folder][current_file]["$id"] = "file:" + current_file + # Now go through and define the absolute reference paths. # We have to do this recursively as we do not know # where we will see "$ref$. @@ -74,28 +67,19 @@ def load_schema_local( # by the folders provided in search_parameters. # Source: https://stackoverflow.com/questions/10756427/loop-through-all-nested-dictionary-values - def set_refs( - d, - root_folder - ): + def set_refs(d, root_folder): # Set the keys. - if '$ref' in d: + if "$ref" in d: # If the reference is internal to the document, ignore it. # Otherwise, define the reference. - if d['$ref'][0] != '#': - d['$ref'] = 'file:' + os.getcwd() + '/' + root_folder + d['$ref'] + if d["$ref"][0] != "#": + d["$ref"] = "file:" + os.getcwd() + "/" + root_folder + d["$ref"] for k, v in d.items(): - if isinstance( - v, - dict - ): - set_refs( - v, - root_folder - ) + if isinstance(v, dict): + set_refs(v, root_folder) # Kick it back. return d @@ -104,65 +88,59 @@ def set_refs( # outside of the hosting folder. # Are we defining for requests or for validations? - - if mode == 'requests': - # Call set refs by each top-level folder. + if mode == "requests": + + # Call set refs by each top-level folder. for folder, contents in schema.items(): - schema[folder] = set_refs( - schema[folder], - root_folder = 'api/' - ) + schema[folder] = set_refs(schema[folder], root_folder="api/") - elif mode == 'validations': + elif mode == "validations": # Call set refs by each top-level folder. - for file, contents in schema['validation_definitions/'].items(): + for file, contents in schema["validation_definitions/"].items(): # Split the file name up to help construct the root folder. - file_name_split = file.split('/') + file_name_split = file.split("/") # Where is the 'validation_definitions/' item? - vd_index = file_name_split.index('validation_definitions') + vd_index = file_name_split.index("validation_definitions") # Collapse everything after this index but before the file name. - collapsed = '/'.join( - file_name_split[vd_index+1:len(file_name_split)-1] - ) + '/' + collapsed = ( + "/".join(file_name_split[vd_index + 1 : len(file_name_split) - 1]) + + "/" + ) # Set the name. - schema['validation_definitions/'][file] = set_refs( - schema['validation_definitions/'][file], - root_folder = 'api/validation_definitions/' + collapsed + schema["validation_definitions/"][file] = set_refs( + schema["validation_definitions/"][file], + root_folder="api/validation_definitions/" + collapsed, ) # Return the public-facing schema AND the processed schema? return schema - # Define the schema for each request type. - def define_request_schema( - self, - schema - ): + def define_request_schema(self, schema): # schema: everything found in self.load_local_schema. # Create a dictionary to return all the request types. - returning = {'DELETE': {}, 'GET': {}, 'PATCH': {}, 'POST': {}} + returning = {"DELETE": {}, "GET": {}, "PATCH": {}, "POST": {}} # Now go through the schema to locate the request information. for k, v in schema.items(): # If the object title is a given request type, update returning. - if v['title'] == 'DELETE': - returning['DELETE'] = v - elif v['title'] == 'GET': - returning['GET'] = v - elif v['title'] == 'PATCH': - returning['PATCH'] = v - elif v['title'] == 'POST': - returning['POST'] = v + if v["title"] == "DELETE": + returning["DELETE"] = v + elif v["title"] == "GET": + returning["GET"] = v + elif v["title"] == "PATCH": + returning["PATCH"] = v + elif v["title"] == "POST": + returning["POST"] = v # Kick it back. - return returning \ No newline at end of file + return returning diff --git a/bco_api/api/scripts/utilities/UserUtils.py b/bco_api/api/scripts/utilities/UserUtils.py index bb1ddc48..c1ef5eec 100755 --- a/bco_api/api/scripts/utilities/UserUtils.py +++ b/bco_api/api/scripts/utilities/UserUtils.py @@ -1,22 +1,13 @@ -# Prefix -# from api.model.prefix import Prefix +#!/usr/bin/env python3 +"""User Utilities +Functions for operations with Users +""" -# For returning server information. from django.conf import settings - -# For pulling the user ID directly (see below for -# the note on the documentation error in django-rest-framework). from django.contrib.auth.models import Group, User - -# Permissions from django.contrib.auth.models import Permission - -# For getting the user's token. from rest_framework.authtoken.models import Token -# Conditional logic -from django.db.models import Q - class UserUtils: """ @@ -29,19 +20,20 @@ class UserUtils: ------- """ + def check_permission_exists(self, perm): - """Does the user exist?""" - return Permission.objects.get(codename='test') + """Does the permission exist?""" + return Permission.objects.filter(codename=perm).exists() - def check_group_exists(self, n): + def check_group_exists(self, name): """Does the user exist?""" - return Group.objects.filter(name=n).exists() + return Group.objects.filter(name=name).exists() - def check_user_exists(self, un): + def check_user_exists(self, user_name): """Does the user exist?""" - return User.objects.filter(username=un).exists() + return User.objects.filter(username=user_name).exists() - def check_user_in_group(self, un, gn): + def check_user_in_group(self, user_name, group_name): """Check if a user is in a group. First check that the user exists. @@ -54,42 +46,26 @@ def check_user_in_group(self, un, gn): """ try: - - # Django wants a primary key for the User... - user = User.objects.get(username=un).username - + user = User.objects.get(username=user_name).username try: - - # Django wants a primary key for the Group... - group = Group.objects.get(name=gn).name - - # Finally, check that the user is in the group. - if gn in list(User.objects.get(username=un).groups.values_list('name', flat=True)): - - # Kick back the user and group info. - return { - 'user' : user, - 'group': group - } - + group = Group.objects.get(name=group_name).name + if group_name in list( + User.objects.get(username=user_name).groups.values_list( + "name", flat=True + ) + ): + return {"user": user, "group": group} else: - return False - except Group.DoesNotExist: - - # Bad group. return False - except User.DoesNotExist: - - # Bad user. return False - def check_user_owns_prefix(self, un, prfx): + def check_user_owns_prefix(self, user_name, prfx): """Check if a user owns a prefix.""" - return Prefix.objects.filter(owner_user=un, prefix=prfx).exists() + return Prefix.objects.filter(owner_user=user_name, prefix=prfx).exists() def get_user_groups_by_token(self, token): """Takes token to give groups. @@ -104,15 +80,15 @@ def get_user_groups_by_token(self, token): # group created when the account was created should show up). return Group.objects.filter(user=username) - def get_user_groups_by_username(self, un): + def get_user_groups_by_username(self, user_name): """Takes usernames to give groups. Get the groups for this username (at a minimum the user group created when the account was created should show up). """ - return Group.objects.filter(user=User.objects.get(username=un)) + return Group.objects.filter(user=User.objects.get(username=user_name)) # Get all user information. - def get_user_info(self,username): + def get_user_info(self, username): """Get User Info Arguments @@ -159,37 +135,36 @@ def get_user_info(self,username): user_id = User.objects.get(username=username).pk token = Token.objects.get(user=user_id) other_info = { - 'permissions' : { }, - 'account_creation' : '', - 'account_expiration': '' + "permissions": {}, + "account_creation": "", + "account_expiration": "", } user = User.objects.get(username=username) - user_perms = {'user' : [], 'groups': []} + user_perms = {"user": [], "groups": []} for permission in user.user_permissions.all(): - if permission.name not in user_perms['user']: - user_perms['user'].append(permission.name) + if permission.name not in user_perms["user"]: + user_perms["user"].append(permission.name) for group in user.groups.all(): - if group.name not in user_perms['groups']: - user_perms['groups'].append(group.name) + if group.name not in user_perms["groups"]: + user_perms["groups"].append(group.name) for permission in Permission.objects.filter(group=group): - if permission.name not in user_perms['user']: - user_perms['user'].append(permission.name) + if permission.name not in user_perms["user"]: + user_perms["user"].append(permission.name) - other_info['permissions'] = user_perms + other_info["permissions"] = user_perms - other_info['account_creation'] = user.date_joined + other_info["account_creation"] = user.date_joined return { - 'hostname' : settings.ALLOWED_HOSTS[0], - 'human_readable_hostname': settings.HUMAN_READABLE_HOSTNAME, - 'public_hostname' : settings.PUBLIC_HOSTNAME, - 'token' : token.key, - 'username' : user.username, - 'other_info' : other_info - } - + "hostname": settings.ALLOWED_HOSTS[0], + "human_readable_hostname": settings.HUMAN_READABLE_HOSTNAME, + "public_hostname": settings.PUBLIC_HOSTNAME, + "token": token.key, + "username": user.username, + "other_info": other_info, + } def prefixes_for_user(self, user_object): """Prefix for a given user. @@ -200,21 +175,27 @@ def prefixes_for_user(self, user_object): a prefix automatically means viewing permission. """ - - return list(set([i.split('_')[1] for i in user_object.get_all_permissions()])) + return list(set([i.split("_")[1] for i in user_object.get_all_permissions()])) - def prefix_perms_for_user(self, user_object, flatten=True, specific_permission=None): + def prefix_perms_for_user( + self, user_object, flatten=True, specific_permission=None + ): """Prefix permissions for a given user.""" if specific_permission is None: - specific_permission = ['add', 'change', 'delete', 'view', 'draft', 'publish'] - - prefixed = self.get_user_info( - user_object - )['other_info']['permissions'] + specific_permission = [ + "add", + "change", + "delete", + "view", + "draft", + "publish", + ] + + prefixed = self.get_user_info(user_object)["other_info"]["permissions"] permissions = [] - for pre in prefixed['user']: + for pre in prefixed["user"]: permissions.append(Permission.objects.get(name=pre).codename) return permissions @@ -261,7 +242,7 @@ def prefix_perms_for_user(self, user_object, flatten=True, specific_permission=N # # Return based on what we need. # if flatten == True: - + # # Only unique permissions are returned. # return flat_perms @@ -276,12 +257,13 @@ def user_from_request(self, request): ---------- request: rest_framework.request.Request Django request object. - + Returns ------- django.contrib.auth.models.User """ user_id = Token.objects.get( - key=request.META.get('HTTP_AUTHORIZATION').split(' ')[1]).user_id + key=request.META.get("HTTP_AUTHORIZATION").split(" ")[1] + ).user_id return User.objects.get(id=user_id) diff --git a/bco_api/api/serializers.py b/bco_api/api/serializers.py index 3a51c86b..f7b337cb 100755 --- a/bco_api/api/serializers.py +++ b/bco_api/api/serializers.py @@ -1,13 +1,9 @@ from rest_framework import serializers - - # ----- Request Serializers ----- # - - # Serializers must be abstracted in order to use abstracted models. # Source (last solution): https://stackoverflow.com/questions/33137165/django-rest-framework-abstract-class-serializer @@ -17,20 +13,15 @@ # Source (4th response): https://stackoverflow.com/questions/30831731/create-a-generic-serializer-with-a-dynamic-model-in-meta -def getGenericSerializer( - incoming_model, - incoming_fields -): - class GenericObjectSerializer( - serializers.ModelSerializer - ): - - # Arguments - # incoming_table: the table to write to. +def getGenericSerializer(incoming_model, incoming_fields): + class GenericObjectSerializer(serializers.ModelSerializer): + + # Arguments + # incoming_table: the table to write to. class Meta: model = incoming_model fields = incoming_fields - return GenericObjectSerializer \ No newline at end of file + return GenericObjectSerializer diff --git a/bco_api/api/signals.py b/bco_api/api/signals.py index 9b36b0ea..486aaf9d 100644 --- a/bco_api/api/signals.py +++ b/bco_api/api/signals.py @@ -1,13 +1,13 @@ # Source: https://stackoverflow.com/a/42744626/5029459 + def populate_models(sender, **kwargs): - """Initial DB setup - """ + """Initial DB setup""" from api.models import BCO from api.model.groups import GroupInfo from api.scripts.utilities import DbUtils - + # The BCO groups need to be created FIRST because # models.py listens for user creation and automatically # adds any new user to bco_drafter and bco_publishers. @@ -20,9 +20,6 @@ def populate_models(sender, **kwargs): # Custom publishing permissions which use the model name. # Source: https://stackoverflow.com/a/9940053/5029459 - - - # Create a bco drafter and publisher if they don't exist. @@ -30,107 +27,90 @@ def populate_models(sender, **kwargs): # in models.py # NO password is set here... - if User.objects.filter(username = 'bco_drafter').count() == 0: - User.objects.create_user( - username = 'bco_drafter' - ) - - if User.objects.filter(username = 'bco_publisher').count() == 0: - User.objects.create_user( - username = 'bco_publisher' - ) - + if User.objects.filter(username="bco_drafter").count() == 0: + User.objects.create_user(username="bco_drafter") + + if User.objects.filter(username="bco_publisher").count() == 0: + User.objects.create_user(username="bco_publisher") + # BCO is the anon (public) prefix. - + # Note that user creation is listened for in # models.py by associate_user_group. - - # Create the anonymous user if they don't exist. - if User.objects.filter(username = 'anon').count() == 0: - User.objects.create_user( - username = 'anon' - ) - + + # Create the anonymous user if they don't exist. + if User.objects.filter(username="anon").count() == 0: + User.objects.create_user(username="anon") + # Create an administrator if they don't exist. - if User.objects.filter(username = 'wheel').count() == 0: - User.objects.create_superuser( - username = 'wheel', - password = 'wheel' - ) + if User.objects.filter(username="wheel").count() == 0: + User.objects.create_superuser(username="wheel", password="wheel") # Make bco_publisher the group owner of the prefix 'BCO'. - if BCO.objects.filter(prefix = 'BCO').count() == 0: + if BCO.objects.filter(prefix="BCO").count() == 0: # Django wants a primary key for the Group... - group = Group.objects.get(name = 'bco_publisher').name + group = Group.objects.get(name="bco_publisher").name # Django wants a primary key for the User... - user = User.objects.get(username = 'bco_publisher').username + user = User.objects.get(username="bco_publisher").username DbUtils.DbUtils().write_object( - p_app_label = 'api', - p_model_name = 'Prefix', - p_fields = ['created_by', 'owner_group', 'owner_user', 'prefix'], - p_data = { - 'created_by': user, - 'owner_group': group, - 'owner_user': user, - 'prefix': 'BCO' - } + p_app_label="api", + p_model_name="Prefix", + p_fields=["created_by", "owner_group", "owner_user", "prefix"], + p_data={ + "created_by": user, + "owner_group": group, + "owner_user": user, + "prefix": "BCO", + }, ) # Create the default (non-anon, non-wheel) groups if they don't exist. # Group administrators - if Group.objects.filter(name = 'group_admins').count() == 0: - Group.objects.create(name = 'group_admins') + if Group.objects.filter(name="group_admins").count() == 0: + Group.objects.create(name="group_admins") GroupInfo.objects.create( delete_members_on_group_deletion=False, - description='Group administrators', - group=Group.objects.get(name='group_admins'), + description="Group administrators", + group=Group.objects.get(name="group_admins"), max_n_members=-1, - owner_user=User.objects.get(username='wheel') + owner_user=User.objects.get(username="wheel"), ) # Create the permissions for group administrators. - for perm in ['add', 'change', 'delete', 'view']: - + for perm in ["add", "change", "delete", "view"]: + # Permissions already come with the system, # so just associated them. # Give the group administrators the permissions. - Group.objects.get( - name = 'group_admins' - ).permissions.add( - Permission.objects.get( - codename = perm + '_group' - ) + Group.objects.get(name="group_admins").permissions.add( + Permission.objects.get(codename=perm + "_group") ) - + # Prefix administrators - if Group.objects.filter(name = 'prefix_admins').count() == 0: - Group.objects.create(name = 'prefix_admins') + if Group.objects.filter(name="prefix_admins").count() == 0: + Group.objects.create(name="prefix_admins") GroupInfo.objects.create( delete_members_on_group_deletion=False, - description='Prefix administrators', - group=Group.objects.get(name='prefix_admins'), + description="Prefix administrators", + group=Group.objects.get(name="prefix_admins"), max_n_members=-1, - owner_user=User.objects.get(username='wheel') + owner_user=User.objects.get(username="wheel"), ) - + # Create the permissions for prefix administrators. - for perm in ['add', 'change', 'delete', 'view']: + for perm in ["add", "change", "delete", "view"]: # Permissions already come with the system, # so just associated them. # Give the group administrators the permissions. - Group.objects.get( - name = 'prefix_admins' - ).permissions.add( - Permission.objects.get( - codename = perm + '_prefix' - ) + Group.objects.get(name="prefix_admins").permissions.add( + Permission.objects.get(codename=perm + "_prefix") ) # Associate wheel with all groups. group = Group.objects.all() for g in group: - User.objects.get(username = 'wheel').groups.add(g) + User.objects.get(username="wheel").groups.add(g) diff --git a/bco_api/api/tests/test_bcos.json b/bco_api/api/tests/test_bcos.json index d990063d..dfafed65 100644 --- a/bco_api/api/tests/test_bcos.json +++ b/bco_api/api/tests/test_bcos.json @@ -332,5 +332,339 @@ "extension_domain": [ "" ] + }, + { + "provenance_domain": { + "embargo": {}, + "contributors": [ + { + "name": "Charles Hadley King", + "affiliation": "George Washington University", + "email": "hadley_king@gwu.edu", + "contribution": [ + "createdBy", + "curatedBy" + ], + "orcid": "https://orcid.org/0000-0003-1409-4549" + }, + { + "name": "Eric Donaldson", + "affiliation": "FDA", + "email": "Eric.Donaldson@fda.hhs.gov", + "contribution": [ + "authoredBy" + ] + } + ], + "review": [ + { + "status": "approved", + "reviewer_comment": "Approved by GW staff. Waiting for approval from FDA Reviewer", + "date": "2020-04-21T14:17:21-0400", + "reviewer": { + "name": "Hadley Kinga", + "email": "hadley_king@gwmail.gwu.edu", + "affiliation": "George Washington University", + "contribution": [ + "createdBy" + ] + } + } + ], + "name": "HCV1a ledipasvir resistance SNP detection", + "version": "1.5", + "license": "https://spdx.org/licenses/CC-BY-4.0.html", + "created": "2020-04-28T18:21:31.465Z", + "modified": "2021-09-08T13:42:22.111771" + }, + "description_domain": { + "keywords": [ + "HCV1a", + "Ledipasvir", + "antiviral resistance", + "SNP", + "amino acid substitutions" + ], + "xref": [ + { + "namespace": "pubchem.compound", + "name": "PubChem-compound", + "ids": [ + "67505836" + ], + "access_time": "2020-01-31T06:00:00.000Z" + }, + { + "namespace": "pubmed", + "name": "PubMed", + "ids": [ + "26508693" + ], + "access_time": "2020-01-01T07:00:00.000Z" + }, + { + "namespace": "so", + "name": "Sequence Ontology", + "ids": [ + "SO:000002", + "SO:0000694", + "SO:0000667", + "SO:0000045" + ], + "access_time": "2020-01-01T06:00:00.000Z" + }, + { + "namespace": "taxonomy", + "name": "Taxonomy", + "ids": [ + "31646" + ], + "access_time": "2020-01-01T07:00:00.000Z" + } + ], + "platform": [ + "HIVE" + ], + "pipeline_steps": [ + { + "step_number": 1, + "name": "HIVE-hexagon", + "description": "Alignment of reads to a set of references", + "version": "1.3", + "prerequisite": [ + { + "name": "Hepatitis C virus genotype 1", + "uri": { + "uri": "http://www.ncbi.nlm.nih.gov/nuccore/22129792", + "access_time": "2017-01-24T09:40:17-0500" + } + }, + { + "name": "Hepatitis C virus type 1b complete genome", + "uri": { + "uri": "http://www.ncbi.nlm.nih.gov/nuccore/5420376", + "access_time": "2017-01-24T09:40:17-0500" + } + }, + { + "name": "Hepatitis C virus (isolate JFH-1) genomic RNA", + "uri": { + "uri": "http://www.ncbi.nlm.nih.gov/nuccore/13122261", + "access_time": "2017-01-24T09:40:17-0500" + } + }, + { + "name": "Hepatitis C virus clone J8CF, complete genome", + "uri": { + "uri": "http://www.ncbi.nlm.nih.gov/nuccore/386646758", + "access_time": "2017-01-24T09:40:17-0500" + } + }, + { + "name": "Hepatitis C virus S52 polyprotein gene", + "uri": { + "uri": "http://www.ncbi.nlm.nih.gov/nuccore/295311559", + "access_time": "2017-01-24T09:40:17-0500" + } + } + ], + "input_list": [ + { + "uri": "http://example.com/dna.cgi?cmd=objFile&ids=514683", + "access_time": "2017-01-24T09:40:17-0500" + }, + { + "uri": "http://example.com/dna.cgi?cmd=objFile&ids=514682", + "access_time": "2017-01-24T09:40:17-0500" + } + ], + "output_list": [ + { + "uri": "http://example.com/data/514769/allCount-aligned.csv", + "access_time": "2017-01-24T09:40:17-0500" + } + ] + }, + { + "step_number": 2, + "name": "HIVE-heptagon", + "description": "variant calling", + "version": "1.3", + "input_list": [ + { + "uri": "http://example.com/data/514769/dnaAccessionBased.csv", + "access_time": "2017-01-24T09:40:17-0500" + } + ], + "output_list": [ + { + "uri": "http://example.com/data/514801/SNPProfile.csv", + "access_time": "2017-01-24T09:40:17-0500" + }, + { + "uri": "http://example.com/data/14769/allCount-aligned.csv", + "access_time": "2017-01-24T09:40:17-0500" + } + ] + } + ] + }, + "execution_domain": { + "script": [ + { + "uri": { + "uri": "https://example.com/workflows/antiviral_resistance_detection_hive.py" + } + } + ], + "script_driver": "shell", + "software_prerequisites": [ + { + "name": "HIVE-hexagon", + "version": "babajanian.1", + "uri": { + "uri": "http://example.com/dna.cgi?cmd=dna-hexagon&cmdMode=-", + "access_time": "2017-01-24T09:40:17-0500", + "sha1_checksum": "d60f506cddac09e9e816531e7905ca1ca6641e3c" + } + }, + { + "name": "HIVE-heptagon", + "version": "albinoni.2", + "uri": { + "uri": "http://example.com/dna.cgi?cmd=dna-heptagon&cmdMode=-", + "access_time": "2017-01-24T09:40:17-0500" + } + } + ], + "external_data_endpoints": [ + { + "name": "HIVE", + "url": "http://example.com/dna.cgi?cmd=login" + }, + { + "name": "access to e-utils", + "url": "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/" + } + ], + "environment_variables": { + "HOSTTYPE": "x86_64-linux", + "EDITOR": "vim" + } + }, + "io_domain": { + "input_subdomain": [ + { + "uri": { + "filename": "Hepatitis C virus genotype 1", + "uri": "http://www.ncbi.nlm.nih.gov/nuccore/22129792", + "access_time": "2017-01-24T09:40:17-0500" + } + }, + { + "uri": { + "filename": "Hepatitis C virus type 1b complete genome", + "uri": "http://www.ncbi.nlm.nih.gov/nuccore/5420376", + "access_time": "2017-01-24T09:40:17-0500" + } + }, + { + "uri": { + "filename": "Hepatitis C virus (isolate JFH-1) genomic RNA", + "uri": "http://www.ncbi.nlm.nih.gov/nuccore/13122261", + "access_time": "2017-01-24T09:40:17-0500" + } + }, + { + "uri": { + "uri": "http://www.ncbi.nlm.nih.gov/nuccore/386646758", + "access_time": "2017-01-24T09:40:17-0500" + } + }, + { + "uri": { + "filename": "Hepatitis C virus S52 polyprotein gene", + "uri": "http://www.ncbi.nlm.nih.gov/nuccore/295311559", + "access_time": "2017-01-24T09:40:17-0500" + } + }, + { + "uri": { + "filename": "HCV1a_drug_resistant_sample0001-01", + "uri": "http://example.com/nuc-read/514682", + "access_time": "2017-01-24T09:40:17-0500" + } + }, + { + "uri": { + "filename": "HCV1a_drug_resistant_sample0001-02", + "uri": "http://example.com/nuc-read/514683", + "access_time": "2017-01-24T09:40:17-0500" + } + } + ], + "output_subdomain": [ + { + "mediatype": "text/csv", + "uri": { + "uri": "http://example.com/data/514769/dnaAccessionBased.csv", + "access_time": "2017-01-24T09:40:17-0500" + } + }, + { + "mediatype": "text/csv", + "uri": { + "uri": "http://example.com/data/514801/SNPProfile*.csv", + "access_time": "2017-01-24T09:40:17-0500" + } + } + ] + }, + "error_domain": { + "empirical_error": { + "false_negative_alignment_hits": "<0.0010", + "false_discovery": "<0.05" + }, + "algorithmic_error": { + "false_positive_mutation_calls_discovery": "<0.00005", + "false_discovery": "0.005" + } + }, + "etag": "06c56ccdeeea2e805dd909302d5ab05d3e31c8e16fed17a27510cb00f53cc59e", + "parametric_domain": [ + { + "param": "seed", + "value": "14", + "step": "1" + }, + { + "param": "minimum_match_len", + "value": "66", + "step": "1" + }, + { + "param": "divergence_threshold_percent", + "value": "0.30", + "step": "1" + }, + { + "param": "minimum_coverage", + "value": "15", + "step": "2" + }, + { + "param": "freq_cutoff", + "value": "0.10", + "step": "2" + } + ], + "usability_domain": [ + "Identify baseline single nucleotide polymorphisms (SNPs)[SO:0000694], (insertions)[SO:0000667], and (deletions)[SO:0000045] that correlate with reduced (ledipasvir)[pubchem.compound:67505836] antiviral drug efficacy in (Hepatitis C virus subtype 1)[taxonomy:31646]" + ], + "object_id": "http://localhost:8000/BCO_000001/DRAFT", + "spec_version": "https://w3id.org/ieee/ieee-2791-schema/2791object.json", + "extension_domain": [ + "" + ] } ] \ No newline at end of file diff --git a/bco_api/api/tests/test_forms.py b/bco_api/api/tests/test_forms.py index e48a6bd8..ad97e0ee 100644 --- a/bco_api/api/tests/test_forms.py +++ b/bco_api/api/tests/test_forms.py @@ -5,4 +5,4 @@ from django.test import TestCase -# Create your tests here. \ No newline at end of file +# Create your tests here. diff --git a/bco_api/api/tests/test_group_post_api.py b/bco_api/api/tests/test_group_post_api.py new file mode 100644 index 00000000..61a404b0 --- /dev/null +++ b/bco_api/api/tests/test_group_post_api.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 + +"""Prefix Model Testing + +""" + +import json +from datetime import timedelta +from django.contrib.auth.models import Group, Permission, User +from django.test import TestCase, Client +from django.utils import timezone +from rest_framework.test import force_authenticate, APIRequestFactory +from api.views import ApiGroupsInfo, ApiGroupsCreate, ApiGroupsDelete +from api.tests.test_model_groups import GroupsTestCase +from rest_framework.authtoken.models import Token + + +class GroupApiTestCase(TestCase): + """Tests for API calls""" + + def setUp(self): + """ + Sets up the test variables. + """ + # Client allows us to call the endpoints as if the server was running. + # Since the server isn't running, this lets us use this for testing. + self.client = Client() + self.factory = APIRequestFactory() + + # Sample BCO information for various API calls + # This will let us insert directly in the DB or + # insert via API call. + json_file = open("api/tests/test_bcos.json") + data = json.load(json_file) + self.sample_bco = { + "object_id_root": "BCO_000001", + "object_id_version": "/1.5", + "owner_user": "wheel", + "owner_group": "bco_drafter", + "prefix": "BCO", + "schema": "IEEE", + "state": "PUBLISHED", + "contents": json.dumps(data[0]), + } + self.sample_bco["object_id"] = "http://localhost:8000/{}{}".format( + self.sample_bco["object_id_root"], self.sample_bco["object_id_version"] + ) + self.expiration = timezone.now() + timedelta( + seconds=600 + ) # make valid for 10 minutes + self.user = User.objects.get(username=self.sample_bco["owner_user"]) + + def test_post_api_groups_create(self): + """Test post_api_groups_create API endpoint + + Creates a group. + """ + + view = ApiGroupsCreate.as_view() + + # Set up the request to make, this is the API call along with data that would be posted. + request = self.factory.post( + "api/groups/create/", + { + "POST_api_groups_create": [ + { + "name": "test", + "delete_members_on_group_deletion": True, + "description": "This is a test group.", + "max_n_members": 10, + "expiration": self.expiration, + } + ] + }, + format="json", + HTTP_AUTHORIZATION="Token {}".format(self.user.auth_token), + ) + + # Try to force login as the owner + force_authenticate(request, user=self.user, token=self.user.auth_token) + + # Get the response as if the API was called via a web browser + response = view(request) + + # Assert that it was successful + self.assertEqual(response.data[0]["request_status"], "SUCCESS") + # Assert the status code is as expected (CREATED). + self.assertEqual(response.data[0]["status_code"], "201") + # Assert the request status code (returned via code) is set properly + self.assertEqual(response.status_code, 200) + + def test_post_api_groups_info(self): + """Test post_api_groups_info API endpoint + + Gets group information by user. + """ + + view = ApiGroupsInfo.as_view() + + # Create the group to be queried + current_test = GroupsTestCase() + current_test.setUp() + current_test.create_group() + + request = self.factory.post( + "api/accounts/group_info/", + {"POST_api_groups_info": {"names": ["test"]}}, + format="json", + HTTP_AUTHORIZATION="Token {}".format(self.user.auth_token), + ) + + # Try to force login as the owner + force_authenticate(request, user=self.user, token=self.user.auth_token) + response = view(request) + + # Assert the status code is as expected. + self.assertEqual(response.status_code, 200) + + # TODO: The endpoint isn't completely implemented yet + # Need to revisit assertions once it is. + # print("\ttest_post_api_groups_info response: {}".format(response)) + + def test_post_api_groups_delete(self): + """Test post_api_groups_delete API endpoint + + Deletes a group. + """ + # print("Delete group test.") + view = ApiGroupsDelete.as_view() + + # Create the group to be deleted + current_test = GroupsTestCase() + current_test.setUp() + current_test.create_group() + + request = self.factory.post( + "api/groups/delete/", + {"POST_api_groups_delete": {"names": ["test"]}}, + format="json", + HTTP_AUTHORIZATION="Token {}".format(self.user.auth_token), + ) + + # Try to force login as the owner + force_authenticate(request, user=self.user, token=self.user.auth_token) + response = view(request) + + # print("\ttest_post_api_groups_delete response: {}".format(response.data)) + # import pdb; pdb.set_trace() + # Assert the status code is as expected. + self.assertEqual(response.status_code, 200) diff --git a/bco_api/api/tests/test_model_bco.py b/bco_api/api/tests/test_model_bco.py index 67d8dab7..42ffad3a 100644 --- a/bco_api/api/tests/test_model_bco.py +++ b/bco_api/api/tests/test_model_bco.py @@ -8,6 +8,7 @@ from django.test import TestCase from django.utils import timezone from django.contrib.auth.models import Group, Permission, User + # from django.urls import reverse from api.models import BCO @@ -16,14 +17,16 @@ class BcoTestCase(TestCase): """Test for BCO""" def setUp(self): - self.object_id_root = 'BCO_000001' - self.object_id_version = '/1.5' - self.owner_user = 'anon' - self.owner_group = 'bco_drafter' - self.prefix = 'BCO' - self.schema = 'IEEE' - self.state = 'PUBLISHED' - self.object_id = 'http://localhost:8000/{}{}'.format(self.object_id_root, self.object_id_version) + self.object_id_root = "BCO_000001" + self.object_id_version = "/1.5" + self.owner_user = "anon" + self.owner_group = "bco_drafter" + self.prefix = "BCO" + self.schema = "IEEE" + self.state = "PUBLISHED" + self.object_id = "http://localhost:8000/{}{}".format( + self.object_id_root, self.object_id_version + ) def create_bco(self): """Create Test BCO @@ -33,7 +36,7 @@ def create_bco(self): """ - json_file = open('api/tests/test_bcos.json') + json_file = open("api/tests/test_bcos.json") data = json.load(json_file) return BCO.objects.create( contents=json.dumps(data[0]), @@ -44,13 +47,13 @@ def create_bco(self): prefix=self.prefix, schema=self.schema, state=self.state, - last_update=timezone.now() + last_update=timezone.now(), ) def test_bco_creation(self): """Test BCO creation - Creates BCO, asserts that BCO is an instance of BCO, and asserts the Object id matches + Creates BCO, asserts that BCO is an instance of BCO, and asserts the Object id matches """ biocompute = self.create_bco() @@ -60,16 +63,19 @@ def test_bco_creation(self): self.assertEqual(biocompute.state, self.state) self.assertEqual(biocompute.schema, self.schema) self.assertEqual(str(biocompute.prefix), self.prefix) - self.assertEqual(biocompute.owner_group, Group.objects.get(name=self.owner_group)) - self.assertEqual(biocompute.owner_user, User.objects.get(username=self.owner_user)) + self.assertEqual( + biocompute.owner_group, Group.objects.get(name=self.owner_group) + ) + self.assertEqual( + biocompute.owner_user, User.objects.get(username=self.owner_user) + ) def test_bco_view(self): - """Test BCO Published view submission - """ + """Test BCO Published view submission""" biocompute = self.create_bco() self.assertTrue(isinstance(biocompute, BCO)) - resp = self.client.get(f'/{self.object_id_root}{self.object_id_version}') + resp = self.client.get(f"/{self.object_id_root}{self.object_id_version}") bco_response = json.loads(resp.data[0]) self.assertTrue(isinstance(bco_response, dict)) self.assertEqual(resp.status_code, 200) diff --git a/bco_api/api/tests/test_model_groups.py b/bco_api/api/tests/test_model_groups.py index 3e2a3376..48fd1491 100644 --- a/bco_api/api/tests/test_model_groups.py +++ b/bco_api/api/tests/test_model_groups.py @@ -4,11 +4,11 @@ """ -import json +from datetime import timedelta from django.test import TestCase from django.contrib.auth.models import Group, User from django.utils import timezone -from datetime import timedelta + # from django.urls import reverse from api.model.groups import GroupInfo @@ -22,12 +22,12 @@ def setUp(self): self.delete_members_on_group_deletion = False self.description = "A test group." self.max_n_members = 100 - self.expiration = timezone.now() + timedelta(seconds=600) # make valid for 10 minutes + self.expiration = timezone.now() + timedelta( + seconds=600 + ) # make valid for 10 minutes def create_group(self): - """Create Test Group - - """ + """Create Test Group""" # Return a tuple so we can distinguish easier in the test_group_creation (readability) return ( @@ -38,14 +38,14 @@ def create_group(self): owner_user=User.objects.get(username=self.username), description=self.description, max_n_members=self.max_n_members, - expiration=self.expiration - ) + expiration=self.expiration, + ), ) def test_group_creation(self): """Test Group creation - Creates Group, asserts that group and group info are properly set. + Creates Group, asserts that group and group info are properly set. """ new_group, new_group_info = self.create_group() @@ -55,10 +55,16 @@ def test_group_creation(self): # GroupInfo assertions self.assertTrue(isinstance(new_group_info, GroupInfo)) - self.assertEqual(new_group_info.delete_members_on_group_deletion, self.delete_members_on_group_deletion) + self.assertEqual(new_group_info.__str__(), new_group_info.group.name) + self.assertEqual( + new_group_info.delete_members_on_group_deletion, + self.delete_members_on_group_deletion, + ) self.assertEqual(new_group_info.description, self.description) self.assertEqual(new_group_info.group, Group.objects.get(name=self.group_name)) - self.assertEqual(new_group_info.owner_user, User.objects.get(username=self.username)) + self.assertEqual( + new_group_info.owner_user, User.objects.get(username=self.username) + ) self.assertEqual(new_group_info.max_n_members, self.max_n_members) self.assertEqual(new_group_info.expiration, self.expiration) diff --git a/bco_api/api/tests/test_model_new_user.py b/bco_api/api/tests/test_model_new_user.py deleted file mode 100644 index 4fa28c97..00000000 --- a/bco_api/api/tests/test_model_new_user.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python3 - -"""Models Testing - -""" - -import json -from django.test import TestCase -from django.utils import timezone -from django.contrib.auth.models import Group, Permission, User -# from django.urls import reverse -from api.models import new_users - - -class NewUserTestCase(TestCase): - """Test for BCO""" - - def setUp(self): - self.email = "test@gwu.edu" - self.temp_identifier = "OOO" - self.token = "SampleToken" - self.hostname = "UserDB" - - def create_user(self): - """Create Test User - """ - - return new_users.objects.create( - email="test@gwu.edu", - temp_identifier="OOO", - token="SampleToken", - hostname="UserDB" - ) - - def test_user_creation(self): - """Test user creation - - Creates new user, asserts that it is an instance of new_user, and asserts the email, - token, and hostname match. - """ - - user = self.create_user() - self.assertTrue(isinstance(user, new_users)) - self.assertEqual(user.__email__(), self.email) - self.assertEqual(user.__token__(), self.token) - self.assertEqual(user.__hostname__(), self.hostname) - self.assertEqual(user.__temp_identifier__(), self.temp_identifier) diff --git a/bco_api/api/tests/test_model_prefix.py b/bco_api/api/tests/test_model_prefix.py index 7c099883..5f59d955 100644 --- a/bco_api/api/tests/test_model_prefix.py +++ b/bco_api/api/tests/test_model_prefix.py @@ -8,27 +8,26 @@ from django.test import TestCase from django.utils import timezone from django.contrib.auth.models import Group, Permission, User + # from django.urls import reverse from api.model.prefix import Prefix, prefix_table from datetime import timedelta class PrefixTestCase(TestCase): - """Test for Prefix - - """ + """Test for Prefix""" def setUp(self): - self.username = 'wheel' - self.name = 'bco_drafter' - self.description = 'test prefix' - self.prefix = 'TEST' - self.expiration = timezone.now() + timedelta(seconds=600) # make valid for 10 minutes + self.username = "wheel" + self.name = "bco_drafter" + self.description = "test prefix" + self.prefix = "TEST" + self.expiration = timezone.now() + timedelta( + seconds=600 + ) # make valid for 10 minutes def create_prefix(self): - """Create Test Prefix - - """ + """Create Test Prefix""" return Prefix.objects.create( prefix=self.prefix, @@ -37,13 +36,13 @@ def create_prefix(self): owner_user=User.objects.get(username=self.username), description=self.description, created=timezone.now(), - expires=self.expiration + expires=self.expiration, ) def test_prefix_creation(self): """Test prefix creation - Creates prefix, + Creates prefix, """ prefix = self.create_prefix() @@ -62,28 +61,21 @@ def test_prefix_creation(self): class PrefixTableTestCase(TestCase): - """Test for Prefix Table - - """ + """Test for Prefix Table""" def setUp(self): self.n_objects = 4 - self.prefix = 'TEST' + self.prefix = "TEST" def create_prefix_table(self): - """Create Test Prefix Table + """Create Test Prefix Table""" - """ + return prefix_table.objects.create(prefix=self.prefix, n_objects=self.n_objects) - return prefix_table.objects.create( - prefix=self.prefix, - n_objects=self.n_objects - ) - - def test_prefix_creation(self): + def test_prefix_table_creation(self): """Test prefix creation - Creates prefix, + Creates prefix, """ ptable = self.create_prefix_table() diff --git a/bco_api/api/tests/test_model_user.py b/bco_api/api/tests/test_model_user.py new file mode 100644 index 00000000..62c9d424 --- /dev/null +++ b/bco_api/api/tests/test_model_user.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 + +"""User Testing + +""" + +from django.test import TestCase +from api.scripts.utilities.DbUtils import DbUtils as db_utils +from api.scripts.utilities.UserUtils import UserUtils as user_utils + +# from django.urls import reverse +from api.models import new_users + + +class UserTestCase(TestCase): + """User Test Case""" + + def setUp(self): + self.email = "test@gwu.edu" + self.temp_identifier = "OOO" + self.token = "SampleToken" + self.hostname = "UserDB" + + def create_user(self): + """Create Test User""" + + return new_users.objects.create( + email="test@gwu.edu", + temp_identifier="OOO", + token="SampleToken", + hostname="UserDB", + ) + + def test_user_creation(self): + """Test user creation + + Creates new user, asserts that it is an instance of new_user, and asserts the email, + token, and hostname match. + """ + + user = self.create_user() + self.assertTrue(isinstance(user, new_users)) + self.assertEqual(user.__email__(), self.email) + self.assertEqual(user.__token__(), self.token) + self.assertEqual(user.__hostname__(), self.hostname) + self.assertEqual(user.__temp_identifier__(), self.temp_identifier) + # import pdb; pdb.set_trace() + + def test_activate_user(self): + """Activate new user + + Creates new user, activates the user, then asserts that it is in the + proper groups. + """ + + user = self.create_user() + self.assertTrue( + db_utils.check_activation_credentials( + self, + p_app_label="api", + p_model_name="new_users", + p_email=user.email, + p_temp_identifier=user.temp_identifier, + ) + ) + + username = db_utils.activate_account(self, p_email=user.email)[0] + info = user_utils.get_user_info(self, username) + groups = info["other_info"]["permissions"]["groups"] + self.assertTrue(user_utils.check_user_exists(self, username)) + self.assertIn("bco_drafter", groups) + self.assertIn("bco_publisher", groups) + self.assertIn(str(username), groups) diff --git a/bco_api/api/tests/test_prefix_post_api.py b/bco_api/api/tests/test_prefix_post_api.py index 5437994f..d70be575 100644 --- a/bco_api/api/tests/test_prefix_post_api.py +++ b/bco_api/api/tests/test_prefix_post_api.py @@ -5,47 +5,61 @@ """ import json -from django.test import TestCase, Client +from django.test import TestCase, Client, RequestFactory from django.utils import timezone +from django.urls import reverse from django.contrib.auth.models import Group, Permission, User +from rest_framework.authtoken.models import Token + # from django.urls import reverse from api.models import BCO +from api.views import ApiObjectsDraftsCreate +from api.scripts.method_specific.POST_api_objects_drafts_create import ( + post_api_objects_drafts_create, +) +from rest_framework.test import APIRequestFactory, force_authenticate + from datetime import timedelta class ApiTestCase(TestCase): - """Tests for API calls - - """ + """Tests for API calls""" def setUp(self): # Client allows us to call the endpoints as if the server was running. # Since the server isn't running, this lets us use this for testing. self.client = Client() + self.factory = APIRequestFactory() # Sample BCO information for various API calls # This will let us insert direclty in the DB or # insert via API call. - json_file = open('api/tests/test_bcos.json') + json_file = open("api/tests/test_bcos.json") data = json.load(json_file) self.sample_bco = { - "object_id_root": 'BCO_000001', - "object_id_version": '/1.5', - "owner_user": 'anon', - "owner_group": 'bco_drafter', - "prefix": 'BCO', - "schema": 'IEEE', - "state": 'PUBLISHED', - "contents": json.dumps(data[0]) + "object_id_root": "BCO_000001", + "object_id_version": "/1.5", + "owner_user": "bco_drafter", + "owner_group": "bco_drafter", + "prefix": "BCO", + "schema": "IEEE", + "state": "PUBLISHED", + "contents": data[0], } - self.sample_bco["object_id"] = 'http://localhost:8000/{}{}'.format(self.sample_bco["object_id_root"], self.sample_bco["object_id_version"]) + self.sample_bco["object_id"] = "http://localhost:8000/{}{}".format( + self.sample_bco["object_id_root"], self.sample_bco["object_id_version"] + ) + self.expiration = timezone.now() + timedelta( + seconds=600 + ) # make valid for 10 minutes + # self.user = User.objects.create_user(username=self.sample_bco["owner_user"], password="12345") - self.expiration = timezone.now() + timedelta(seconds=600) # make valid for 10 minutes + self.user = User.objects.get(username=self.sample_bco["owner_user"]) + # self.user.set_password("12345") + # self.user.save() def create_bco_direct(self): - """Create BCO directly in DB if needed - - """ + """Create BCO directly in DB if needed""" # We can use this to create a BCO in the DB if the API call to create fails. return BCO.objects.create( contents=self.sample_bco["contents"], @@ -56,32 +70,62 @@ def create_bco_direct(self): prefix=self.sample_bco["prefix"], schema=self.sample_bco["schema"], state=self.sample_bco["state"], - last_update=timezone.now() + last_update=timezone.now(), ) def test_post_api_prefixes_create(self): """Test post_api_prefixes_create API endpoint - Creates a prefix. + Creates a prefix. """ # Try to force login as the owner - if self.client.force_login(user=User.objects.get(username=self.sample_bco["owner_user"])): - response = self.client.post('/api/objects/drafts/create/', { - "POST_api_objects_draft_create": - [ - { - "prefix": self.sample_bco["prefix"], - "owner_group": self.sample_bco["owner_group"], - "object_id": self.sample_bco["object_id"], - "schema": self.sample_bco["schema"], - "contents": self.sample_bco["contents"], - } - ] - }) - print("Response: ".format(response)) - else: - # Appears login failed - print("Failed login") - return False + token = Token.objects.get(user=self.user) + + bco_request = { + "POST_api_objects_draft_create": [ + { + "prefix": self.sample_bco["prefix"], + "owner_group": self.sample_bco["owner_group"], + "object_id": self.sample_bco["object_id"], + "schema": self.sample_bco["schema"], + "contents": self.sample_bco["contents"], + } + ] + } + # anon needs permissions first + + request = self.factory.post( + path="/api/objects/drafts/create/", + data=bco_request, + format="json", + HTTP_AUTHORIZATION="Token {}".format(token), + ) + force_authenticate(request, user=self.user) + + response = ApiObjectsDraftsCreate.as_view()(request) + # print("RESPONSE: {}".format(response)) + # # if self.client.force_login(user=user): + # self.client.force_login(user=self.user) + # self.client.login(username=self.sample_bco["owner_user"], password="12345", token=token) + # response = self.client.post('/api/objects/drafts/create/', { + # "POST_api_objects_draft_create": + # [ + # { + # "prefix": self.sample_bco["prefix"], + # "owner_group": self.sample_bco["owner_group"], + # "object_id": self.sample_bco["object_id"], + # "schema": self.sample_bco["schema"], + # "contents": self.sample_bco["contents"], + # } + # ] + # }) + self.assertEqual(response.status_code, 200) + # print("Bad response ({}) when trying to test /api/objects/drafts/create/".format(response.status_code)) + # return False + # return True + # else: + # # Appears login failed + # print("Failed login") + # return False diff --git a/bco_api/api/tests/test_views.py b/bco_api/api/tests/test_views.py index 6b53d884..7f16c428 100644 --- a/bco_api/api/tests/test_views.py +++ b/bco_api/api/tests/test_views.py @@ -3,4 +3,4 @@ """ -from django.test import TestCase \ No newline at end of file +from django.test import TestCase diff --git a/bco_api/api/tests_automated.py b/bco_api/api/tests_automated.py index 7026e000..4e351b3a 100755 --- a/bco_api/api/tests_automated.py +++ b/bco_api/api/tests_automated.py @@ -9,14 +9,14 @@ from django.db.models.signals import post_migrate from api.signals import populate_models + class ApiConfig(TestCase): - """API Configuration - """ + """API Configuration""" - default_auto_field = 'django.db.models.AutoField' - name = 'api' + default_auto_field = "django.db.models.AutoField" + name = "api" def ready(self): """Create the anonymous user if they don't exist.""" post_migrate.connect(populate_models, sender=self) - print('test') + print("test") diff --git a/bco_api/api/urls.py b/bco_api/api/urls.py index edbaacc7..426622ea 100755 --- a/bco_api/api/urls.py +++ b/bco_api/api/urls.py @@ -6,7 +6,7 @@ # For importing configuration files import configparser -from xml.etree.ElementTree import VERSION +from django.conf import settings # For favicon and any other static files from django.urls import path, re_path @@ -17,7 +17,7 @@ from drf_yasg.views import get_schema_view from drf_yasg import openapi -from .views import ( +from api.views import ( ApiAccountsActivateUsernameTempIdentifier, ApiAccountsDescribe, ApiAccountsNew, @@ -45,21 +45,24 @@ ApiPublicDescribe, DraftObjectId, ObjectIdRootObjectId, - ObjectIdRootObjectIdVersion + ObjectIdRootObjectIdVersion, + ValidateBCO, ) + # Load the server config file. server_config = configparser.ConfigParser() -server_config.read('./server.conf') -PUBLISH_ONLY = server_config['PUBLISHONLY']['publishonly'] -VERSION = server_config['VERSION']['version'] +server_config.read(settings.BASE_DIR + "/server.conf") + +PUBLISH_ONLY = server_config["PUBLISHONLY"]["publishonly"] +VERSION = server_config["VERSION"]["version"] ShcemaView = get_schema_view( openapi.Info( title="BioCompute Object Data Base API (BCODB API)", default_version=VERSION, description="A web application that can be used to create, store and " - "edit BioCompute objects based on BioCompute schema described " - "in the BCO specification document.", + "edit BioCompute objects based on BioCompute schema described " + "in the BCO specification document.", terms_of_service="https://github.com/biocompute-objects/bco_api/blob/master/LICENSE", contact=openapi.Contact(email="object.biocompute@gmail.com"), license=openapi.License(name="MIT License"), @@ -71,133 +74,90 @@ urlpatterns = [] # Do we have a publish-only server? -if PUBLISH_ONLY == 'True': +if PUBLISH_ONLY == "True": urlpatterns = [ - - re_path(r'^api/doc(?P\.json|\.yaml)$', + re_path( + r"^api/doc(?P\.json|\.yaml)$", ShcemaView.without_ui(cache_timeout=0), - name='schema-json' - ), - path('api/docs/', - ShcemaView.with_ui('swagger', cache_timeout=0), - name='schema-swagger-ui' - ), - path('api/redocs/', - ShcemaView.with_ui('redoc', cache_timeout=0), - name='schema-redoc' - ), - path('', - ObjectIdRootObjectId.as_view() + name="schema-json", ), - path('/', - ObjectIdRootObjectIdVersion.as_view() + path( + "api/docs/", + ShcemaView.with_ui("swagger", cache_timeout=0), + name="schema-swagger-ui", ), - path('api/objects/publish/', - ApiObjectsPublish.as_view() + path( + "api/redocs/", + ShcemaView.with_ui("redoc", cache_timeout=0), + name="schema-redoc", ), - path('api/objects/published/', - ApiObjectsPublished.as_view() + path("", ObjectIdRootObjectId.as_view()), + path( + "/", + ObjectIdRootObjectIdVersion.as_view(), ), - path('api/public/describe/', - ApiPublicDescribe.as_view() - ) + path("api/objects/publish/", ApiObjectsPublish.as_view()), + path("api/objects/published/", ApiObjectsPublished.as_view()), + path("api/public/describe/", ApiPublicDescribe.as_view()), ] -elif PUBLISH_ONLY == 'False': +elif PUBLISH_ONLY == "False": urlpatterns = [ - re_path(r'^api/docs(?P\.json|\.yaml)$', + re_path( + r"^api/docs(?P\.json|\.yaml)$", ShcemaView.without_ui(cache_timeout=0), - name='schema-json' + name="schema-json", + ), + path( + "favicon.ico", + RedirectView.as_view(url=staticfiles_storage.url("img/favicon.ico")), + ), + path( + "api/docs/", + ShcemaView.with_ui("swagger", cache_timeout=0), + name="schema-swagger-ui", + ), + path( + "api/redocs/", + ShcemaView.with_ui("redoc", cache_timeout=0), + name="schema-redoc", + ), + path("/DRAFT", DraftObjectId.as_view()), + path("", ObjectIdRootObjectId.as_view()), + path( + "/", + ObjectIdRootObjectIdVersion.as_view(), + ), + path( + "api/accounts/activate//", + ApiAccountsActivateUsernameTempIdentifier.as_view(), + ), + path("api/accounts/describe/", ApiAccountsDescribe.as_view()), + path("api/accounts/new/", ApiAccountsNew.as_view()), + path("api/groups/group_info/", ApiGroupsInfo.as_view()), + path("api/groups/create/", ApiGroupsCreate.as_view()), + path("api/groups/delete/", ApiGroupsDelete.as_view()), + path("api/groups/modify/", ApiGroupsModify.as_view()), + path("api/objects/drafts/create/", ApiObjectsDraftsCreate.as_view()), + path("api/objects/drafts/modify/", ApiObjectsDraftsModify.as_view()), + path("api/objects/drafts/permissions/", ApiObjectsDraftsPermissions.as_view()), + path( + "api/objects/drafts/permissions/set/", + ApiObjectsDraftsPermissionsSet.as_view(), ), - path('favicon.ico', RedirectView.as_view(url=staticfiles_storage.url('img/favicon.ico'))), - path('api/docs/', - ShcemaView.with_ui('swagger', cache_timeout=0), - name='schema-swagger-ui'), - path('api/redocs/', - ShcemaView.with_ui('redoc', cache_timeout=0), - name='schema-redoc' - ), - path('/DRAFT', - DraftObjectId.as_view() - ), - path('', - ObjectIdRootObjectId.as_view() - ), - path('/', - ObjectIdRootObjectIdVersion.as_view() - ), - path('api/accounts/activate//', - ApiAccountsActivateUsernameTempIdentifier.as_view() - ), - path('api/accounts/describe/', - ApiAccountsDescribe.as_view() - ), - path('api/accounts/new/', - ApiAccountsNew.as_view() - ), - path('api/groups/group_info/', - ApiGroupsInfo.as_view() - ), - path('api/groups/create/', - ApiGroupsCreate.as_view() - ), - path('api/groups/delete/', - ApiGroupsDelete.as_view() - ), - path('api/groups/modify/', - ApiGroupsModify.as_view() - ), - path('api/objects/drafts/create/', - ApiObjectsDraftsCreate.as_view() - ), - path('api/objects/drafts/modify/', - ApiObjectsDraftsModify.as_view() - ), - path('api/objects/drafts/permissions/', - ApiObjectsDraftsPermissions.as_view() - ), - path('api/objects/drafts/permissions/set/', - ApiObjectsDraftsPermissionsSet.as_view() - ), - path('api/objects/drafts/publish/', - ApiObjectsDraftsPublish.as_view() - ), - path('api/objects/drafts/read/', - ApiObjectsDraftsRead.as_view() - ), - path('api/objects/drafts/token/', - ApiObjectsDraftsToken.as_view() - ), - path('api/objects/publish/', - ApiObjectsPublish.as_view() - ), - path('api/objects/search/', - ApiObjectsSearch.as_view() - ), - path('api/objects/token/', - ApiObjectsToken.as_view() - ), - path('api/objects/published/', - ApiObjectsPublished.as_view() - ), - path('api/prefixes/create/', - ApiPrefixesCreate.as_view() - ), - path('api/prefixes/delete/', - ApiPrefixesDelete.as_view() - ), - path('api/prefixes/permissions/set/', - ApiPrefixesPermissionsSet.as_view() - ), - path('api/prefixes/token/', - ApiPrefixesToken.as_view() - ), - path('api/prefixes/token/flat/', - ApiPrefixesTokenFlat.as_view() - ), - path('api/prefixes/modify/', - ApiPrefixesModify.as_view() - ), - path('api/public/describe/', - ApiPublicDescribe.as_view()) + path("api/objects/drafts/publish/", ApiObjectsDraftsPublish.as_view()), + path("api/objects/drafts/read/", ApiObjectsDraftsRead.as_view()), + path("api/objects/drafts/token/", ApiObjectsDraftsToken.as_view()), + path("api/objects/publish/", ApiObjectsPublish.as_view()), + path("api/objects/search/", ApiObjectsSearch.as_view()), + path("api/objects/validate/", ValidateBCO.as_view()), + path("api/objects/token/", ApiObjectsToken.as_view()), + path("api/objects/published/", ApiObjectsPublished.as_view()), + path("api/prefixes/create/", ApiPrefixesCreate.as_view()), + path("api/prefixes/delete/", ApiPrefixesDelete.as_view()), + path("api/prefixes/permissions/set/", ApiPrefixesPermissionsSet.as_view()), + path("api/prefixes/token/", ApiPrefixesToken.as_view()), + path("api/prefixes/token/flat/", ApiPrefixesTokenFlat.as_view()), + path("api/prefixes/modify/", ApiPrefixesModify.as_view()), + path("api/public/describe/", ApiPublicDescribe.as_view()), ] diff --git a/bco_api/api/validation_definitions/IEEE/IEEE2791-2020.schema b/bco_api/api/validation_definitions/IEEE/2791object.json similarity index 100% rename from bco_api/api/validation_definitions/IEEE/IEEE2791-2020.schema rename to bco_api/api/validation_definitions/IEEE/2791object.json diff --git a/bco_api/api/views.py b/bco_api/api/views.py index 51074c79..6ab90d56 100755 --- a/bco_api/api/views.py +++ b/bco_api/api/views.py @@ -3,30 +3,26 @@ Django views for BCODB API """ -# Based on the "Class Based API View" example at -# https://codeloop.org/django-rest-framework-course-for-beginners/ - -# For instructions on calling class methods from other classes, see -# https://stackoverflow.com/questions/3856413/call-class-method-from-another-class from drf_yasg import openapi from drf_yasg.utils import swagger_auto_schema from rest_framework import status -# By-view permissions from rest_framework.permissions import IsAuthenticated -# Message page -# Source: https://www.django-rest-framework.org/topics/html-and-forms/#rendering-html from rest_framework.renderers import TemplateHTMLRenderer from rest_framework.response import Response -# Views from rest_framework.views import APIView - -from .permissions import RequestorInPrefixAdminsGroup -# FIX +from api.permissions import RequestorInPrefixAdminsGroup from api.scripts.method_specific.GET_activate_account import GET_activate_account from api.scripts.method_specific.GET_draft_object_by_id import get_draft_object_by_id -from api.scripts.method_specific.GET_published_object_by_id import GET_published_object_by_id -from api.scripts.method_specific.GET_published_object_by_id_with_version import GET_published_object_by_id_with_version +from api.scripts.method_specific.GET_published_object_by_id import ( + GET_published_object_by_id, +) +from api.scripts.method_specific.GET_published_object_by_id_with_version import ( + GET_published_object_by_id_with_version, +) +from api.scripts.method_specific.POST_validate_payload_against_schema import ( + post_validate_bco, +) # Request-specific methods from api.model.groups import ( @@ -40,31 +36,43 @@ post_api_prefixes_delete, post_api_prefixes_modify, post_api_prefixes_permissions_set, - post_api_prefixes_token, post_api_prefixes_token_flat, ) -from api.scripts.method_specific.POST_api_accounts_describe import POST_api_accounts_describe +from api.scripts.method_specific.POST_api_accounts_describe import ( + POST_api_accounts_describe, +) from api.scripts.method_specific.POST_api_accounts_new import POST_api_accounts_new -from api.scripts.method_specific.POST_api_objects_drafts_create import post_api_objects_drafts_create -from api.scripts.method_specific.POST_api_objects_drafts_modify import post_api_objects_drafts_modify -from api.scripts.method_specific.POST_api_objects_drafts_permissions import POST_api_objects_drafts_permissions -from api.scripts.method_specific.POST_api_objects_drafts_permissions_set import POST_api_objects_drafts_permissions_set -from api.scripts.method_specific.POST_api_objects_drafts_publish import post_api_objects_drafts_publish -from api.scripts.method_specific.POST_api_objects_drafts_read import POST_api_objects_drafts_read -from api.scripts.method_specific.POST_api_objects_drafts_token import POST_api_objects_drafts_token -from api.scripts.method_specific.POST_api_objects_publish import POST_api_objects_publish -from api.scripts.method_specific.POST_api_objects_published import POST_api_objects_published +from api.scripts.method_specific.POST_api_objects_drafts_create import ( + post_api_objects_drafts_create, +) +from api.scripts.method_specific.POST_api_objects_drafts_modify import ( + post_api_objects_drafts_modify, +) +from api.scripts.method_specific.POST_api_objects_drafts_permissions import ( + POST_api_objects_drafts_permissions, +) +from api.scripts.method_specific.POST_api_objects_drafts_permissions_set import ( + POST_api_objects_drafts_permissions_set, +) +from api.scripts.method_specific.POST_api_objects_drafts_publish import ( + post_api_objects_drafts_publish, +) +from api.scripts.method_specific.POST_api_objects_drafts_read import ( + POST_api_objects_drafts_read, +) +from api.scripts.method_specific.POST_api_objects_drafts_token import ( + POST_api_objects_drafts_token, +) +from api.scripts.method_specific.POST_api_objects_publish import ( + post_api_objects_publish, +) +from api.scripts.method_specific.POST_api_objects_published import ( + POST_api_objects_published, +) from api.scripts.method_specific.POST_api_objects_search import post_api_objects_search from api.scripts.method_specific.POST_api_objects_token import POST_api_objects_token -# from api.scripts.method_specific.POST_api_prefixes_create import POST_api_prefixes_create -# from api.scripts.method_specific.POST_api_prefixes_delete import POST_api_prefixes_delete -# from api.scripts.method_specific.POST_api_prefixes_modify import POST_api_prefixes_modify -# from api.scripts.method_specific.POST_api_prefixes_permissions_set import POST_api_prefixes_permissions_set -# from api.scripts.method_specific.POST_api_prefixes_token import POST_api_prefixes_token -# from api.scripts.method_specific.POST_api_prefixes_token_flat import POST_api_prefixes_token_flat - # For helper functions from api.scripts.utilities import UserUtils @@ -132,43 +140,58 @@ class ApiAccountsActivateUsernameTempIdentifier(APIView): other users to act as the verification layer in addition to the system. """ + authentication_classes = [] permission_classes = [] # For the success and error messages renderer_classes = [TemplateHTMLRenderer] - template_name = 'api/account_activation_message.html' + template_name = "api/account_activation_message.html" auth = [] - auth.append(openapi.Parameter('username', openapi.IN_PATH, description="Username to be authenticated.", - type=openapi.TYPE_STRING)) - auth.append(openapi.Parameter('temp_identifier', openapi.IN_PATH, - description="The temporary identifier needed to authenticate the activation. This " - "is found in the temporary account table (i.e. where an account is " - "staged).", - type=openapi.TYPE_STRING)) - - @swagger_auto_schema(manual_parameters=auth, responses={ + auth.append( + openapi.Parameter( + "username", + openapi.IN_PATH, + description="Username to be authenticated.", + type=openapi.TYPE_STRING, + ) + ) + auth.append( + openapi.Parameter( + "temp_identifier", + openapi.IN_PATH, + description="The temporary identifier needed to authenticate the activation. This " + "is found in the temporary account table (i.e. where an account is " + "staged).", + type=openapi.TYPE_STRING, + ) + ) + + @swagger_auto_schema( + manual_parameters=auth, + responses={ 201: "Account has been authorized.", 208: "Account has already been authorized.", 403: "Requestor's credentials were rejected.", - 424: "Account has not been registered." - }, tags=["Account Management"]) + 424: "Account has not been registered.", + }, + tags=["Account Management"], + ) def get(self, request, username: str, temp_identifier: str): - # Check the request to make sure it is valid - not sure what this is really doing though - # Placeholder + """Check the request to make sure it is valid - not sure what this is really doing though + Placeholder""" check_get(request) checked = None if checked is None: # Pass the request to the handling function - return GET_activate_account(username=username, temp_identifier=temp_identifier) + return GET_activate_account( + username=username, temp_identifier=temp_identifier + ) else: return Response( - { - 'activation_success': False, - 'status' : status.HTTP_400_BAD_REQUEST - } - ) + {"activation_success": False, "status": status.HTTP_400_BAD_REQUEST} + ) # Source: https://www.django-rest-framework.org/api-guide/authentication/#by-exposing-an-api-endpoint @@ -178,30 +201,37 @@ class ApiAccountsDescribe(APIView): -------------------- No schema for this request since only the Authorization header is required. - The word 'Token' must be included in the header. - For example: 'Token 627626823549f787c3ec763ff687169206626149' + The word 'Token' must be included in the header. + For example: 'Token 627626823549f787c3ec763ff687169206626149' """ auth = [ - openapi.Parameter('Authorization', + openapi.Parameter( + "Authorization", openapi.IN_HEADER, description="Authorization Token", - type=openapi.TYPE_STRING + type=openapi.TYPE_STRING, ) ] - @swagger_auto_schema(manual_parameters=auth, responses={ + @swagger_auto_schema( + manual_parameters=auth, + responses={ 200: "Authorization is successful.", 400: "Bad request. Authorization is not provided in the request headers.", - 401: "Unauthorized. Authentication credentials were not provided." - }, tags=["Account Management"]) + 401: "Unauthorized. Authentication credentials were not provided.", + }, + tags=["Account Management"], + ) def post(self, request): """ Pass the request to the handling function - Source: https://stackoverflow.com/a/31813810 + Source: https://stackoverflow.com/a/31813810 """ - if 'Authorization' in request.headers: - return POST_api_accounts_describe(token=request.META.get('HTTP_AUTHORIZATION')) + if "Authorization" in request.headers: + return POST_api_accounts_describe( + token=request.META.get("HTTP_AUTHORIZATION") + ) else: return Response(status=status.HTTP_400_BAD_REQUEST) @@ -216,39 +246,37 @@ class ApiGroupsInfo(APIView): """ POST_api_groups_info_schema = openapi.Schema( - type=openapi.TYPE_OBJECT, - required=['names'], - properties={ - 'names': openapi.Schema( - type=openapi.TYPE_ARRAY, - description='List of groups to delete.', - items=openapi.Schema( - type=openapi.TYPE_STRING - ) - ), - } - ) + type=openapi.TYPE_OBJECT, + required=["names"], + properties={ + "names": openapi.Schema( + type=openapi.TYPE_ARRAY, + description="List of groups to delete.", + items=openapi.Schema(type=openapi.TYPE_STRING), + ), + }, + ) request_body = openapi.Schema( type=openapi.TYPE_OBJECT, title="Group Information Schema", description="API call checks a user's groups and permissions" - " in this system.", - required=['POST_api_groups_info'], - properties={ - 'POST_api_groups_info': POST_api_groups_info_schema - } + " in this system.", + required=["POST_api_groups_info"], + properties={"POST_api_groups_info": POST_api_groups_info_schema}, ) - @swagger_auto_schema(request_body=request_body, responses={ + @swagger_auto_schema( + request_body=request_body, + responses={ 200: "Authorization is successful. Group permissions returned", 400: "Bad request. Authorization is not provided in the request headers.", - 401: "Unauthorized. Authentication credentials were not valid." - }, tags=["Group Management"]) - + 401: "Unauthorized. Authentication credentials were not valid.", + }, + tags=["Group Management"], + ) def post(self, request): - """ Post? - """ + """Post?""" return check_post_and_process(request, post_api_groups_info) @@ -262,45 +290,62 @@ class ApiGroupsCreate(APIView): POST_api_groups_create_schema = openapi.Schema( type=openapi.TYPE_OBJECT, - required=['name'], + required=["name"], properties={ - 'name': openapi.Schema(type=openapi.TYPE_STRING, - description='The name of the group to create'), - 'usernames': openapi.Schema(type=openapi.TYPE_ARRAY, + "name": openapi.Schema( + type=openapi.TYPE_STRING, description="The name of the group to create" + ), + "usernames": openapi.Schema( + type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING), - description='List of users to add to the group.'), - 'delete_members_on_group_deletion': openapi.Schema(type=openapi.TYPE_BOOLEAN, - description='Delete the members of the group if the group is deleted.'), - 'description': openapi.Schema(type=openapi.TYPE_STRING, - description='Description of the group.'), - 'expiration': openapi.Schema(type=openapi.TYPE_STRING, - description='Expiration date and time of the group. Note, ' - 'this needs to be in a Python DateTime compatible format.'), - 'max_n_members': openapi.Schema(type=openapi.TYPE_INTEGER, - description='Maximum number of members to allow in the group.'), - }, - description="Groups to create along with associated information." - ) + description="List of users to add to the group.", + ), + "delete_members_on_group_deletion": openapi.Schema( + type=openapi.TYPE_BOOLEAN, + description="Delete the members of the group if the group is deleted.", + ), + "description": openapi.Schema( + type=openapi.TYPE_STRING, description="Description of the group." + ), + "expiration": openapi.Schema( + type=openapi.TYPE_STRING, + description="Expiration date and time of the group. Note, " + "this needs to be in a Python DateTime compatible format.", + ), + "max_n_members": openapi.Schema( + type=openapi.TYPE_INTEGER, + description="Maximum number of members to allow in the group.", + ), + }, + description="Groups to create along with associated information.", + ) request_body = openapi.Schema( - type=openapi.TYPE_OBJECT, - title="Group Creation Schema", - description="Parameters that are supported when trying to create a group.", - required=['POST_api_groups_create'], - properties={ - 'POST_api_groups_create': openapi.Schema(type=openapi.TYPE_ARRAY, - items=POST_api_groups_create_schema, - description='Groups and actions to take on them.')}) - - @swagger_auto_schema(request_body=request_body, responses={ + type=openapi.TYPE_OBJECT, + title="Group Creation Schema", + description="Parameters that are supported when trying to create a group.", + required=["POST_api_groups_create"], + properties={ + "POST_api_groups_create": openapi.Schema( + type=openapi.TYPE_ARRAY, + items=POST_api_groups_create_schema, + description="Groups and actions to take on them.", + ) + }, + ) + + @swagger_auto_schema( + request_body=request_body, + responses={ 200: "Group creation is successful.", 400: "Bad request.", 403: "Invalid token.", - 409: "Group conflict. There is already a group with this name." - }, tags=["Group Management"]) + 409: "Group conflict. There is already a group with this name.", + }, + tags=["Group Management"], + ) def post(self, request): - """"Post? - """ + """ "Post?""" return check_post_and_process(request, post_api_groups_create) @@ -315,40 +360,40 @@ class ApiGroupsDelete(APIView): returned then the caller should loop through the response to understand which deletes failed and why. """ + POST_api_groups_delete_schema = openapi.Schema( type=openapi.TYPE_OBJECT, - required=['names'], + required=["names"], properties={ - 'names': openapi.Schema( + "names": openapi.Schema( type=openapi.TYPE_ARRAY, - description='List of groups to delete.', - items=openapi.Schema( - type=openapi.TYPE_STRING - ) + description="List of groups to delete.", + items=openapi.Schema(type=openapi.TYPE_STRING), ), - } + }, ) request_body = openapi.Schema( type=openapi.TYPE_OBJECT, title="Group Deletion Schema", description="Parameters that are supported when trying to delete " - "one or more groups.", - required=['POST_api_groups_delete'], - properties={ - 'POST_api_groups_delete': POST_api_groups_delete_schema - } - ) - - @swagger_auto_schema(request_body=request_body, responses={ - 200: "Group deletion is successful.", - 300: "Mixture of successes and failures in a bulk delete.", - 400: "Bad request.", - 403: "Invalid token.", - 404: "Missing optional bulk parameters, this request has no effect.", - 418: "More than the expected one group was deleted." - }, tags=["Group Management"]) - + "one or more groups.", + required=["POST_api_groups_delete"], + properties={"POST_api_groups_delete": POST_api_groups_delete_schema}, + ) + + @swagger_auto_schema( + request_body=request_body, + responses={ + 200: "Group deletion is successful.", + 300: "Mixture of successes and failures in a bulk delete.", + 400: "Bad request.", + 403: "Invalid token.", + 404: "Missing optional bulk parameters, this request has no effect.", + 418: "More than the expected one group was deleted.", + }, + tags=["Group Management"], + ) def post(self, request): return check_post_and_process(request, post_api_groups_delete) @@ -377,51 +422,72 @@ class ApiGroupsModify(APIView): POST_api_groups_modify_schema = openapi.Schema( type=openapi.TYPE_OBJECT, - required=['name'], + required=["name"], properties={ - 'name': openapi.Schema(type=openapi.TYPE_STRING, - description='The name of the group to modify'), - 'actions': openapi.Schema(type=openapi.TYPE_OBJECT, + "name": openapi.Schema( + type=openapi.TYPE_STRING, description="The name of the group to modify" + ), + "actions": openapi.Schema( + type=openapi.TYPE_OBJECT, properties={ - 'rename': openapi.Schema(type=openapi.TYPE_STRING, - description=""), - 'redescribe': openapi.Schema(type=openapi.TYPE_STRING, - description="Change the description of the group to this."), - 'owner_user': openapi.Schema(type=openapi.TYPE_STRING, - description="Change the owner of the group to this user."), - 'remove_users': openapi.Schema(type=openapi.TYPE_ARRAY, + "rename": openapi.Schema(type=openapi.TYPE_STRING, description=""), + "redescribe": openapi.Schema( + type=openapi.TYPE_STRING, + description="Change the description of the group to this.", + ), + "owner_user": openapi.Schema( + type=openapi.TYPE_STRING, + description="Change the owner of the group to this user.", + ), + "remove_users": openapi.Schema( + type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING), - description="Users to remove from the group."), - 'disinherit_from': openapi.Schema(type=openapi.TYPE_ARRAY, + description="Users to remove from the group.", + ), + "disinherit_from": openapi.Schema( + type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING), - description="Groups to disinherit permissions from."), - 'add_users': openapi.Schema(type=openapi.TYPE_ARRAY, + description="Groups to disinherit permissions from.", + ), + "add_users": openapi.Schema( + type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING), - description="Users to add to the group."), - 'inherit_from' : openapi.Schema(type=openapi.TYPE_ARRAY, + description="Users to add to the group.", + ), + "inherit_from": openapi.Schema( + type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING), - description="Groups to inherit permissions from."), - }, - description="Actions to take upon the group.") - } - ) + description="Groups to inherit permissions from.", + ), + }, + description="Actions to take upon the group.", + ), + }, + ) request_body = openapi.Schema( - type=openapi.TYPE_OBJECT, - title="Group Modification Schema", - description="Parameters that are supported when trying to modify one or more groups.", - required=['POST_api_groups_modify'], - properties={ - 'POST_api_groups_modify': openapi.Schema(type=openapi.TYPE_ARRAY, - items=POST_api_groups_modify_schema, - description='Groups and actions to take on them.'), - }) - - @swagger_auto_schema(request_body=request_body, responses={ + type=openapi.TYPE_OBJECT, + title="Group Modification Schema", + description="Parameters that are supported when trying to modify one or more groups.", + required=["POST_api_groups_modify"], + properties={ + "POST_api_groups_modify": openapi.Schema( + type=openapi.TYPE_ARRAY, + items=POST_api_groups_modify_schema, + description="Groups and actions to take on them.", + ), + }, + ) + + @swagger_auto_schema( + request_body=request_body, + responses={ 200: "Group modification is successful.", 400: "Bad request.", - 403: "Insufficient privileges." - }, tags=["Group Management"]) + 403: "Insufficient privileges.", + }, + tags=["Group Management"], + ) def post(self, request): return check_post_and_process(request, post_api_groups_modify) @@ -447,30 +513,42 @@ class ApiAccountsNew(APIView): } ``` """ - + # Anyone can ask for a new account authentication_classes = [] permission_classes = [] request_body = openapi.Schema( - type=openapi.TYPE_OBJECT, - title="Account Creation Schema", - description="Account creation schema description.", - required=['hostname', 'email', 'token'], - properties={ - 'hostname': openapi.Schema(type=openapi.TYPE_STRING, description='Hostname of the User Database.'), - 'email' : openapi.Schema(type=openapi.TYPE_STRING, description='Email address of user.'), - 'token' : openapi.Schema(type=openapi.TYPE_STRING, description='Token returned with new user being ' - 'generated in the User Database.'), - }) - - @swagger_auto_schema(request_body=request_body, responses={ + type=openapi.TYPE_OBJECT, + title="Account Creation Schema", + description="Account creation schema description.", + required=["hostname", "email", "token"], + properties={ + "hostname": openapi.Schema( + type=openapi.TYPE_STRING, description="Hostname of the User Database." + ), + "email": openapi.Schema( + type=openapi.TYPE_STRING, description="Email address of user." + ), + "token": openapi.Schema( + type=openapi.TYPE_STRING, + description="Token returned with new user being " + "generated in the User Database.", + ), + }, + ) + + @swagger_auto_schema( + request_body=request_body, + responses={ 200: "Account creation is successful.", 400: "Bad request.", 403: "Invalid token.", 409: "Account has already been authenticated or requested.", - 500: "Unable to save the new account or send authentication email." - }, tags=["Account Management"]) + 500: "Unable to save the new account or send authentication email.", + }, + tags=["Account Management"], + ) def post(self, request) -> Response: print("Request: {}".format(request)) return check_post_and_process(request, POST_api_accounts_new) @@ -486,38 +564,53 @@ class ApiObjectsDraftsCreate(APIView): """ POST_api_objects_draft_create_schema = openapi.Schema( - type=openapi.TYPE_OBJECT, - required=['prefix', 'owner_group', 'schema', 'contents'], - properties={ - 'prefix' : openapi.Schema(type=openapi.TYPE_STRING, - description='BCO Prefix to use'), - 'owner_group': openapi.Schema(type=openapi.TYPE_STRING, - description='Group which owns the BCO draft.'), - 'object_id' : openapi.Schema(type=openapi.TYPE_STRING, - description='BCO Object ID.'), - 'schema' : openapi.Schema(type=openapi.TYPE_STRING, - description='Which schema the BCO satisfies.'), - 'contents' : openapi.Schema(type=openapi.TYPE_OBJECT, additional_properties=True, description="Contents of the BCO.") - } - ) + type=openapi.TYPE_OBJECT, + required=["prefix", "owner_group", "schema", "contents"], + properties={ + "prefix": openapi.Schema( + type=openapi.TYPE_STRING, description="BCO Prefix to use" + ), + "owner_group": openapi.Schema( + type=openapi.TYPE_STRING, description="Group which owns the BCO draft." + ), + "object_id": openapi.Schema( + type=openapi.TYPE_STRING, description="BCO Object ID." + ), + "schema": openapi.Schema( + type=openapi.TYPE_STRING, description="Which schema the BCO satisfies." + ), + "contents": openapi.Schema( + type=openapi.TYPE_OBJECT, + additional_properties=True, + description="Contents of the BCO.", + ), + }, + ) request_body = openapi.Schema( - type=openapi.TYPE_OBJECT, - title="Create BCO Draft Schema", - description="Parameters that are supported when trying to create a draft BCO.", - required=['POST_api_objects_draft_create'], - properties={ - 'POST_api_objects_draft_create': openapi.Schema(type=openapi.TYPE_ARRAY, - items=POST_api_objects_draft_create_schema, - description='BCO Drafts to create.'), - }) - - @swagger_auto_schema(request_body=request_body, responses={ + type=openapi.TYPE_OBJECT, + title="Create BCO Draft Schema", + description="Parameters that are supported when trying to create a draft BCO.", + required=["POST_api_objects_draft_create"], + properties={ + "POST_api_objects_draft_create": openapi.Schema( + type=openapi.TYPE_ARRAY, + items=POST_api_objects_draft_create_schema, + description="BCO Drafts to create.", + ), + }, + ) + + @swagger_auto_schema( + request_body=request_body, + responses={ 200: "Creation of BCO draft is successful.", 300: "Some requests failed and some succeeded.", 400: "Bad request.", - 403: "Invalid token." - }, tags=["BCO Management"]) + 403: "Invalid token.", + }, + tags=["BCO Management"], + ) def post(self, request) -> Response: return check_post_and_process(request, post_api_objects_drafts_create) @@ -533,30 +626,43 @@ class ApiObjectsDraftsModify(APIView): """ POST_api_objects_drafts_modify_schema = openapi.Schema( - type=openapi.TYPE_OBJECT, - required=['object_id', 'contents'], - properties={ - 'object_id': openapi.Schema(type=openapi.TYPE_STRING, description='BCO Object ID.'), - 'contents' : openapi.Schema(type=openapi.TYPE_OBJECT, additional_properties=True, description="Contents of the BCO."), - } - ) + type=openapi.TYPE_OBJECT, + required=["object_id", "contents"], + properties={ + "object_id": openapi.Schema( + type=openapi.TYPE_STRING, description="BCO Object ID." + ), + "contents": openapi.Schema( + type=openapi.TYPE_OBJECT, + additional_properties=True, + description="Contents of the BCO.", + ), + }, + ) request_body = openapi.Schema( - type=openapi.TYPE_OBJECT, - title="Modify BCO Draft Schema", - description="Parameters that are supported when trying to modify a draft BCO.", - required=['POST_api_objects_drafts_modify'], - properties={ - 'POST_api_objects_drafts_modify': openapi.Schema(type=openapi.TYPE_ARRAY, - items=POST_api_objects_drafts_modify_schema, - description='BCO Drafts to modify.'), - }) - - @swagger_auto_schema(request_body=request_body, responses={ + type=openapi.TYPE_OBJECT, + title="Modify BCO Draft Schema", + description="Parameters that are supported when trying to modify a draft BCO.", + required=["POST_api_objects_drafts_modify"], + properties={ + "POST_api_objects_drafts_modify": openapi.Schema( + type=openapi.TYPE_ARRAY, + items=POST_api_objects_drafts_modify_schema, + description="BCO Drafts to modify.", + ), + }, + ) + + @swagger_auto_schema( + request_body=request_body, + responses={ 200: "Modification of BCO draft is successful.", 400: "Bad request.", - 403: "Invalid token." - }, tags=["BCO Management"]) + 403: "Invalid token.", + }, + tags=["BCO Management"], + ) def post(self, request) -> Response: return check_post_and_process(request, post_api_objects_drafts_modify) @@ -571,31 +677,44 @@ class ApiObjectsDraftsPermissions(APIView): """ POST_api_objects_drafts_permissions_schema = openapi.Schema( - type=openapi.TYPE_OBJECT, - required=['object_id', 'contents'], - properties={ - 'object_id': openapi.Schema(type=openapi.TYPE_STRING, description='BCO Object ID.'), - 'contents' : openapi.Schema(type=openapi.TYPE_OBJECT, additional_properties=True, description="Contents of the BCO."), - } - ) + type=openapi.TYPE_OBJECT, + required=["object_id", "contents"], + properties={ + "object_id": openapi.Schema( + type=openapi.TYPE_STRING, description="BCO Object ID." + ), + "contents": openapi.Schema( + type=openapi.TYPE_OBJECT, + additional_properties=True, + description="Contents of the BCO.", + ), + }, + ) request_body = openapi.Schema( - type=openapi.TYPE_OBJECT, - title="Get BCO Permissions Schema", - description="Parameters that are supported when fetching draft BCO permissions.", - required=['POST_api_objects_drafts_permissions'], - properties={ - 'POST_api_objects_drafts_permissions': openapi.Schema(type=openapi.TYPE_ARRAY, - items=POST_api_objects_drafts_permissions_schema, - description='BCO Drafts to fetch permissions for.'), - }) - - @swagger_auto_schema(request_body=request_body, responses={ + type=openapi.TYPE_OBJECT, + title="Get BCO Permissions Schema", + description="Parameters that are supported when fetching draft BCO permissions.", + required=["POST_api_objects_drafts_permissions"], + properties={ + "POST_api_objects_drafts_permissions": openapi.Schema( + type=openapi.TYPE_ARRAY, + items=POST_api_objects_drafts_permissions_schema, + description="BCO Drafts to fetch permissions for.", + ), + }, + ) + + @swagger_auto_schema( + request_body=request_body, + responses={ 200: "Checking BCO permissions is successful.", 300: "Some requests failed.", 400: "Bad request.", - 403: "Invalid token." - }, tags=["BCO Management"]) + 403: "Invalid token.", + }, + tags=["BCO Management"], + ) def post(self, request) -> Response: return check_post_and_process(request, POST_api_objects_drafts_permissions) @@ -613,41 +732,57 @@ class ApiObjectsDraftsPermissionsSet(APIView): # TODO: The POST_api_objects_draft_permissions_set call needs to be fixed, doesn't appear to work POST_api_objects_drafts_permissions_set_schema = openapi.Schema( - type=openapi.TYPE_OBJECT, - required=['object_id'], - properties={ - 'object_id': openapi.Schema(type=openapi.TYPE_STRING, description='BCO Object ID.'), - 'actions' : openapi.Schema( - type=openapi.TYPE_OBJECT, - properties={ - 'remove_permissions': openapi.Schema(type=openapi.TYPE_STRING, - description="Remove permissions from these users."), - 'full_permissions' : openapi.Schema(type=openapi.TYPE_STRING, - description="Give users full permissions."), - 'add_permissions' : openapi.Schema(type=openapi.TYPE_STRING, - description="Add permissions to these users."), - }, - description="Actions to modify BCO permissions.") - } - ) + type=openapi.TYPE_OBJECT, + required=["object_id"], + properties={ + "object_id": openapi.Schema( + type=openapi.TYPE_STRING, description="BCO Object ID." + ), + "actions": openapi.Schema( + type=openapi.TYPE_OBJECT, + properties={ + "remove_permissions": openapi.Schema( + type=openapi.TYPE_STRING, + description="Remove permissions from these users.", + ), + "full_permissions": openapi.Schema( + type=openapi.TYPE_STRING, + description="Give users full permissions.", + ), + "add_permissions": openapi.Schema( + type=openapi.TYPE_STRING, + description="Add permissions to these users.", + ), + }, + description="Actions to modify BCO permissions.", + ), + }, + ) request_body = openapi.Schema( - type=openapi.TYPE_OBJECT, - title="Set BCO Permissions Schema", - description="Parameters that are supported when setting draft BCO permissions.", - required=['POST_api_objects_drafts_permissions_set'], - properties={ - 'POST_api_objects_drafts_permissions_set': openapi.Schema(type=openapi.TYPE_ARRAY, - items=POST_api_objects_drafts_permissions_set_schema, - description='BCO Drafts to set permissions for.'), - }) - - @swagger_auto_schema(request_body=request_body, responses={ + type=openapi.TYPE_OBJECT, + title="Set BCO Permissions Schema", + description="Parameters that are supported when setting draft BCO permissions.", + required=["POST_api_objects_drafts_permissions_set"], + properties={ + "POST_api_objects_drafts_permissions_set": openapi.Schema( + type=openapi.TYPE_ARRAY, + items=POST_api_objects_drafts_permissions_set_schema, + description="BCO Drafts to set permissions for.", + ), + }, + ) + + @swagger_auto_schema( + request_body=request_body, + responses={ 200: "Setting BCO permissions is successful.", 300: "Some requests failed.", 400: "Bad request.", - 403: "Invalid token." - }, tags=["BCO Management"]) + 403: "Invalid token.", + }, + tags=["BCO Management"], + ) def post(self, request) -> Response: return check_post_and_process(request, POST_api_objects_drafts_permissions_set) @@ -667,39 +802,48 @@ class ApiObjectsDraftsPublish(APIView): POST_api_objects_drafts_publish_schema = openapi.Schema( type=openapi.TYPE_OBJECT, - required=['draft_id', 'prefix'], + required=["draft_id", "prefix"], properties={ - 'prefix': openapi.Schema(type=openapi.TYPE_STRING, - description='BCO Prefix to publish with.'), - 'draft_id': openapi.Schema(type=openapi.TYPE_STRING, - description='BCO Object Draft ID.'), - 'object_id': openapi.Schema(type=openapi.TYPE_STRING, - description='BCO Object ID.'), - 'delete_draft': openapi.Schema(type=openapi.TYPE_BOOLEAN, - description='Whether or not to delete the draft.' - ' False by default.'), - } + "prefix": openapi.Schema( + type=openapi.TYPE_STRING, description="BCO Prefix to publish with." + ), + "draft_id": openapi.Schema( + type=openapi.TYPE_STRING, description="BCO Object Draft ID." + ), + "object_id": openapi.Schema( + type=openapi.TYPE_STRING, description="BCO Object ID." + ), + "delete_draft": openapi.Schema( + type=openapi.TYPE_BOOLEAN, + description="Whether or not to delete the draft." " False by default.", + ), + }, ) request_body = openapi.Schema( - type=openapi.TYPE_OBJECT, - title="Publish Draft BCO Schema", - description="Parameters that are supported when setting publishing BCOs.", - required=['POST_api_objects_drafts_publish'], - properties={ - 'POST_api_objects_drafts_publish': openapi.Schema( - type=openapi.TYPE_ARRAY, - items=POST_api_objects_drafts_publish_schema, - description='BCO drafts to publish.') - } - ) + type=openapi.TYPE_OBJECT, + title="Publish Draft BCO Schema", + description="Parameters that are supported when setting publishing BCOs.", + required=["POST_api_objects_drafts_publish"], + properties={ + "POST_api_objects_drafts_publish": openapi.Schema( + type=openapi.TYPE_ARRAY, + items=POST_api_objects_drafts_publish_schema, + description="BCO drafts to publish.", + ) + }, + ) - @swagger_auto_schema(request_body=request_body, responses={ + @swagger_auto_schema( + request_body=request_body, + responses={ 200: "BCO Publication is successful.", 300: "Some requests failed.", 400: "Bad request.", - 403: "Invalid token." - }, tags=["BCO Management"]) + 403: "Invalid token.", + }, + tags=["BCO Management"], + ) def post(self, request) -> Response: return check_post_and_process(request, post_api_objects_drafts_publish) @@ -714,30 +858,39 @@ class ApiObjectsDraftsRead(APIView): """ POST_api_objects_drafts_read_schema = openapi.Schema( - type=openapi.TYPE_OBJECT, - required=['object_id'], - properties={ - 'object_id': openapi.Schema(type=openapi.TYPE_STRING, description='BCO Object ID.'), - } - ) + type=openapi.TYPE_OBJECT, + required=["object_id"], + properties={ + "object_id": openapi.Schema( + type=openapi.TYPE_STRING, description="BCO Object ID." + ), + }, + ) request_body = openapi.Schema( - type=openapi.TYPE_OBJECT, - title="Read BCO Schema", - description="Parameters that are supported when reading BCOs.", - required=['POST_api_objects_drafts_read'], - properties={ - 'POST_api_objects_drafts_read': openapi.Schema(type=openapi.TYPE_ARRAY, - items=POST_api_objects_drafts_read_schema, - description='BCO objects to read.'), - }) - - @swagger_auto_schema(request_body=request_body, responses={ + type=openapi.TYPE_OBJECT, + title="Read BCO Schema", + description="Parameters that are supported when reading BCOs.", + required=["POST_api_objects_drafts_read"], + properties={ + "POST_api_objects_drafts_read": openapi.Schema( + type=openapi.TYPE_ARRAY, + items=POST_api_objects_drafts_read_schema, + description="BCO objects to read.", + ), + }, + ) + + @swagger_auto_schema( + request_body=request_body, + responses={ 200: "Read BCO is successful.", 300: "Some requests failed.", 400: "Bad request.", - 403: "Invalid token." - }, tags=["BCO Management"]) + 403: "Invalid token.", + }, + tags=["BCO Management"], + ) def post(self, request) -> Response: return check_post_and_process(request, POST_api_objects_drafts_read) @@ -755,40 +908,45 @@ class ApiObjectsDraftsToken(APIView): type=openapi.TYPE_OBJECT, title="Get Draft BCO Schema", description="Parameters that are supported when fetching a draft BCO.", - required=['POST_api_objects_drafts_token'], + required=["POST_api_objects_drafts_token"], properties={ - 'POST_api_objects_drafts_token': openapi.Schema( + "POST_api_objects_drafts_token": openapi.Schema( type=openapi.TYPE_OBJECT, - required=['fields'], + required=["fields"], properties={ - 'fields': openapi.Schema( + "fields": openapi.Schema( type=openapi.TYPE_ARRAY, items=openapi.Schema( type=openapi.TYPE_STRING, description="Field to return", enum=[ - 'contents', - 'last_update', - 'object_class', - 'object_id', - 'owner_group', - 'owner_user', - 'prefix', - 'schema', - 'state' - ] + "contents", + "last_update", + "object_class", + "object_id", + "owner_group", + "owner_user", + "prefix", + "schema", + "state", + ], ), - description="Fields to return.") - } - ) - } - ) + description="Fields to return.", + ) + }, + ) + }, + ) - @swagger_auto_schema(request_body=request_body, responses={ + @swagger_auto_schema( + request_body=request_body, + responses={ 200: "Fetch BCO drafts is successful.", 400: "Bad request.", - 403: "Invalid token." - }, tags=["BCO Management"]) + 403: "Invalid token.", + }, + tags=["BCO Management"], + ) def post(self, request) -> Response: # TODO: Not checking for authorization here? # No schema for this request since only @@ -803,23 +961,53 @@ class ApiObjectsPublish(APIView): Take the bulk request and publish objects directly. """ - # TODO: Need to get the schema that is being sent here from FE + POST_api_objects_publish_schema = openapi.Schema( + type=openapi.TYPE_OBJECT, + required=["prefix", "owner_group", "schema", "contents"], + properties={ + "prefix": openapi.Schema( + type=openapi.TYPE_STRING, description="BCO Prefix to use" + ), + "owner_group": openapi.Schema( + type=openapi.TYPE_STRING, description="Group which owns the BCO." + ), + "object_id": openapi.Schema( + type=openapi.TYPE_STRING, description="BCO Object ID." + ), + "schema": openapi.Schema( + type=openapi.TYPE_STRING, description="Which schema the BCO satisfies." + ), + "contents": openapi.Schema( + type=openapi.TYPE_OBJECT, + description="Contents of the BCO.", + ), + }, + ) + request_body = openapi.Schema( - type=openapi.TYPE_OBJECT, - title="BCO Publication Schema", - description="Publish description.", - properties={ - 'x': openapi.Schema(type=openapi.TYPE_STRING, description='Description of X'), - 'y': openapi.Schema(type=openapi.TYPE_STRING, description='Description of Y'), - }) - - @swagger_auto_schema(request_body=request_body, responses={ + type=openapi.TYPE_OBJECT, + title="BCO Publication Schema", + description="Parameters that are supported when trying to create a published BCO.", + properties={ + "POST_api_objects_publish": openapi.Schema( + type=openapi.TYPE_ARRAY, + items=POST_api_objects_publish_schema, + description="BCO Drafts to create.", + ), + }, + ) + + @swagger_auto_schema( + request_body=request_body, + responses={ 200: "BCO publication is successful.", 400: "Bad request.", - 403: "Invalid token." - }, tags=["BCO Management"]) + 403: "Invalid token.", + }, + tags=["BCO Management"], + ) def post(self, request) -> Response: - return check_post_and_process(request, POST_api_objects_publish) + return check_post_and_process(request, post_api_objects_publish) class ApiObjectsSearch(APIView): @@ -835,19 +1023,28 @@ class ApiObjectsSearch(APIView): # permission_classes = [] # TODO: Need to get the schema that is being sent here from FE request_body = openapi.Schema( - type=openapi.TYPE_OBJECT, - title="BCO Publication Schema", - description="Publish description.", - properties={ - 'x': openapi.Schema(type=openapi.TYPE_STRING, description='Description of X'), - 'y': openapi.Schema(type=openapi.TYPE_STRING, description='Description of Y'), - }) - - @swagger_auto_schema(request_body=request_body, responses={ + type=openapi.TYPE_OBJECT, + title="BCO Publication Schema", + description="Publish description.", + properties={ + "x": openapi.Schema( + type=openapi.TYPE_STRING, description="Description of X" + ), + "y": openapi.Schema( + type=openapi.TYPE_STRING, description="Description of Y" + ), + }, + ) + + @swagger_auto_schema( + request_body=request_body, + responses={ 200: "BCO publication is successful.", 400: "Bad request.", - 403: "Invalid token." - }, tags=["BCO Management"]) + 403: "Invalid token.", + }, + tags=["BCO Management"], + ) def post(self, request) -> Response: return check_post_and_process(request, post_api_objects_search) @@ -869,28 +1066,45 @@ class ApiObjectsToken(APIView): type=openapi.TYPE_OBJECT, title="Get BCO Schema", description="Parameters that are supported when fetching a BCOs.", - required=['POST_api_objects_token'], + required=["POST_api_objects_token"], properties={ - 'POST_api_objects_token': openapi.Schema( + "POST_api_objects_token": openapi.Schema( type=openapi.TYPE_OBJECT, - required=['fields'], + required=["fields"], properties={ - 'fields': openapi.Schema( + "fields": openapi.Schema( type=openapi.TYPE_ARRAY, items=openapi.Schema( type=openapi.TYPE_STRING, description="Field to return", - enum=['contents', 'last_update', 'object_class', 'object_id', 'owner_group', 'owner_user', - 'prefix', 'schema', 'state'] + enum=[ + "contents", + "last_update", + "object_class", + "object_id", + "owner_group", + "owner_user", + "prefix", + "schema", + "state", + ], ), - description="Fields to return.") - })}) + description="Fields to return.", + ) + }, + ) + }, + ) - @swagger_auto_schema(request_body=request_body, responses={ + @swagger_auto_schema( + request_body=request_body, + responses={ 200: "Fetch BCOs is successful.", 400: "Bad request.", - 403: "Invalid token." - }, tags=["BCO Management"]) + 403: "Invalid token.", + }, + tags=["BCO Management"], + ) def post(self, request) -> Response: # No schema for this request since only # the Authorization header is required. @@ -906,40 +1120,18 @@ class ApiObjectsPublished(APIView): Get all BCOs available for a specific token, including published ones. """ - # auth = [] - # auth.append( - # openapi.Parameter('Token', openapi.IN_HEADER, description="Authorization Token", type=openapi.TYPE_STRING)) - - # request_body = openapi.Schema( - # type=openapi.TYPE_OBJECT, - # title="Get BCO Schema", - # description="Parameters that are supported when fetching a BCOs.", - # required=['POST_api_objects_token'], - # properties={ - # 'POST_api_objects_token': openapi.Schema( - # type=openapi.TYPE_OBJECT, - # required=['fields'], - # properties={ - # 'fields': openapi.Schema( - # type=openapi.TYPE_ARRAY, - # items=openapi.Schema( - # type=openapi.TYPE_STRING, - # description="Field to return", - # enum=['contents', 'last_update', 'object_class', 'object_id', 'owner_group', 'owner_user', - # 'prefix', 'schema', 'state'] - # ), - # description="Fields to return.") - # })}) - - # Anyone can view a published object authentication_classes = [] permission_classes = [] auth = [] - @swagger_auto_schema(manual_parameters=auth, responses={ - 200: "Success.", - 400: "Internal Error. BCO Name and Version are not properly formatted.", - }, tags=["BCO Management"]) + @swagger_auto_schema( + manual_parameters=auth, + responses={ + 200: "Success.", + 400: "Internal Error. BCO Name and Version are not properly formatted.", + }, + tags=["BCO Management"], + ) def get(self, request) -> Response: return POST_api_objects_published() # return POST_api_objects_token(rqst=request) @@ -986,27 +1178,47 @@ class ApiPrefixesCreate(APIView): # TODO: Need to get the schema that is being sent here from FE request_body = openapi.Schema( - type=openapi.TYPE_OBJECT, - title="Prefix Creation Schema", - description="Several parameters are required to create a prefix.", - required=['owner_group', 'owner_user', 'prefix'], - properties={ - 'description': openapi.Schema(type=openapi.TYPE_STRING, description='A description of what this prefix should represent. For example, the prefix \'GLY\' would be related to BCOs which were derived from GlyGen workflows.'), - 'expiration_date': openapi.Schema(type=openapi.TYPE_STRING, description='The datetime at which this prefix expires in the format YYYY-MM-DD-HH-MM-SS.'), - 'owner_group': openapi.Schema(type=openapi.TYPE_STRING, description='Which group should own the prefix. *The requestor does not have to be in owner_group to assign this.*'), - 'owner_user': openapi.Schema(type=openapi.TYPE_STRING, description='Which user should own the prefix. *The requestor does not have to be owner_user to assign this.*'), - 'prefixes': openapi.Schema(type=openapi.TYPE_ARRAY, description='Any prefix which satsifies the naming standard (see link...)', items=openapi.Items(type=openapi.TYPE_STRING)) - }) - - @swagger_auto_schema(request_body=request_body, responses={ - 201: "The prefix was successfully created.", - 400: "Bad request for one of two reasons: \n1) the prefix does not" - "follow the naming standard, or \n2) owner_user and/or" - "owner_group do not exist.", - 401: "Unauthorized. Authentication credentials were not provided.", - 403: "Forbidden. User doesnot have permission to perform this action", - 409: "The prefix the requestor is attempting to create already exists." - }, tags=["Prefix Management"] + type=openapi.TYPE_OBJECT, + title="Prefix Creation Schema", + description="Several parameters are required to create a prefix.", + required=["owner_group", "owner_user", "prefix"], + properties={ + "description": openapi.Schema( + type=openapi.TYPE_STRING, + description="A description of what this prefix should represent. For example, the prefix 'GLY' would be related to BCOs which were derived from GlyGen workflows.", + ), + "expiration_date": openapi.Schema( + type=openapi.TYPE_STRING, + description="The datetime at which this prefix expires in the format YYYY-MM-DD-HH-MM-SS.", + ), + "owner_group": openapi.Schema( + type=openapi.TYPE_STRING, + description="Which group should own the prefix. *The requestor does not have to be in owner_group to assign this.*", + ), + "owner_user": openapi.Schema( + type=openapi.TYPE_STRING, + description="Which user should own the prefix. *The requestor does not have to be owner_user to assign this.*", + ), + "prefixes": openapi.Schema( + type=openapi.TYPE_ARRAY, + description="Any prefix which satsifies the naming standard (see link...)", + items=openapi.Items(type=openapi.TYPE_STRING), + ), + }, + ) + + @swagger_auto_schema( + request_body=request_body, + responses={ + 201: "The prefix was successfully created.", + 400: "Bad request for one of two reasons: \n1) the prefix does not" + "follow the naming standard, or \n2) owner_user and/or" + "owner_group do not exist.", + 401: "Unauthorized. Authentication credentials were not provided.", + 403: "Forbidden. User doesnot have permission to perform this action", + 409: "The prefix the requestor is attempting to create already exists.", + }, + tags=["Prefix Management"], ) def post(self, request) -> Response: return check_post_and_process(request, post_api_prefixes_create) @@ -1032,7 +1244,7 @@ class ApiPrefixesDelete(APIView): ``` """ - + # Permissions - prefix admins only permission_classes = [RequestorInPrefixAdminsGroup] @@ -1041,21 +1253,25 @@ class ApiPrefixesDelete(APIView): type=openapi.TYPE_OBJECT, title="Prefix Deletion Schema", description="Provide a list of prefixes to delete.", - required=['prefixes'], + required=["prefixes"], properties={ - 'prefixes': openapi.Schema( + "prefixes": openapi.Schema( type=openapi.TYPE_ARRAY, - description='Any prefix in the API.', - items=openapi.Items(type=openapi.TYPE_STRING)), - } + description="Any prefix in the API.", + items=openapi.Items(type=openapi.TYPE_STRING), + ), + }, ) - @swagger_auto_schema(request_body=request_body, responses={ - 200: "Deleting a prefix was successful.", - 401: "Unauthorized. Authentication credentials were not provided.", - 403: "Forbidden. User doesnot have permission to perform this action", - 404: "The prefix couldn't be found so therefore it could not be deleted." - }, tags=["Prefix Management"] + @swagger_auto_schema( + request_body=request_body, + responses={ + 200: "Deleting a prefix was successful.", + 401: "Unauthorized. Authentication credentials were not provided.", + 403: "Forbidden. User doesnot have permission to perform this action", + 404: "The prefix couldn't be found so therefore it could not be deleted.", + }, + tags=["Prefix Management"], ) def post(self, request) -> Response: return check_post_and_process(request, post_api_prefixes_delete) @@ -1102,42 +1318,45 @@ class ApiPrefixesModify(APIView): type=openapi.TYPE_OBJECT, required=[], properties={ - 'description': openapi.Schema( + "description": openapi.Schema( type=openapi.TYPE_STRING, - description='A description of what this prefix should' - ' represent. For example, the prefix \'GLY\' would be ' - 'related to BCOs which were derived from GlyGen workflows.' - ), - 'expiration_date': openapi.Schema( + description="A description of what this prefix should" + " represent. For example, the prefix 'GLY' would be " + "related to BCOs which were derived from GlyGen workflows.", + ), + "expiration_date": openapi.Schema( type=openapi.TYPE_STRING, - description='The datetime at which this prefix expires in the' - ' format YYYY-MM-DD-HH-MM-SS.'), - 'prefix': openapi.Schema( + description="The datetime at which this prefix expires in the" + " format YYYY-MM-DD-HH-MM-SS.", + ), + "prefix": openapi.Schema( type=openapi.TYPE_STRING, - description='Any prefix which satsifies the naming standard') - } - - + description="Any prefix which satsifies the naming standard", + ), + }, ) POST_api_prefixes_modify_schema = openapi.Schema( type=openapi.TYPE_OBJECT, required=[], properties={ - 'owner_group': openapi.Schema( + "owner_group": openapi.Schema( type=openapi.TYPE_STRING, - description='Which group should own the prefix. *The' - ' requestor does not have to be in the owner group to' - ' assign this.*'), - 'owner_user': openapi.Schema( + description="Which group should own the prefix. *The" + " requestor does not have to be in the owner group to" + " assign this.*", + ), + "owner_user": openapi.Schema( type=openapi.TYPE_STRING, - description='Which user should own the prefix. *The requestor' - ' does not have to be owner_user but owner_user must be in' - ' owner_group*.'), - 'prefixes': openapi.Schema( + description="Which user should own the prefix. *The requestor" + " does not have to be owner_user but owner_user must be in" + " owner_group*.", + ), + "prefixes": openapi.Schema( type=openapi.TYPE_ARRAY, items=prefixes_object_schema, - description='Any prefix which satsifies the naming standard') - } + description="Any prefix which satsifies the naming standard", + ), + }, ) # TODO: Need to get the schema that is being sent here from FE @@ -1145,20 +1364,25 @@ class ApiPrefixesModify(APIView): type=openapi.TYPE_OBJECT, title="Prefix Modification Schema", description="Several parameters are required to modify a prefix.", - required=['POST_api_prefixes_modify'], + required=["POST_api_prefixes_modify"], properties={ - 'POST_api_prefixes_modify': openapi.Schema( + "POST_api_prefixes_modify": openapi.Schema( type=openapi.TYPE_ARRAY, items=POST_api_prefixes_modify_schema, - description='' + description="", ) - }) # TODO: ADD LINK FOR PREFIX DOCUMENTATION - - @swagger_auto_schema(request_body=request_body, responses={ + }, + ) # TODO: ADD LINK FOR PREFIX DOCUMENTATION + + @swagger_auto_schema( + request_body=request_body, + responses={ 200: "The prefix was successfully modified.", 400: "Bad request because owner_user and/or owner_group do not exist.", - 404: "The prefix provided could not be found." - }, tags=["Prefix Management"]) + 404: "The prefix provided could not be found.", + }, + tags=["Prefix Management"], + ) def post(self, request) -> Response: return check_post_and_process(request, post_api_prefixes_modify) @@ -1201,29 +1425,48 @@ class ApiPrefixesPermissionsSet(APIView): ``` """ - + # Permissions - prefix admins only permission_classes = [RequestorInPrefixAdminsGroup] # TODO: Need to get the schema that is being sent here from FE request_body = openapi.Schema( - type=openapi.TYPE_OBJECT, - title="Prefix Permissions Schema", - description="Set the permissions for a prefix.", - required=['permissions', 'prefix'], - properties={ - 'group': openapi.Schema(type=openapi.TYPE_STRING, description='Which group the permission is being assigned to.'), - 'mode': openapi.Schema(type=openapi.TYPE_STRING, description='Whether to \'add\' (append), \'remove\' (subtract), or define the \'full_set\' of permissions.'), - 'permissions': openapi.Schema(type=openapi.TYPE_STRING, description='Which permissions to assign.'), - 'prefix': openapi.Schema(type=openapi.TYPE_STRING, description='Which prefix to assign the permissions to.'), - 'username': openapi.Schema(type=openapi.TYPE_STRING, description='Which user the permission is being assigned to.'), - }) - - @swagger_auto_schema(request_body=request_body, responses={ + type=openapi.TYPE_OBJECT, + title="Prefix Permissions Schema", + description="Set the permissions for a prefix.", + required=["permissions", "prefix"], + properties={ + "group": openapi.Schema( + type=openapi.TYPE_STRING, + description="Which group the permission is being assigned to.", + ), + "mode": openapi.Schema( + type=openapi.TYPE_STRING, + description="Whether to 'add' (append), 'remove' (subtract), or define the 'full_set' of permissions.", + ), + "permissions": openapi.Schema( + type=openapi.TYPE_STRING, description="Which permissions to assign." + ), + "prefix": openapi.Schema( + type=openapi.TYPE_STRING, + description="Which prefix to assign the permissions to.", + ), + "username": openapi.Schema( + type=openapi.TYPE_STRING, + description="Which user the permission is being assigned to.", + ), + }, + ) + + @swagger_auto_schema( + request_body=request_body, + responses={ 201: "The prefix permissions were updated succesfully.", 400: "Bad request because 1) the requestor isn't the owner of the prefix, or 2) the provided username and/or group could not be found.", - 404: "The prefix provided was not found." - }, tags=["Prefix Management"]) + 404: "The prefix provided was not found.", + }, + tags=["Prefix Management"], + ) def post(self, request) -> Response: return check_post_and_process(request, post_api_prefixes_permissions_set) @@ -1240,14 +1483,25 @@ class ApiPrefixesToken(APIView): For example: 'Token 627626823549f787c3ec763ff687169206626149'. """ - auth = [openapi.Parameter('Authorization', openapi.IN_HEADER, description="Authorization Token", type=openapi.TYPE_STRING)] + auth = [ + openapi.Parameter( + "Authorization", + openapi.IN_HEADER, + description="Authorization Token", + type=openapi.TYPE_STRING, + ) + ] - @swagger_auto_schema(manual_parameters=auth, responses={ + @swagger_auto_schema( + manual_parameters=auth, + responses={ 200: "The Authorization header was provided and available prefixes were returned.", - 400: "The Authorization header was not provided." - }, tags=["Prefix Management"]) + 400: "The Authorization header was not provided.", + }, + tags=["Prefix Management"], + ) def post(self, request) -> Response: - if 'Authorization' in request.headers: + if "Authorization" in request.headers: # Pass the request to the handling function # Source: https://stackoverflow.com/a/31813810 return POST_api_prefixes_token(request=request) @@ -1267,15 +1521,25 @@ class ApiPrefixesTokenFlat(APIView): For example: 'Token 627626823549f787c3ec763ff687169206626149'. """ - auth = [openapi.Parameter('Authorization', openapi.IN_HEADER, description="Authorization Token", type=openapi.TYPE_STRING)] + auth = [ + openapi.Parameter( + "Authorization", + openapi.IN_HEADER, + description="Authorization Token", + type=openapi.TYPE_STRING, + ) + ] - @swagger_auto_schema(manual_parameters=auth, responses={ + @swagger_auto_schema( + manual_parameters=auth, + responses={ 200: "The Authorization header was provided and available prefixes were returned.", - 401: "The Authorization header was not provided." - }, tags=["Prefix Management"]) - + 401: "The Authorization header was not provided.", + }, + tags=["Prefix Management"], + ) def post(self, request) -> Response: - if 'Authorization' in request.headers: + if "Authorization" in request.headers: # Pass the request to the handling function # Source: https://stackoverflow.com/a/31813810 return post_api_prefixes_token_flat(request=request) @@ -1292,6 +1556,7 @@ class ApiPublicDescribe(APIView): Returns information about the API. """ + authentication_classes = [] permission_classes = [] @@ -1303,15 +1568,19 @@ class ApiPublicDescribe(APIView): auth = [] - @swagger_auto_schema(manual_parameters=auth, responses={ + @swagger_auto_schema( + manual_parameters=auth, + responses={ 201: "Account has been authorized.", 208: "Account has already been authorized.", 403: "Requestor's credentials were rejected.", - 424: "Account has not been registered." - }, tags=["API Information"]) + 424: "Account has not been registered.", + }, + tags=["API Information"], + ) def get(self, request): # Pass the request to the handling function - return Response(UserUtils.UserUtils().get_user_info(username='anon')) + return Response(UserUtils.UserUtils().get_user_info(username="anon")) # Source: https://www.django-rest-framework.org/api-guide/permissions/#setting-the-permission-policy @@ -1332,16 +1601,25 @@ class DraftObjectId(APIView): # template_name = 'api/account_activation_message.html' auth = [] - auth.append(openapi.Parameter('object_id', openapi.IN_PATH, description="Object ID to be viewed.", - type=openapi.TYPE_STRING)) + auth.append( + openapi.Parameter( + "object_id", + openapi.IN_PATH, + description="Object ID to be viewed.", + type=openapi.TYPE_STRING, + ) + ) - @swagger_auto_schema(manual_parameters=auth, responses={ + @swagger_auto_schema( + manual_parameters=auth, + responses={ 201: "Account has been authorized.", 208: "Account has already been authorized.", 403: "Requestor's credentials were rejected.", - 424: "Account has not been registered." - }, tags=["BCO Management"]) - + 424: "Account has not been registered.", + }, + tags=["BCO Management"], + ) def get(self, request, object_id): # No need to check the request (unnecessary for GET as it's checked # by the url parser?). @@ -1350,7 +1628,7 @@ def get(self, request, object_id): # TODO: This is not dealing with the draft_object_id parameter being passed in? # return GET_draft_object_by_id(do_id=request.build_absolute_uri(), rqst=request) - # return GET_draft_object_by_id(do_id=draft_object_id, rqst=request) + # return GET_draft_object_by_id(do_id=draft_object_id, rqst=request) return get_draft_object_by_id(do_id=object_id, request=request) @@ -1373,19 +1651,29 @@ class ObjectIdRootObjectId(APIView): # template_name = 'api/account_activation_message.html' auth = [] - auth.append(openapi.Parameter('object_id_root', openapi.IN_PATH, description="Object ID to be viewed.", - type=openapi.TYPE_STRING)) + auth.append( + openapi.Parameter( + "object_id_root", + openapi.IN_PATH, + description="Object ID to be viewed.", + type=openapi.TYPE_STRING, + ) + ) # Anyone can view a published object authentication_classes = [] permission_classes = [] - @swagger_auto_schema(manual_parameters=auth, responses={ + @swagger_auto_schema( + manual_parameters=auth, + responses={ 201: "Account has been authorized.", 208: "Account has already been authorized.", 403: "Requestor's credentials were rejected.", - 424: "Account has not been registered." - }, tags=["BCO Management"]) + 424: "Account has not been registered.", + }, + tags=["BCO Management"], + ) def get(self, request, object_id_root): return GET_published_object_by_id(object_id_root) @@ -1409,20 +1697,87 @@ class ObjectIdRootObjectIdVersion(APIView): # template_name = 'api/account_activation_message.html' auth = [] - auth.append(openapi.Parameter('object_id_root', openapi.IN_PATH, description="Object ID to be viewed.", - type=openapi.TYPE_STRING)) - auth.append(openapi.Parameter('object_id_version', openapi.IN_PATH, description="Object version to be viewed.", - type=openapi.TYPE_STRING)) + auth.append( + openapi.Parameter( + "object_id_root", + openapi.IN_PATH, + description="Object ID to be viewed.", + type=openapi.TYPE_STRING, + ) + ) + auth.append( + openapi.Parameter( + "object_id_version", + openapi.IN_PATH, + description="Object version to be viewed.", + type=openapi.TYPE_STRING, + ) + ) # Anyone can view a published object authentication_classes = [] permission_classes = [] - @swagger_auto_schema(manual_parameters=auth, responses={ + @swagger_auto_schema( + manual_parameters=auth, + responses={ 201: "Account has been authorized.", 208: "Account has already been authorized.", 403: "Requestor's credentials were rejected.", - 424: "Account has not been registered." - }, tags=["BCO Management"]) + 424: "Account has not been registered.", + }, + tags=["BCO Management"], + ) def get(self, request, object_id_root, object_id_version): - return GET_published_object_by_id_with_version(object_id_root, object_id_version) + return GET_published_object_by_id_with_version( + object_id_root, object_id_version + ) + + +class ValidateBCO(APIView): + """ + Bulk Validate BCOs + + -------------------- + + Bulk operation to validate BCOs. + + ```JSON + { + "POST_validate_bco": [ + {...}, + {...} + ] + } + + """ + + authentication_classes = [] + permission_classes = [] + + request_body = openapi.Schema( + type=openapi.TYPE_OBJECT, + title="Validate BCO", + description="Bulk request for validating a BCO", + required=["BCO"], + properties={ + "POST_validate_bco": openapi.Schema( + type=openapi.TYPE_ARRAY, + description="A BCO to validate", + items=openapi.Items(type=openapi.TYPE_OBJECT), + ) + }, + ) + + @swagger_auto_schema( + request_body=request_body, + responses={ + 201: "Account has been authorized.", + 208: "Account has already been authorized.", + 403: "Requestor's credentials were rejected.", + 424: "Account has not been registered.", + }, + tags=["BCO Management"], + ) + def post(self, request) -> Response: + return check_post_and_process(request, post_validate_bco) diff --git a/bco_api/bco_api/asgi.py b/bco_api/bco_api/asgi.py index efbbcf9c..ee832654 100755 --- a/bco_api/bco_api/asgi.py +++ b/bco_api/bco_api/asgi.py @@ -11,6 +11,6 @@ from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'bco_api.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bco_api.settings") application = get_asgi_application() diff --git a/bco_api/bco_api/settings.py b/bco_api/bco_api/settings.py index 09ef94d4..671f6050 100755 --- a/bco_api/bco_api/settings.py +++ b/bco_api/bco_api/settings.py @@ -8,8 +8,10 @@ """ import os + # For importing configuration files import configparser + # For importing schema from api.scripts.utilities import SettingsUtils @@ -19,76 +21,81 @@ # --- SECURITY SETTINGS --- # # Load the server config file. server_config = configparser.ConfigParser() -server_config.read('./server.conf') +server_config.read(BASE_DIR + "/server.conf") # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ # Is this a production server? -PRODUCTION = server_config['PRODUCTION']['production'] +PRODUCTION = server_config["PRODUCTION"]["production"] # Set the anonymous user's key. -ANON_KEY = server_config['KEYS']['anon'] +ANON_KEY = server_config["KEYS"]["anon"] # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '$vz@#@^q(od&$rf&*6^z!m5nh6qw2*cq*j6fha#^h9(r7$xqy4' +SECRET_KEY = "$vz@#@^q(od&$rf&*6^z!m5nh6qw2*cq*j6fha#^h9(r7$xqy4" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = PRODUCTION # The human-readable hostname. -HUMAN_READABLE_HOSTNAME = server_config['HRHOSTNAME']['hrnames'] +HUMAN_READABLE_HOSTNAME = server_config["HRHOSTNAME"]["hrnames"] # The publicly accessible hostname. -if server_config['PRODUCTION']['production'] == 'True': - PUBLIC_HOSTNAME = server_config['PUBLICHOSTNAME']['prod_name'] -elif server_config['PRODUCTION']['production'] == 'False': - PUBLIC_HOSTNAME = server_config['PUBLICHOSTNAME']['name'] +if server_config["PRODUCTION"]["production"] == "True": + PUBLIC_HOSTNAME = server_config["PUBLICHOSTNAME"]["prod_name"] +elif server_config["PRODUCTION"]["production"] == "False": + PUBLIC_HOSTNAME = server_config["PUBLICHOSTNAME"]["name"] # Source: https://dzone.com/articles/how-to-fix-django-cors-error # Check for open (public) access to the API. -if server_config['REQUESTS_FROM']['public'].strip() == 'false': +if server_config["REQUESTS_FROM"]["public"].strip() == "false": # Process the requester groups. # configparser automatically strips white space off the # ends of arguments. - requesters = [server_config['REQUESTS_FROM'][i].strip() for i in server_config['REQUESTS_FROM']] - requesters.remove('false') - requesters = [i.split(',') for i in requesters] + requesters = [ + server_config["REQUESTS_FROM"][i].strip() + for i in server_config["REQUESTS_FROM"] + ] + requesters.remove("false") + requesters = [i.split(",") for i in requesters] # Flatten the list. # Source: https://stackabuse.com/python-how-to-flatten-list-of-lists/ flattened = [item.strip() for sublist in requesters for item in sublist] - if server_config['PRODUCTION']['production'] == 'True': - ALLOWED_HOSTS = [i.strip() for i in server_config['HOSTNAMES']['prod_names'].split(',')] - elif server_config['PRODUCTION']['production'] == 'False': - ALLOWED_HOSTS = [i.strip() for i in server_config['HOSTNAMES']['names'].split(',')] + if server_config["PRODUCTION"]["production"] == "True": + ALLOWED_HOSTS = [ + i.strip() for i in server_config["HOSTNAMES"]["prod_names"].split(",") + ] + elif server_config["PRODUCTION"]["production"] == "False": + ALLOWED_HOSTS = [ + i.strip() for i in server_config["HOSTNAMES"]["names"].split(",") + ] CORS_ORIGIN_ALLOW_ALL = False CORS_ORIGIN_WHITELIST = tuple(flattened) -elif server_config['REQUESTS_FROM']['public'].strip() == 'true': - if server_config['PRODUCTION']['production'] == 'True': - ALLOWED_HOSTS = [server_config['HOSTNAMES']['prod_names'].split(',')[0],'*'] +elif server_config["REQUESTS_FROM"]["public"].strip() == "true": + if server_config["PRODUCTION"]["production"] == "True": + ALLOWED_HOSTS = [server_config["HOSTNAMES"]["prod_names"].split(",")[0], "*"] CORS_ORIGIN_ALLOW_ALL = True - elif server_config['PRODUCTION']['production'] == 'False': - ALLOWED_HOSTS = [server_config['HOSTNAMES']['names'].split(',')[0],'*'] + elif server_config["PRODUCTION"]["production"] == "False": + ALLOWED_HOSTS = [server_config["HOSTNAMES"]["names"].split(",")[0], "*"] CORS_ORIGIN_ALLOW_ALL = True # Use the REST framework # Source: https://www.django-rest-framework.org/api-guide/authentication/#setting-the-authentication-scheme # Source: https://www.django-rest-framework.org/api-guide/permissions/#setting-the-permission-policy REST_FRAMEWORK = { - 'DEFAULT_AUTHENTICATION_CLASSES': [ - 'rest_framework.authentication.TokenAuthentication' - ], - 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.IsAuthenticated' + "DEFAULT_AUTHENTICATION_CLASSES": [ + "rest_framework.authentication.TokenAuthentication" ], - 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema' + "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"], + "DEFAULT_SCHEMA_CLASS": "rest_framework.schemas.coreapi.AutoSchema", } # Password validation @@ -96,24 +103,24 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] # Object-level permissions with django-guardian # Source: https://github.com/django-guardian/django-guardian#configuration AUTHENTICATION_BACKENDS = [ - 'django.contrib.auth.backends.ModelBackend', - 'guardian.backends.ObjectPermissionBackend', + "django.contrib.auth.backends.ModelBackend", + "guardian.backends.ObjectPermissionBackend", ] # --- APPLICATION --- # @@ -122,84 +129,78 @@ # Token-based authentication. # Source: https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.admindocs', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'rest_framework', - 'rest_framework.authtoken', - 'drf_yasg', - 'rest_framework_swagger', - 'api', - 'reset_migrations', - 'guardian' + "django.contrib.admin", + "django.contrib.admindocs", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "rest_framework", + "rest_framework.authtoken", + "drf_yasg", + "rest_framework_swagger", + "api", + "reset_migrations", + "guardian", ] # Source: https://dzone.com/articles/how-to-fix-django-cors-error MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'corsheaders.middleware.CorsMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "corsheaders.middleware.CorsMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'bco_api.urls' +ROOT_URLCONF = "bco_api.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] SWAGGER_SETTINGS = { - 'SECURITY_DEFINITIONS': { - 'Bearer': { - 'type': 'apiKey', - 'name': 'Authorization', - 'in': 'header' - } + "SECURITY_DEFINITIONS": { + "Bearer": {"type": "apiKey", "name": "Authorization", "in": "header"} } } -REDOC_SETTINGS = { - 'LAZY_RENDERING': False -} +REDOC_SETTINGS = {"LAZY_RENDERING": False} -WSGI_APPLICATION = 'bco_api.wsgi.application' +WSGI_APPLICATION = "bco_api.wsgi.application" # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -211,53 +212,56 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ -STATIC_URL = '/api/static/' +STATIC_URL = "/api/static/" # STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] -STATIC_ROOT = '/var/www/bcoeditor/bco_api/bco_api/static/' +STATIC_ROOT = "/var/www/bcoeditor/bco_api/bco_api/static/" # ----- CUSTOM VARIABLES AND METHODS ----- # # Load request and validation templates (definitions). # Note that we will get TWO loads of settings.py if we start without runserver --noreload # There is only set of definitions for requests, but for validations, we may have sub-folders. # First, the request definitions. -REQUEST_TEMPLATES = SettingsUtils.SettingsUtils().load_schema_local(search_parameters={ - 'request_definitions/': '.schema' -}, mode = 'requests') + +REQUEST_TEMPLATES = SettingsUtils.SettingsUtils().load_schema_local( + search_parameters={"request_definitions/": ".schema"}, mode="requests" +) # Define the schema for each request type. -REQUEST_TEMPLATES = SettingsUtils.SettingsUtils().define_request_schema(schema=REQUEST_TEMPLATES['request_definitions/']) +REQUEST_TEMPLATES = SettingsUtils.SettingsUtils().define_request_schema( + schema=REQUEST_TEMPLATES["request_definitions/"] +) # The validation situation is more complex. # First, we need to get all of the folders under validation_definitions. -VALIDATION_TEMPLATES = SettingsUtils.SettingsUtils().load_schema_local(search_parameters={ - 'validation_definitions/': '.schema' -}, mode = 'validations') +VALIDATION_TEMPLATES = SettingsUtils.SettingsUtils().load_schema_local( + search_parameters={"validation_definitions/": ".schema"}, mode="validations" +) # Make the object naming accessible as a dictionary. OBJECT_NAMING = {} -if server_config['PRODUCTION']['production'] == 'True': +if server_config["PRODUCTION"]["production"] == "True": - for i in server_config['OBJECT_NAMING']: - if i.split('_')[0] == 'prod': + for i in server_config["OBJECT_NAMING"]: + if i.split("_")[0] == "prod": # Strip out the production flag. - STRIPPED = '_'.join(i.split('_')[1:]) + STRIPPED = "_".join(i.split("_")[1:]) - OBJECT_NAMING[STRIPPED] = server_config['OBJECT_NAMING'][i] + OBJECT_NAMING[STRIPPED] = server_config["OBJECT_NAMING"][i] -elif server_config['PRODUCTION']['production'] == 'False': +elif server_config["PRODUCTION"]["production"] == "False": - for i in server_config['OBJECT_NAMING']: - if i.split('_')[0] != 'prod': - OBJECT_NAMING[i] = server_config['OBJECT_NAMING'][i] + for i in server_config["OBJECT_NAMING"]: + if i.split("_")[0] != "prod": + OBJECT_NAMING[i] = server_config["OBJECT_NAMING"][i] # emailing notifications # e-Mail settings are explained at https://stackoverflow.com/questions/46053085/django-gmail-smtp-error-please-run-connect-first -EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' -EMAIL_HOST = 'localhost' +EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" +EMAIL_HOST = "localhost" EMAIL_PORT = 25 -DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" diff --git a/bco_api/bco_api/urls.py b/bco_api/bco_api/urls.py index d113d877..3588b5ac 100755 --- a/bco_api/bco_api/urls.py +++ b/bco_api/bco_api/urls.py @@ -6,6 +6,6 @@ from django.urls import path, include urlpatterns = [ - path('api/admin/', admin.site.urls), - path('', include('api.urls')), + path("api/admin/", admin.site.urls), + path("", include("api.urls")), ] diff --git a/bco_api/bco_api/wsgi.py b/bco_api/bco_api/wsgi.py index ca368d8e..d9c7f0ec 100755 --- a/bco_api/bco_api/wsgi.py +++ b/bco_api/bco_api/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'bco_api.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bco_api.settings") application = get_wsgi_application() diff --git a/bco_api/manage.py b/bco_api/manage.py index 5dfa8cd9..9df61197 100755 --- a/bco_api/manage.py +++ b/bco_api/manage.py @@ -6,7 +6,7 @@ def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'bco_api.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bco_api.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -18,5 +18,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/bco_api/server.conf b/bco_api/server.conf index 1764fdde..45a7d708 100755 --- a/bco_api/server.conf +++ b/bco_api/server.conf @@ -7,7 +7,7 @@ production=False # DB Version [VERSION] -version=22.01 +version=22.06 # NOTE: Valid values are True or False (note the capitalization). # Is this a publish-only server? diff --git a/requirements.txt b/requirements.txt index b5198bfa..231c5160 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,10 +2,12 @@ appdirs==1.4.3 asgiref==3.3.4 astroid==2.9.3 attrs==20.3.0 +black==22.6.0 CacheControl==0.12.6 certifi==2019.11.28 chardet==3.0.4 charset-normalizer==2.0.7 +click==8.1.3 colorama==0.4.3 contextlib2==0.6.0 coreapi==2.3.3 @@ -39,8 +41,10 @@ lockfile==0.12.2 MarkupSafe==2.0.1 mccabe==0.6.1 msgpack==0.6.2 +mypy-extensions==0.4.3 openapi-codec==1.3.2 packaging==20.3 +pathspec==0.9.0 pep517==0.8.2 platformdirs==2.5.1 progress==1.5 @@ -62,6 +66,7 @@ simplejson==3.17.5 six==1.14.0 sqlparse==0.4.2 toml==0.10.2 +tomli==2.0.1 typing_extensions==4.1.1 uritemplate==3.0.1 urllib3==1.26.5