diff --git a/.env.example b/.env.example index 4a0d2e14e..182a0cc37 100644 --- a/.env.example +++ b/.env.example @@ -11,4 +11,5 @@ ONTOLOGY_DIRECTORY=/limbus/ontologies/ DEBUG=True LC_ALL=C.UTF-8 LANG=C.UTF-8 -DOID_PATH=https://users.aber.ac.uk/keo7/doid.xrdf \ No newline at end of file +## DOID_PATH=https://users.aber.ac.uk/keo7/doid.xrdf +DOID_PATH=/limbus/ontologies/doid.xrdf ## https://www.dropbox.com/s/1v3b69iw7o1nibs/doid.xrdf \ No newline at end of file diff --git a/services/web/__init__.py b/services/web/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/services/web/app/admin/routes/sites.py b/services/web/app/admin/routes/sites.py index b1fd7f556..d6fce3f08 100644 --- a/services/web/app/admin/routes/sites.py +++ b/services/web/app/admin/routes/sites.py @@ -65,6 +65,7 @@ def sites_new_site(): "street_address_one": form.address_line_one.data, "street_address_two": form.address_line_two.data, "city": form.city.data, + "county": form.county.data, "country": form.country.data, "post_code": form.post_code.data, } diff --git a/services/web/app/api/generics.py b/services/web/app/api/generics.py index 2aa071d2e..f22e132f5 100644 --- a/services/web/app/api/generics.py +++ b/services/web/app/api/generics.py @@ -58,7 +58,7 @@ def generic_lock( view_schema: masql.SQLAlchemySchema, tokenuser: UserAccount, ): - existing = Model.query.filter_by(id=id).first() + existing = model.query.filter_by(id=id).first() if not existing: return not_found() @@ -66,11 +66,10 @@ def generic_lock( existing.is_locked = not existing.is_locked existing.editor_id = tokenuser.id - db.session.add(existing) db.session.commit() db.session.flush() - return success_with_content_response(view_schema.dump(existing)) + return success_with_content_response(existing.is_locked) def generic_edit( @@ -128,4 +127,3 @@ def generic_delete( #return success_with_content_response() return success_without_content_response() - diff --git a/services/web/app/api/responses.py b/services/web/app/api/responses.py index 7a8f78fad..9a617034d 100644 --- a/services/web/app/api/responses.py +++ b/services/web/app/api/responses.py @@ -21,12 +21,20 @@ def not_found(): {"ContentType": "application/json"}, ) -def locked(): - return ( - {"success": False, "message": "Data locked"}, - 404, - {"ContentType": "application/json"}, +def sample_assigned_delete_response(): + return( + {"success": False, "message":"Can't delete assigned samples"}, + 400, + {"ContentType":"application/json"} ) + +def in_use_response(entity): + return( + {"success": False, "message":"Has associated " + entity}, + 400, + {"ContentType":"application/json"} + ) + def no_values_response(): return ( {"success": False, "message": "No input data provided"}, @@ -34,6 +42,11 @@ def no_values_response(): {"ContentType": "application/json"}, ) +def locked_response(): + return({"success": False, "message": "Entity is locked"}, + 400, + {"ContentType": "application/json"}) + def invalid_query_response(): return ( diff --git a/services/web/app/decorators.py b/services/web/app/decorators.py index bf7121d6f..8d30b88f8 100644 --- a/services/web/app/decorators.py +++ b/services/web/app/decorators.py @@ -30,6 +30,15 @@ def decorated_function(*args, **kwargs): return decorated_function +def check_if_locked(model): + def inner(f): + def wrapper(*args,**kwargs): + if(model.query.filter_by(id=kwargs["id"]).is_locked): + return abort(401) + return f(*args,**kwargs) + return wrapper + return inner + def requires_access_level(f): @wraps(f) diff --git a/services/web/app/misc/models.py b/services/web/app/misc/models.py index 43bd93d62..d290faba1 100644 --- a/services/web/app/misc/models.py +++ b/services/web/app/misc/models.py @@ -38,6 +38,7 @@ class SiteInformation(Base, RefAuthorMixin): miabis_id = db.Column(db.String(128)) acronym = db.Column(db.String(64)) name = db.Column(db.String(128)) + is_locked = db.Column(db.Boolean,default=False) description = db.Column(db.String(128)) url = db.Column(db.String(128)) address_id = db.Column(db.Integer, db.ForeignKey("address.id"), nullable=False) diff --git a/services/web/app/static/css/global.css b/services/web/app/static/css/global.css index cb2e46f9f..5ffdf49d2 100644 --- a/services/web/app/static/css/global.css +++ b/services/web/app/static/css/global.css @@ -58,6 +58,34 @@ nav img { color: #000; } +.btn-cancel { + background-color: #eb4034; + color: #ffffff; + margin-right:0.5em; +} + +.btn-cancel:hover { + background-color:#a62b23; + color: #ffffff; +} + +.btn-delete{ + background-color: #ffffff; + color: #eb4034; + border: 1px #eb4034 solid; +} + + + +.btn-form{ + background-color: #007bff; + color:white +} +.btn-form:hover{ + background-color: #0062cc; + color: white; +} + #wmd-preview { background-color: #f5f5f5; padding:1em; diff --git a/services/web/app/static/css/select.dataTables.min.css b/services/web/app/static/css/select.dataTables.min.css new file mode 100644 index 000000000..baaf17c7a --- /dev/null +++ b/services/web/app/static/css/select.dataTables.min.css @@ -0,0 +1 @@ +table.dataTable tbody>tr.selected,table.dataTable tbody>tr>.selected{background-color:#b0bed9}table.dataTable.stripe tbody>tr.odd.selected,table.dataTable.stripe tbody>tr.odd>.selected,table.dataTable.display tbody>tr.odd.selected,table.dataTable.display tbody>tr.odd>.selected{background-color:#acbad4}table.dataTable.hover tbody>tr.selected:hover,table.dataTable.hover tbody>tr>.selected:hover,table.dataTable.display tbody>tr.selected:hover,table.dataTable.display tbody>tr>.selected:hover{background-color:#aab7d1}table.dataTable.order-column tbody>tr.selected>.sorting_1,table.dataTable.order-column tbody>tr.selected>.sorting_2,table.dataTable.order-column tbody>tr.selected>.sorting_3,table.dataTable.order-column tbody>tr>.selected,table.dataTable.display tbody>tr.selected>.sorting_1,table.dataTable.display tbody>tr.selected>.sorting_2,table.dataTable.display tbody>tr.selected>.sorting_3,table.dataTable.display tbody>tr>.selected{background-color:#acbad5}table.dataTable.display tbody>tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody>tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody>tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody>tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody>tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody>tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody>tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody>tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody>tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody>tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody>tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody>tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody>tr.odd>.selected,table.dataTable.order-column.stripe tbody>tr.odd>.selected{background-color:#a6b4cd}table.dataTable.display tbody>tr.even>.selected,table.dataTable.order-column.stripe tbody>tr.even>.selected{background-color:#acbad5}table.dataTable.display tbody>tr.selected:hover>.sorting_1,table.dataTable.order-column.hover tbody>tr.selected:hover>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody>tr.selected:hover>.sorting_2,table.dataTable.order-column.hover tbody>tr.selected:hover>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody>tr.selected:hover>.sorting_3,table.dataTable.order-column.hover tbody>tr.selected:hover>.sorting_3{background-color:#a5b2cb}table.dataTable.display tbody>tr:hover>.selected,table.dataTable.display tbody>tr>.selected:hover,table.dataTable.order-column.hover tbody>tr:hover>.selected,table.dataTable.order-column.hover tbody>tr>.selected:hover{background-color:#a2aec7}table.dataTable tbody td.select-checkbox,table.dataTable tbody th.select-checkbox{position:relative}table.dataTable tbody td.select-checkbox:before,table.dataTable tbody td.select-checkbox:after,table.dataTable tbody th.select-checkbox:before,table.dataTable tbody th.select-checkbox:after{display:block;position:absolute;top:1.2em;left:50%;width:12px;height:12px;box-sizing:border-box}table.dataTable tbody td.select-checkbox:before,table.dataTable tbody th.select-checkbox:before{content:" ";margin-top:-6px;margin-left:-6px;border:1px solid black;border-radius:3px}table.dataTable tr.selected td.select-checkbox:after,table.dataTable tr.selected th.select-checkbox:after{content:"✓";font-size:20px;margin-top:-19px;margin-left:-6px;text-align:center;text-shadow:1px 1px #b0bed9,-1px -1px #b0bed9,1px -1px #b0bed9,-1px 1px #b0bed9}table.dataTable.compact tbody td.select-checkbox:before,table.dataTable.compact tbody th.select-checkbox:before{margin-top:-12px}table.dataTable.compact tr.selected td.select-checkbox:after,table.dataTable.compact tr.selected th.select-checkbox:after{margin-top:-16px}div.dataTables_wrapper span.select-info,div.dataTables_wrapper span.select-item{margin-left:.5em}@media screen and (max-width: 640px){div.dataTables_wrapper span.select-info,div.dataTables_wrapper span.select-item{margin-left:0;display:block}} diff --git a/services/web/app/static/css/storage/navbar.css b/services/web/app/static/css/storage/navbar.css index abbe37555..13ea9faf1 100644 --- a/services/web/app/static/css/storage/navbar.css +++ b/services/web/app/static/css/storage/navbar.css @@ -12,7 +12,7 @@ overflow: auto; top: 30px; left: 0; - z-index: 998; + z-index: 997; transition: all 0.3s; } @@ -23,7 +23,7 @@ #sidebar-collapse { float: right; margin: 0; - z-index: 999; + z-index: 998; position: relative; margin-top: 50%; right: -1em; @@ -36,4 +36,29 @@ #sidebar.active #sidebar-collapse { right: -3em; +} + +/* The Modal (background) */ +.modal-delete { + display: none; /* Hidden by default */ + position: fixed; /* Stay in place */ + z-index: 999; /* Sit on top */ + left: 0; + top: 15%; + width: 100%; /* Full width */ + height: 100%; /* Full height */ + overflow: auto; /* Enable scroll if needed */ + /*background-color: #474e5d;*/ + padding-top: 50px; +} + +/* Modal Content/Box */ +.modal-delete-content { + color:black; + background-color: #fefefe; + margin: 5% auto 15% auto; /* 5% from the top, 15% from the bottom and centered */ + border: 2px solid #000; + border-radius: 0; + width: 70%; /* Could be more or less, depending on screen size */ + height: fit-content; } \ No newline at end of file diff --git a/services/web/app/static/js/storage/delete.js b/services/web/app/static/js/storage/delete.js new file mode 100644 index 000000000..98354da21 --- /dev/null +++ b/services/web/app/static/js/storage/delete.js @@ -0,0 +1,26 @@ +/* +Copyright (C) 2020 Keiron O'Shea + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +// Get the modal +var modal = document.getElementById('delete-confirmation'); + +// When the user clicks anywhere outside of the modal, close it +window.onclick = function(event) { + if (event.target === modal) { + modal.style.display = "none"; + } +} \ No newline at end of file diff --git a/services/web/app/static/js/storage/navtree.js b/services/web/app/static/js/storage/navtree.js index ac9cdf4ed..834b7f69e 100644 --- a/services/web/app/static/js/storage/navtree.js +++ b/services/web/app/static/js/storage/navtree.js @@ -15,6 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ + function sap2tree(sap) { var sites = sap["content"]; diff --git a/services/web/app/static/js/storage/panel.js b/services/web/app/static/js/storage/panel.js index aa68b9727..55625b1c8 100644 --- a/services/web/app/static/js/storage/panel.js +++ b/services/web/app/static/js/storage/panel.js @@ -38,7 +38,7 @@ function get_panel_information() { function render_counts(basic_statistics) { $("#site_count").html(basic_statistics["site_count"]); - $("#room_count").html(basic_statistics["site_count"]); + $("#room_count").html(basic_statistics["room_count"]); $("#building_count").html(basic_statistics["building_count"]); $("#cold_storage_count").html(basic_statistics["cold_storage_count"]); diff --git a/services/web/app/static/js/storage/rack/view.js b/services/web/app/static/js/storage/rack/view.js index 5bf0edb89..8c8fc9b67 100644 --- a/services/web/app/static/js/storage/rack/view.js +++ b/services/web/app/static/js/storage/rack/view.js @@ -265,9 +265,12 @@ function render_sample_table(samples) { return sample_type_information["fluid_type"]; } else if (data["sample"]["base_type"] == "Cellular") { - return sample_type_information["cellular_type"] + " > " + sample_type_information["tiss_type"]; + return sample_type_information["cellular_type"]; // + " > " + sample_type_information["tissue_type"]; } - + else if (data["sample"]["base_type"] == "Molecular") { + return sample_type_information["molecular_type"]; // + " > " + sample_type_information["tissue_type"]; + } + } }, diff --git a/services/web/app/storage/api/building.py b/services/web/app/storage/api/building.py index a8c0fc777..54dee6dbe 100644 --- a/services/web/app/storage/api/building.py +++ b/services/web/app/storage/api/building.py @@ -13,14 +13,20 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from flask import request, current_app, jsonify, send_file +from flask import request, current_app, jsonify, send_file,url_for from ...api import api from ...api.responses import * from ...api.filters import generate_base_query_filters, get_filters_and_joins -from ...decorators import token_required +from ...decorators import token_required,check_if_locked from ...webarg_parser import use_args, use_kwargs, parser -from ...database import db, Building, UserAccount +from ...database import db, Building, UserAccount,Room +from ..api.room import func_room_delete + + +import requests +from ...misc import get_internal_api_header + from marshmallow import ValidationError @@ -93,11 +99,53 @@ def storage_lock_building(id: int, tokenuser: UserAccount): building.is_locked = not building.is_locked building.editor_id = tokenuser.id - db.session.add(building) db.session.commit() db.session.flush() - return success_with_content_response(basic_building_schema.dump(building)) + return success_with_content_response(building.is_locked) + + +@api.route("/storage/building/LIMBBUILD-/delete", methods=["PUT"]) +@token_required +def storage_building_delete(id, tokenuser: UserAccount): + existing = Building.query.filter_by(id=id).first() + + if not existing: + return not_found() + + if existing.is_locked: + return locked_response() + + existing.editor_id = tokenuser.id + + code = delete_buildings_func(existing) + + siteID = existing.site_id + + if code == "success": + return success_with_content_response(siteID) + elif code == "cold storage": + return in_use_response(code) + elif code == "locked": + return locked_response() + else: + return no_values_response() + +def delete_buildings_func(record): + attachedRooms = Room.query.filter(Room.building_id == record.id).all() + for rooms in attachedRooms: + code = func_room_delete(rooms) + if code == "cold storage": + return "cold storage" + elif code == "locked" or record.is_locked: + return "locked" + + try: + db.session.delete(record) + db.session.commit() + return "success" + except Exception as err: + return transaction_error_response(err) @api.route("/storage/building/LIMBBUILD-/edit", methods=["PUT"]) @@ -108,6 +156,8 @@ def storage_edit_building(id: int, tokenuser: UserAccount): if not building: return not_found() + if building.is_locked: + return not_allowed() values = request.get_json() diff --git a/services/web/app/storage/api/lts.py b/services/web/app/storage/api/lts.py index 696b28425..fcae41850 100644 --- a/services/web/app/storage/api/lts.py +++ b/services/web/app/storage/api/lts.py @@ -20,6 +20,7 @@ from ...api.filters import generate_base_query_filters, get_filters_and_joins from ...decorators import token_required from ...webarg_parser import use_args, use_kwargs, parser +from ..api.shelf import func_shelf_delete import requests @@ -60,6 +61,42 @@ def storage_coldstorage_edit_view(id, tokenuser: UserAccount): new_cold_storage_schema.dump(ColdStorage.query.filter_by(id=id).first_or_404()) ) + +@api.route("/storage/coldstorage/LIMBCS-/delete", methods=["PUT"]) +@token_required +def storage_coldstorage_delete(id, tokenuser: UserAccount): + existing = ColdStorage.query.filter_by(id=id).first() + + if not existing: + return not_found() + + if existing.is_locked: + return locked_response() + + existing.editor_id = tokenuser.id + roomID = existing.room_id + + code = delete_coldstorage_func(existing) + + if code == "success": + return success_with_content_response(roomID) + elif code == "has sample": + return sample_assigned_delete_response() + else: + return no_values_response() + +def delete_coldstorage_func(record): + attachedShelves = ColdStorageShelf.query.filter(ColdStorageShelf.storage_id==record.id).all() + for shelves in attachedShelves: + if func_shelf_delete(shelves) == "has sample": + return "has sample" + try: + db.session.delete(record) + db.session.commit() + return "success" + except Exception as err: + return transaction_error_response(err) + @api.route("/storage/coldstorage/LIMBCS-/service/new", methods=["POST"]) @token_required def storage_coldstorage_new_service_report(id, tokenuser: UserAccount): @@ -148,7 +185,7 @@ def storage_coldstorage_edit(id, tokenuser: UserAccount): @api.route("/storage/coldstorage/LIMBCS-/lock", methods=["PUT"]) @token_required -def storage_coldstorage_lock(id, tokenuser: UserAccount): +def storage_cold_storage_lock(id, tokenuser: UserAccount): cs = ColdStorage.query.filter_by(id=id).first() @@ -158,11 +195,24 @@ def storage_coldstorage_lock(id, tokenuser: UserAccount): cs.is_locked = not cs.is_locked cs.editor_id = tokenuser.id - db.session.add(cs) - db.session.commit() - db.session.flush() + attachedShelves = ColdStorageShelf.query.filter(ColdStorageShelf.storage_id==cs.id).all() + for shelf in attachedShelves: + shelf.is_locked = cs.is_locked + shelf.editor_id = tokenuser.id + entityStorageRecords = EntityToStorage.query.filter(EntityToStorage.shelf_id==shelf.id).all() + for ES in entityStorageRecords: + rack = SampleRack.query.filter(SampleRack.id==ES.rack_id).first() + rack.is_locked = cs.is_locked + rack.editor_id = tokenuser.id + + try: + db.session.commit() + db.session.flush() + return success_with_content_response(cs.is_locked) + except Exception as err: + return transaction_error_response(err) - return success_with_content_response(basic_cold_storage_schema.dump(cs)) + return success_with_content_response(cs.is_locked) @api.route("/storage/coldstorage/LIMBCS-/associatie/document", methods=["POST"]) @@ -173,8 +223,7 @@ def storage_coldstorage_document(id, tokenuser: UserAccount): if not values: return no_values_response() - coldstorage_response = requests.get( - url_for("api.storage_coldstorage_view", id=id, _external=True), + coldstorage_response = requests.get(url_for("api.storage_coldstorage_view", id=id, _external=True), headers=get_internal_api_header(tokenuser), ) diff --git a/services/web/app/storage/api/misc.py b/services/web/app/storage/api/misc.py index c336d3e05..a594ffcc8 100644 --- a/services/web/app/storage/api/misc.py +++ b/services/web/app/storage/api/misc.py @@ -35,8 +35,6 @@ @api.route("/storage/transfer/rack_to_shelf", methods=["POST"]) @token_required def storage_transfer_rack_to_shelf(tokenuser: UserAccount): - # TODO: Need to check if Rack in table, and if it is - move. - values = request.get_json() if not values: @@ -47,9 +45,9 @@ def storage_transfer_rack_to_shelf(tokenuser: UserAccount): except ValidationError as err: return validation_error_response(err) - ets = EntityToStorage.query.filter_by(rack_id=values["rack_id"]).first() + ets = EntityToStorage.query.filter_by(rack_id=values["rack_id"], storage_type='BTS').first() - if ets != None: + if ets is not None: ets.box_id = None ets.shelf_id = values["shelf_id"] ets.editor_id = tokenuser.id @@ -59,9 +57,9 @@ def storage_transfer_rack_to_shelf(tokenuser: UserAccount): ets = EntityToStorage(**rack_to_shelf_result) ets.author_id = tokenuser.id ets.storage_type = "BTS" + db.session.add(ets) try: - db.session.add(ets) db.session.commit() return success_with_content_response({"success": True}) except Exception as err: @@ -81,12 +79,17 @@ def storage_transfer_sample_to_shelf(tokenuser: UserAccount): except ValidationError as err: return validation_error_response(err) - ets = EntityToStorage.query.filter_by(sample_id=values["sample_id"]).first() + ets = EntityToStorage.query.filter_by(sample_id=values["sample_id"], storage_type='STB').first() + if ets != None: + # warning, confirmation + db.session.delete(ets) + ets = EntityToStorage.query.filter_by(sample_id=values["sample_id"], storage_type='STS').first() if ets != None: ets.box_id = None ets.shelf_id = values["shelf_id"] ets.editor_id = tokenuser.id + ets.updated_on = func.now() ets.storage_type = "STS" else: diff --git a/services/web/app/storage/api/rack.py b/services/web/app/storage/api/rack.py index aa844b786..ebcdb482d 100644 --- a/services/web/app/storage/api/rack.py +++ b/services/web/app/storage/api/rack.py @@ -12,6 +12,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import print_function from flask import request, current_app, jsonify, send_file, flash from ...api import api @@ -102,12 +103,12 @@ def storage_rack_new_with_samples(tokenuser: UserAccount): sample['entry'] = entry # insert confirmed data to database - return storage_transfer_samples_to_rack( + return func_transfer_samples_to_rack( samples_pos, rack_id, tokenuser ) -def storage_transfer_samples_to_rack(samples_pos, rack_id, tokenuser: UserAccount): +def func_transfer_samples_to_rack(samples_pos, rack_id, tokenuser: UserAccount): # Update entitytostorage with storage_type 'STB' stb_batch = [] for sample in samples_pos: @@ -314,7 +315,7 @@ def storage_rack_fill_with_samples(tokenuser: UserAccount): samples_pos = [{'sample_id': sample['id'], 'row': sample['row'], 'col': sample['col']} for sample in samples] # insert confirmed data to database - return storage_transfer_samples_to_rack( + return func_transfer_samples_to_rack( samples_pos, rack_id, tokenuser ) @@ -399,6 +400,48 @@ def storage_rack_edit_basic(id, tokenuser: UserAccount): db, SampleRack, id, new_sample_rack_schema, rack_schema, values, tokenuser ) +@api.route("/storage/RACK/LIMBRACK-/delete", methods=["POST"]) +@token_required +def storage_rack_delete(id, tokenuser: UserAccount): + rackTableRecord = SampleRack.query.filter_by(id=id).first() + entityStorageTableRecord = EntityToStorage.query.filter(EntityToStorage.rack_id==id).all() + + if not rackTableRecord: + return not_found() + + if rackTableRecord.is_locked: + return locked_response() + + rackTableRecord.editor_id = tokenuser.id + if not entityStorageTableRecord: + try: + db.session.delete(rackTableRecord) + db.session.commit() + return success_with_content_response(None) + except Exception as err: + return transaction_error_response(err) + shelfID = entityStorageTableRecord[0].shelf_id + + response = func_rack_delete(rackTableRecord,entityStorageTableRecord) + if response == "success": + return success_with_content_response(shelfID) + return sample_assigned_delete_response() + +#change to func_rack_delete +def func_rack_delete(record,entityStorageTableRecord): + for ESRecord in entityStorageTableRecord: + if not ESRecord.sample_id is None: + return "has sample" + db.session.delete(ESRecord) + + try: + db.session.flush() + db.session.delete(record) + db.session.commit() + return "success" + except Exception as err: + return transaction_error_response(err) + @api.route("/storage/rack/location/LIMBRACK-", methods=["GET"]) @token_required diff --git a/services/web/app/storage/api/room.py b/services/web/app/storage/api/room.py index 55171f0d2..2799cd782 100644 --- a/services/web/app/storage/api/room.py +++ b/services/web/app/storage/api/room.py @@ -13,14 +13,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from flask import request, current_app, jsonify, send_file +from flask import request, current_app, jsonify, send_file,url_for,redirect from ...api import api from ...api.responses import * from ...api.filters import generate_base_query_filters, get_filters_and_joins from ...decorators import token_required from ...webarg_parser import use_args, use_kwargs, parser -from ...database import db, Room, UserAccount +from ...database import db, Room, UserAccount,ColdStorage +import requests +from .lts import storage_coldstorage_delete +from ...misc import get_internal_api_header +from ..api.lts import delete_coldstorage_func + from marshmallow import ValidationError @@ -28,6 +33,7 @@ from ...api.generics import * + @api.route("/storage/room", methods=["GET"]) @token_required def storage_room_home(tokenuser: UserAccount): @@ -77,16 +83,59 @@ def storage_room_edit(id, tokenuser: UserAccount): db, Room, id, new_room_schema, basic_room_schema, values, tokenuser ) +#* +# Route for deleting a room +#* @api.route("/storage/room/LIMBROOM-/delete", methods=["PUT"]) @token_required def storage_room_delete(id, tokenuser: UserAccount): + existing = Room.query.filter_by(id=id).first() + + if not existing: + return not_found() + + if existing.is_locked: + return locked_response() + + existing.editor_id = tokenuser.id + + buildingID = existing.building_id + + code = func_room_delete(existing) + + # Type of error + if code == "success": + return success_with_content_response(buildingID) + elif code == "cold storage": + return in_use_response(code) + elif code == "locked": + return locked_response() + else: + return no_values_response() + +#* +# Deletes the room passed in. +# Includes validation for deleting. +#* +def func_room_delete(record): + attachedCS = ColdStorage.query.filter(ColdStorage.room_id == record.id).first() + if not attachedCS is None: + return "cold storage" + if record.is_locked: + return "locked" + try: + db.session.delete(record) + db.session.commit() + return "success" + except Exception as err: + return transaction_error_response(err) - values = request.get_json() - return generic_delete( - db, Room, id, tokenuser - ) +#* +# Function changes the state of the lock variable for the room. +# Locking a room reduces the functionality to the user. +#* @api.route("/storage/room/LIMBROOM-/lock", methods=["PUT"]) @token_required def storage_room_lock(id, tokenuser: UserAccount): @@ -96,11 +145,16 @@ def storage_room_lock(id, tokenuser: UserAccount): if not room: return not_found() - room.is_locked = not room.is_locked + # Updates the attribute + if room.is_locked: + room.is_locked = False + else: + room.is_locked = True room.editor_id = tokenuser.id - db.session.add(room) - db.session.commit() - db.session.flush() - - return success_with_content_response(basic_room_schema.dump(room)) + try: + db.session.commit() + db.session.flush() + return success_with_content_response(room.is_locked) + except Exception as err: + return transaction_error_response(err) diff --git a/services/web/app/storage/api/shelf.py b/services/web/app/storage/api/shelf.py index d82ce5ee5..511d14951 100644 --- a/services/web/app/storage/api/shelf.py +++ b/services/web/app/storage/api/shelf.py @@ -20,7 +20,8 @@ from ...api.filters import generate_base_query_filters, get_filters_and_joins from ...decorators import token_required from ...webarg_parser import use_args, use_kwargs, parser -from ...database import db, ColdStorageShelf, UserAccount +from ...database import db, UserAccount,EntityToStorage,SampleRack +from ..api.rack import func_rack_delete from marshmallow import ValidationError from ..views.shelf import * @@ -57,6 +58,46 @@ def storage_shelf_edit(id, tokenuser: UserAccount): tokenuser, ) +@api.route("/storage/shelf/LIMBSHF-/delete", methods=["PUT"]) +@token_required +def storage_shelf_delete(id, tokenuser: UserAccount): + existing = ColdStorageShelf.query.filter_by(id=id).first() + + if not existing: + return not_found() + + if existing.is_locked: + return locked_response() + + existing.editor_id = tokenuser.id + storageID = existing.storage_id + + code = func_shelf_delete(existing) + + if code == "success": + return success_with_content_response(storageID) + elif code == "has sample": + return sample_assigned_delete_response() + else: + return no_values_response() + +def func_shelf_delete(record): + entityStorageRecords = EntityToStorage.query.filter(EntityToStorage.shelf_id==record.id).all() + + for ESRecord in entityStorageRecords: + rackRecord = SampleRack.query.filter(SampleRack.id==ESRecord.rack_id).first() + entityStorageRackRecords = EntityToStorage.query.filter(EntityToStorage.rack_id==rackRecord.id).all() + if func_rack_delete(rackRecord,entityStorageRackRecords) == "has sample": + return "has sample" + + try: + db.session.delete(record) + db.session.commit() + return "success" + except Exception as err: + return transaction_error_response(err) + + @api.route("/storage/shelf/new/", methods=["POST"]) @token_required @@ -72,7 +113,7 @@ def storage_shelf_new(tokenuser: UserAccount): ) -@api.route("/storage/LIMBSHELF-/lock", methods=["POST"]) +@api.route("/storage/LIMBSHF-/lock", methods=["POST"]) @token_required def storage_shelf_lock(id, tokenuser: UserAccount): return generic_lock(db, ColdStorageShelf, id, basic_shelf_schema, tokenuser) diff --git a/services/web/app/storage/api/site.py b/services/web/app/storage/api/site.py index bc1c4af21..ecad92b55 100644 --- a/services/web/app/storage/api/site.py +++ b/services/web/app/storage/api/site.py @@ -15,19 +15,87 @@ from ...api import api from ...api.responses import * +from ..api.building import delete_buildings_func from flask import request, send_file from ...decorators import token_required from marshmallow import ValidationError -from ...database import SiteInformation, UserAccount +from ...database import db,SiteInformation, UserAccount,Sample,Building -from ..views import site_schema +from ..views import site_schema, new_site_schema, basic_site_schema +from ...api.generics import * - -@api.route("/misc/site/LIMBSIT-", methods=["GET"]) +@api.route("/misc/site/LIMBSITE-", methods=["GET"]) @token_required def site_view(id, tokenuser: UserAccount): return success_with_content_response( site_schema.dump(SiteInformation.query.filter_by(id=id).first_or_404()) ) + +@api.route("/storage/site/LIMBSITE-/edit", methods=["PUT"]) +@token_required +def storage_site_edit(id, tokenuser: UserAccount): + + values = request.get_json() + + return generic_edit( + db, SiteInformation, id, new_site_schema, basic_site_schema, values, tokenuser + ) + +#* +# Route for deleting a site. +# Includes validation for deleting. +#* +@api.route("/storage/site/LIMBSITE-/delete", methods=["PUT"]) +@token_required +def storage_site_delete(id, tokenuser: UserAccount): + # Finds the site in the site table + siteTableRecord = SiteInformation.query.filter_by(id=id).first() + # Finds sample records which ref the site to delete and preserve foreign key integrity. + sampleTableRecord = Sample.query.filter(Sample.site_id==id).all() + + + if not siteTableRecord: + return not_found() + + if siteTableRecord.is_locked: + return locked_response() + + siteTableRecord.editor_id = tokenuser.id + + for record in sampleTableRecord: + db.session.delete(record) + try: + db.session.flush() + db.session.delete(siteTableRecord) + db.session.commit() + return success_without_content_response() + except Exception as err: + return transaction_error_response(err) + +#* +# Function changes the state of the lock variable for the site. +# Locking a site reduces the functionality to the admin. +#* +@api.route("/storage/site/LIMBSITE-/lock", methods=["PUT"]) +@token_required +def storage_site_lock(id, tokenuser: UserAccount): + + site = SiteInformation.query.filter_by(id=id).first() + + if not site: + return not_found() + + site.is_locked = not site.is_locked + site.editor_id = tokenuser.id + + try: + db.session.commit() + db.session.flush() + return success_with_content_response(site.is_locked) + except Exception as err: + return transaction_error_response(err) + + + diff --git a/services/web/app/storage/forms/building.py b/services/web/app/storage/forms/building.py index f2153e63d..8eb16ec71 100644 --- a/services/web/app/storage/forms/building.py +++ b/services/web/app/storage/forms/building.py @@ -21,3 +21,4 @@ class BuildingRegistrationForm(FlaskForm): name = StringField("Building Name", validators=[DataRequired()]) submit = SubmitField("Register Building") + diff --git a/services/web/app/storage/forms/rack.py b/services/web/app/storage/forms/rack.py index 0a9ac6ad7..7d5af7b6d 100644 --- a/services/web/app/storage/forms/rack.py +++ b/services/web/app/storage/forms/rack.py @@ -27,16 +27,15 @@ DateField, TimeField, ) - -from wtforms.validators import DataRequired +from wtforms.validators import DataRequired, NumberRange from datetime import datetime from ...sample.enums import Colour class NewSampleRackForm(FlaskForm): serial = StringField("Serial Number", validators=[DataRequired()]) - num_rows = IntegerField("Number of Rows", validators=[DataRequired()], default=1) - num_cols = IntegerField("Number of Columns", validators=[DataRequired()], default=1) + num_rows = IntegerField("Number of Rows", validators=[DataRequired(),NumberRange(1,None,None)], default=1) + num_cols = IntegerField("Number of Columns", validators=[DataRequired(),NumberRange(1,None,None)], default=1) description = TextAreaField("Description") colours = SelectField("Colour", choices=Colour.choices()) entry = StringField("Entry by") diff --git a/services/web/app/storage/forms/shelf.py b/services/web/app/storage/forms/shelf.py index db9fd1808..0758751e3 100644 --- a/services/web/app/storage/forms/shelf.py +++ b/services/web/app/storage/forms/shelf.py @@ -41,6 +41,7 @@ class NewShelfForm(FlaskForm): submit = SubmitField("Register Shelf") + def RackToShelfForm(racks: list) -> FlaskForm: class StaticForm(FlaskForm): date = DateField( diff --git a/services/web/app/storage/models/building.py b/services/web/app/storage/models/building.py index 43e9ed8cd..80f90193f 100644 --- a/services/web/app/storage/models/building.py +++ b/services/web/app/storage/models/building.py @@ -20,6 +20,7 @@ class Building(Base, RefAuthorMixin, RefEditorMixin, UniqueIdentifierMixin): __versioned__ = {} name = db.Column(db.String(128)) + is_locked = db.Column(db.Boolean, default=False) site_id = db.Column(db.Integer, db.ForeignKey("siteinformation.id")) site = db.relationship("SiteInformation", uselist=False) rooms = db.relationship("Room", uselist=True) diff --git a/services/web/app/storage/models/lts.py b/services/web/app/storage/models/lts.py index e72caacae..90df9052a 100644 --- a/services/web/app/storage/models/lts.py +++ b/services/web/app/storage/models/lts.py @@ -26,6 +26,7 @@ class ColdStorage(Base, UniqueIdentifierMixin, RefAuthorMixin, RefEditorMixin): __versioned__ = {} alias = db.Column(db.String(128)) + is_locked = db.Column(db.Boolean,default=False) serial_number = db.Column(db.String(128)) manufacturer = db.Column(db.String(128)) comments = db.Column(db.Text) diff --git a/services/web/app/storage/models/rack.py b/services/web/app/storage/models/rack.py index a6f401c7a..536f4c9b6 100644 --- a/services/web/app/storage/models/rack.py +++ b/services/web/app/storage/models/rack.py @@ -23,6 +23,7 @@ class SampleRack(Base, UniqueIdentifierMixin, RefAuthorMixin, RefEditorMixin): __versioned__ = {} serial_number = db.Column(db.String(256)) description = db.Column(db.Text) + is_locked = db.Column(db.Boolean, default=False) colour = db.Column(db.Enum(Colour)) num_rows = db.Column(db.Integer) num_cols = db.Column(db.Integer) diff --git a/services/web/app/storage/models/room.py b/services/web/app/storage/models/room.py index 5885fc27c..2392b8d2f 100644 --- a/services/web/app/storage/models/room.py +++ b/services/web/app/storage/models/room.py @@ -20,6 +20,7 @@ class Room(Base, RefAuthorMixin, RefEditorMixin, UniqueIdentifierMixin): __versioned__ = {} name = db.Column(db.String(128)) + is_locked = db.Column(db.Boolean,default=False) building_id = db.Column(db.Integer, db.ForeignKey("building.id")) building = db.relationship("Building", uselist=False) storage = db.relationship("ColdStorage", uselist=True) diff --git a/services/web/app/storage/models/shelf.py b/services/web/app/storage/models/shelf.py index db2a72868..babfc4541 100644 --- a/services/web/app/storage/models/shelf.py +++ b/services/web/app/storage/models/shelf.py @@ -20,6 +20,7 @@ class ColdStorageShelf(Base, RefAuthorMixin, RefEditorMixin, UniqueIdentifierMixin): __versioned__ = {} name = db.Column(db.String, nullable=False) + is_locked = db.Column(db.Boolean, default=False) description = db.Column(db.Text) z = db.Column(db.Integer) storage_id = db.Column(db.Integer, db.ForeignKey("coldstorage.id")) diff --git a/services/web/app/storage/routes/building.py b/services/web/app/storage/routes/building.py index 1878b721f..9155c612e 100644 --- a/services/web/app/storage/routes/building.py +++ b/services/web/app/storage/routes/building.py @@ -20,6 +20,7 @@ from flask import render_template, redirect, url_for, abort, flash from flask_login import current_user, login_required from ..forms import BuildingRegistrationForm +from ...decorators import check_if_admin, check_if_locked @storage.route("site/LIMBSITE-/new_building", methods=["GET", "POST"]) @@ -31,6 +32,10 @@ def new_building(id): headers=get_internal_api_header(), ) + if response.json()["content"]["is_locked"]: + return abort(401) + + if response.status_code == 200: form = BuildingRegistrationForm() @@ -59,7 +64,7 @@ def new_building(id): abort(response.status_code) -@storage.route("/building/LIMBUILD-", methods=["GET"]) +@storage.route("/building/LIMBBUILDING-", methods=["GET"]) @login_required def view_building(id): response = requests.get( @@ -75,7 +80,9 @@ def view_building(id): return abort(response.status_code) -@storage.route("/building/LIMBUILD-/edit", methods=["GET", "POST"]) + + +@storage.route("/building/LIMBBUILDING-/edit", methods=["GET", "POST"]) @login_required def edit_building(id): @@ -83,6 +90,8 @@ def edit_building(id): url_for("api.storage_building_view", id=id, _external=True), headers=get_internal_api_header(), ) + if response.json()["content"]["is_locked"]: + return abort(401) if response.status_code == 200: @@ -111,3 +120,96 @@ def edit_building(id): ) return abort(response.status_code) + + +@storage.route("/buildings/LIMBBUILDING-/lock", methods=["GET", "POST"]) +@login_required +@check_if_admin +def lock_building(id): + + edit_response = requests.put( + url_for("api.storage_lock_building", id=id, _external=True), + headers=get_internal_api_header(), + #json=form_information, + ) + + if edit_response.status_code == 200: + if edit_response.json()["content"]: + flash("Building Successfully Locked") + else: + flash("Building Successfully Unlocked") + else: + flash("We have a problem: %s" % (edit_response.status_code)) + + return redirect(url_for("storage.view_building", id=id)) + + #return render_template( + # "storage/room/edit.html", room=response.json()["content"], form=form + #) + + # return abort(response.status_code) + +@storage.route("/buildings/LIMBBUILDING-/delete", methods=["GET", "POST"]) +@login_required +def delete_building(id): + + response = requests.get( + url_for("api.storage_building_view", id=id, _external=True), + headers=get_internal_api_header(), + ) + if response.json()["content"]["is_locked"]: + return abort(401) + + if response.status_code == 200: + + edit_response = requests.put( + url_for("api.storage_building_delete", id=id, _external=True), + headers=get_internal_api_header(), + ) + + if edit_response.status_code == 200: + flash("Building Successfully Deleted") + return redirect(url_for("storage.view_site",id=edit_response.json()["content"],_external=True)) + elif edit_response.status_code == 400 and edit_response.json()["message"] == "Has associated cold storage": + flash("Cannot delete building with associated cold storage") + elif edit_response.status_code == 400 and edit_response.json()["message"] == "Entity is locked": + flash("Cannot delete building with associated rooms which are locked") + else: + flash("We have a problem: %s" % edit_response.status_code ) + + # return redirect(url_for("storage.view_site",id=edit_response.json()["content"], _external=True)) + return redirect(url_for("storage.view_building", id=id, _external=True)) + return abort(response.status_code) + +# @storage.route("/buildings/LIMBBUILD-/delete", methods=["GET", "POST"]) +# @login_required +# def delete_building(id): +# #confirmation = requests.put(url_for("api.storage_building_delete_confirmation",id=id,_external=True)) +# +# response = requests.get( +# url_for("api.storage_building_view", id=id, _external=True), +# headers=get_internal_api_header(), +# ) +# +# if response.status_code == 200: +# +# form = DeleteForm(data=response.json()["content"]) +# +# if form.validate_on_submit(): +# edit_response = requests.put( +# url_for("api.storage_building_delete", id=id, _external=True), +# headers=get_internal_api_header(), +# ) +# +# if edit_response.status_code == 200: +# flash("Building Successfully Deleted") +# else: +# flash("We have a problem: %s" % (id)) +# +# # return redirect(url_for("storage.view_site",id=edit_response.json()["content"], _external=True)) +# return redirect(url_for("storage.index",_external=True)) +# else: +# flash("We have a problem:") +# return abort(response.status_code) + + diff --git a/services/web/app/storage/routes/lts.py b/services/web/app/storage/routes/lts.py index 61cc937e8..c784b5216 100644 --- a/services/web/app/storage/routes/lts.py +++ b/services/web/app/storage/routes/lts.py @@ -28,6 +28,7 @@ import requests from ...misc import get_internal_api_header from flask_login import current_user, login_required +from ...decorators import check_if_admin from ..forms import ( ColdStorageForm, @@ -38,7 +39,7 @@ ) -@storage.route("/coldstorage/new/LIMROOM-", methods=["GET", "POST"]) +@storage.route("/rooms/LIMBROOM-/new_cold_storage", methods=["GET", "POST"]) @login_required def new_cold_storage(id): @@ -47,6 +48,9 @@ def new_cold_storage(id): headers=get_internal_api_header(), ) + if response.json()["content"]["is_locked"]: + return abort(401) + if response.status_code == 200: form = ColdStorageForm() @@ -93,6 +97,9 @@ def new_cold_storage_servicing_report(id: int): headers=get_internal_api_header(), ) + if response.json()["content"]["is_locked"]: + abort(401) + if response.status_code == 200: form = ColdStorageServiceReportForm() @@ -132,6 +139,9 @@ def associate_document(id): headers=get_internal_api_header(), ) + if response.json()["content"]["is_locked"]: + abort(401) + if response.status_code == 200: document_response = requests.get( @@ -197,6 +207,9 @@ def edit_cold_storage(id): headers=get_internal_api_header(), ) + if response.json()["content"]["is_locked"]: + return abort(401) + csinfo = {} csinfo['id']=id csinfo['alias']=response.json()["content"]['alias'] @@ -248,3 +261,57 @@ def edit_cold_storage(id): return abort(response.status_code) + +@storage.route("/coldstorage/LIMBCS-/delete", methods=["GET", "POST"]) +@login_required +def delete_cold_storage(id): + response = requests.get( + url_for("api.storage_coldstorage_view", id=id, _external=True), + headers=get_internal_api_header(), + ) + if response.json()["content"]["is_locked"]: + return abort(401) + if response.status_code==200: + edit_response = requests.put( + url_for("api.storage_coldstorage_delete", id=id, _external=True), + headers=get_internal_api_header(), + ) + + if edit_response.status_code == 200: + flash("Cold Storage Successfully Deleted") + return redirect(url_for("storage.view_room", id=edit_response.json()["content"], _external=True)) + elif edit_response.status_code == 400 and edit_response.json()["message"]== "Can't delete assigned samples": + flash("Cannot delete a cold storage associated with a rack with assigned samples") + else: + flash("We have a problem: %s" % (id)) + + return redirect(url_for("storage.view_cold_storage", id=id, _external=True)) + return abort(response.status_code) + +@storage.route("/coldstorage/LIMBCS-/lock", methods=["GET", "POST"]) +@login_required +@check_if_admin +def lock_cold_storage(id): + + response = requests.get( + url_for("api.storage_coldstorage_view", id=id, _external=True), + headers=get_internal_api_header(), + ) + if response.status_code==200: + edit_response = requests.put( + url_for("api.storage_cold_storage_lock", id=id, _external=True), + headers=get_internal_api_header(), + #json=form_information, + ) + + if edit_response.status_code == 200: + if edit_response.json()["content"]: + flash("Cold Storage Successfully Locked") + else: + flash("Cold Storage Successfully Unlocked") + else: + flash("We have a problem: %s" % (edit_response.status_code)) + + return redirect(url_for("storage.view_cold_storage", id=id)) + + return abort(response.status_code) diff --git a/services/web/app/storage/routes/rack.py b/services/web/app/storage/routes/rack.py index 70eaab381..a0e1c0be2 100644 --- a/services/web/app/storage/routes/rack.py +++ b/services/web/app/storage/routes/rack.py @@ -364,7 +364,18 @@ def rack_automatic_entry_validation_div(_hash: str): @storage.route("/rack/LIMBRACK-") @login_required def view_rack(id): - return render_template("storage/rack/view.html", id=id) + response = requests.get( + url_for("api.storage_rack_view", id=id, _external=True), + headers=get_internal_api_header(), + ) + + if response.status_code == 200: + return render_template( + "storage/rack/view.html", rack=response.json()["content"] + ) + + return abort(response.status_code) + # return render_template("storage/rack/view.html", id=id) @storage.route("/rack/LIMBRACK-/endpoint") @@ -420,6 +431,8 @@ def assign_rack_sample(id, row, column): url_for("api.storage_rack_view", id=id, _external=True), headers=get_internal_api_header(), ) + if view_response.json()["content"]["is_locked"]: + return abort(401) if view_response.status_code == 200: @@ -487,7 +500,6 @@ def storage_rack_fill_with_samples(): ) return response.json() - @storage.route("rack/LIMBRACK-/edit", methods=["GET", "POST"]) @login_required def edit_rack(id): @@ -554,3 +566,104 @@ def edit_rack(id): ) abort(response.status_code) + #--- +# @storage.route("rack/LIMBRACK-/edit", methods=["GET", "POST"]) +# @login_required +# def edit_rack(id): +# response = requests.get( +# # url_for("api.storage_rack_location", id=id, _external=True), +# url_for("api.storage_rack_view", id=id, _external=True), +# headers=get_internal_api_header(), +# ) +# +# # if response.json()["content"]["is_locked"]: +# # return abort(401) +# +# if response.status_code == 200: +# # For SampleRack with location info. +# rack = response.json()["content"] +# print("Rack: ", rack) +# shelves = [] +# shelf_required = True +# +# response1 = requests.get( +# url_for("api.storage_shelves_onsite", id=id, _external=True), +# headers=get_internal_api_header(), +# ) +# if response1.status_code == 200: +# shelves = response1.json()["content"] +# #shelf_required = len(shelves) > 0 +# +# form = EditSampleRackForm(shelves=shelves, +# data={"serial": rack["serial_number"], "description": rack["description"], +# "storage_id": rack['storage_id'], "shelf_id": rack["shelf_id"]} +# ) +# +# delattr(form, "num_cols") +# delattr(form, "num_rows") +# delattr(form, "colours") +# #if not shelf_required: +# # delattr(form, "shelf_id") +# +# if form.validate_on_submit(): +# shelf_id = form.shelf_id.data +# if shelf_id == 0: +# shelf_id = None +# +# form_information = { +# "serial_number": form.serial.data, +# "description": form.description.data, +# "storage_id": form.storage_id.data, +# "shelf_id": shelf_id +# } +# +# +# edit_response = requests.put( +# url_for("api.storage_rack_edit", id=id, _external=True), +# headers=get_internal_api_header(), +# json=form_information, +# ) +# +# if edit_response.status_code == 200: +# flash("Rack Successfully Edited") +# else: +# flash("We have a problem: %s" % (edit_response.json())) +# +# return redirect(url_for("storage.view_rack", id=id)) +# +# return render_template( +# "storage/rack/edit.html", rack=response.json()["content"], form=form +# ) +# +# abort(response.status_code) + + +@storage.route("/rack/LIMBRACK-/delete", methods=["GET", "POST"]) +@login_required +def delete_rack(id): + response = requests.get( + url_for("api.storage_rack_view", id=id, _external=True), + headers=get_internal_api_header(), + ) + + if response.json()["content"]["is_locked"]: + return abort(401) + + if response.status_code == 200: + edit_response = requests.post( + url_for("api.storage_rack_delete", id=id, _external=True), + headers=get_internal_api_header(), + ) + if edit_response.status_code == 200: + flash("Rack Successfully Deleted") + if edit_response.json()["content"] is None: + return redirect(url_for("storage.rack_index")) + return redirect(url_for("storage.view_shelf",id=edit_response.json()["content"], _external=True)) + elif edit_response.status_code == 400 and edit_response.json()["message"]=="Can't delete assigned samples": + flash("Cannot delete rack with assigned samples") + else: + flash("We have a problem: %s" % edit_response.status_code) + return redirect(url_for("storage.view_rack",id=id,_external=True)) + abort(response.status_code) + + diff --git a/services/web/app/storage/routes/room.py b/services/web/app/storage/routes/room.py index 20ee16fd5..e2ad0c7ae 100644 --- a/services/web/app/storage/routes/room.py +++ b/services/web/app/storage/routes/room.py @@ -30,9 +30,10 @@ from .. import storage from ..forms import RoomRegistrationForm, RoomRegistrationForm +from ...decorators import check_if_admin -@storage.route("/building/LIMBBUILD-/room/new", methods=["GET", "POST"]) +@storage.route("/building/LIMBBUILDING-/new_room", methods=["GET", "POST"]) @login_required def new_room(id): @@ -41,6 +42,9 @@ def new_room(id): headers=get_internal_api_header(), ) + if response.json()["content"]["is_locked"]: + return abort(401) + if response.status_code == 200: form = RoomRegistrationForm() @@ -93,15 +97,17 @@ def edit_room(id): headers=get_internal_api_header(), ) + if response.json()["content"]["is_locked"]: + return abort(401) + if response.status_code == 200: form = RoomRegistrationForm(data=response.json()["content"]) - + flash(form.validate_on_submit()) if form.validate_on_submit(): form_information = { "name": form.name.data, } - edit_response = requests.put( url_for("api.storage_room_edit", id=id, _external=True), headers=get_internal_api_header(), @@ -113,8 +119,10 @@ def edit_room(id): else: flash("We have a problem: %s" % (edit_response.json())) + return redirect(url_for("storage.view_room", id=id)) #return redirect(url_for("storage.view_room", id=id)) - return redirect(url_for("storage.view_building", id=id)) + # return redirect(url_for("storage.view_building", id=id)) #DOESNT WORK ID DOESNT REFER TO BUILDING ID + return render_template( "storage/room/edit.html", room=response.json()["content"], form=form @@ -123,22 +131,32 @@ def edit_room(id): return abort(response.status_code) + @storage.route("/rooms/LIMBROOM-/lock", methods=["GET", "POST"]) @login_required +@check_if_admin def lock_room(id): - edit_response = requests.put( + response = requests.get( + url_for("api.storage_room_view", id=id, _external=True), + headers=get_internal_api_header(), + ) + if response.status_code == 200: + edit_response = requests.put( url_for("api.storage_room_lock", id=id, _external=True), headers=get_internal_api_header(), #json=form_information, - ) + ) - if edit_response.status_code == 200: - flash("Room Successfully Locked") - else: - flash("We have a problem: %s" % (edit_response.json())) + if edit_response.status_code == 200: + if edit_response.json()["content"]: + flash("Room Successfully Locked") + else: + flash("Room Successfully Unlocked") + else: + flash("We have a problem: %s" % (edit_response.status_code)) - return redirect(url_for("storage.view_room", id=id)) + return redirect(url_for("storage.view_room", id=id)) #return render_template( # "storage/room/edit.html", room=response.json()["content"], form=form @@ -150,22 +168,28 @@ def lock_room(id): @login_required def delete_room(id): - edit_response = requests.put( - url_for("api.storage_room_delete", id=id, _external=True), + response = requests.get( + url_for("api.storage_room_view", id=id, _external=True), headers=get_internal_api_header(), ) - if edit_response.status_code == 200: - flash("Room Successfully Deleted") - else: - flash("We have a problem: %s" % (edit_response.json())) - - #return redirect(url_for("storage.view_room", id=id)) - #return render_template("storage/index.html") - return redirect(url_for("storage.index", _external=True)) + if response.json()["content"]["is_locked"]: + return abort(401) - #return render_template( - # "storage/room/edit.html", room=response.json()["content"], form=form - #) + if response.status_code == 200: + edit_response = requests.put( + url_for("api.storage_room_delete", id=id, _external=True), + headers=get_internal_api_header(), + ) - #return abort(response.status_code) \ No newline at end of file + if edit_response.status_code == 200: + flash("Room Successfully Deleted") + return redirect(url_for("storage.view_building", id=edit_response.json()["content"], _external=True)) + elif edit_response.status_code == 400 and edit_response.json()["message"] == "Entity is locked": + flash("Cannot delete room as it is locked") + elif edit_response.status_code == 400 and edit_response.json()["message"]== "Has associated cold storage": + flash("Cannot delete room with associated cold storage") + else: + flash("We have a problem: %s" % edit_response.status_code) + return redirect(url_for("storage.view_room", id=id,_external=True)) + return abort(response.status_code) \ No newline at end of file diff --git a/services/web/app/storage/routes/shelf.py b/services/web/app/storage/routes/shelf.py index 735757d51..aec06059d 100644 --- a/services/web/app/storage/routes/shelf.py +++ b/services/web/app/storage/routes/shelf.py @@ -75,7 +75,18 @@ def new_shelf(id): @storage.route("/shelf/LIMBSHF-", methods=["GET"]) @login_required def view_shelf(id): - return render_template("storage/shelf/view.html", id=id) + # return render_template("storage/shelf/view.html", id=id) + response = requests.get( + url_for("api.storage_shelf_view", id=id, _external=True), + headers=get_internal_api_header(), + ) + + if response.status_code == 200: + return render_template( + "storage/shelf/view.html", shelf=response.json()["content"] + ) + + return abort(response.status_code) @storage.route("/shelf/LIMBSHF-/endpoint", methods=["GET"]) @@ -99,12 +110,16 @@ def edit_shelf(id): url_for("api.storage_shelf_view", id=id, _external=True), headers=get_internal_api_header(), ) + if response.json()["content"]["is_locked"]: + return abort(401) if response.status_code == 200: shelf = response.json()["content"] form = NewShelfForm(data=shelf) + if form.validate_on_submit(): + form_information = { "name": form.name.data, "description": form.description.data, @@ -128,6 +143,33 @@ def edit_shelf(id): return abort(response.status_code) +@storage.route("/shelf/LIMBSHF-/delete", methods=["GET", "POST"]) +@login_required +def delete_shelf(id): + response = requests.get( + url_for("api.storage_shelf_view", id=id, _external=True), + headers=get_internal_api_header(), + ) + + if response.json()["content"]["is_locked"]: + return abort(401) + if response.status_code == 200: + edit_response = requests.put( + url_for("api.storage_shelf_delete", id=id, _external=True), + headers=get_internal_api_header(), + ) + + if edit_response.status_code == 200: + flash("Shelf Successfully Deleted") + return redirect(url_for("storage.view_cold_storage", id=edit_response.json()["content"], _external=True)) + elif edit_response.status_code == 400 and edit_response.json()["message"]== "Can't delete assigned samples": + flash("Cannot delete a shelf associated with a rack with assigned samples") + else: + flash("We have a problem: %s" % edit_response.status_code) + return redirect(url_for("storage.view_shelf", id=id,_external=True)) + return abort(response.status_code) + + @storage.route("/shelf/LIMBSHF-/assign_rack", methods=["GET", "POST"]) @login_required def assign_rack_to_shelf(id): @@ -135,6 +177,8 @@ def assign_rack_to_shelf(id): url_for("api.storage_shelf_view", id=id, _external=True), headers=get_internal_api_header(), ) + if response.json()["content"]["is_locked"]: + return abort(401) if response.status_code == 200: @@ -185,6 +229,9 @@ def assign_sample_to_shelf(id): headers=get_internal_api_header(), ) + if response.json()["content"]["is_locked"]: + return abort(401) + if response.status_code == 200: sample_response = requests.get( diff --git a/services/web/app/storage/routes/site.py b/services/web/app/storage/routes/site.py index 696f9925a..9d8daeb70 100644 --- a/services/web/app/storage/routes/site.py +++ b/services/web/app/storage/routes/site.py @@ -28,7 +28,8 @@ from .. import storage import requests from ...misc import get_internal_api_header - +from ..forms import SiteRegistrationForm +from ...decorators import check_if_admin @storage.route("/site/LIMBSITE-", methods=["GET"]) @login_required @@ -45,3 +46,106 @@ def view_site(id): ) return abort(response.status_code) + +@storage.route("/site/LIMBSITE-/delete", methods=["GET", "POST"]) +@login_required +def delete_site(id): + + response = requests.get( + url_for("api.site_view", id=id, _external=True), + headers=get_internal_api_header(), + ) + + if response.json()["content"]["is_locked"]: + return abort(401) + + if response.status_code == 200: + edit_response = requests.put( + url_for("api.storage_site_delete", id=id, _external=True), + headers=get_internal_api_header(), + ) + + if edit_response.status_code == 200: + flash("Site Successfully Deleted") + return redirect(url_for("storage.index", _external=True)) + # elif edit_response.json()["message"]== "Can't delete assigned samples": + # flash("Cannot delete rack with assigned samples") + else: + flash("We have a problem: %s" % (id)) + return redirect(url_for("storage.view_site", id=id, _external=True)) + return abort(response.status_code) + +@storage.route("/site/LIMBSITE-/edit", methods=["GET", "POST"]) +@login_required +def edit_site(id): + + response = requests.get( + url_for("api.site_view", id=id, _external=True), + headers=get_internal_api_header(), + ) + + if response.json()["content"]["is_locked"]: + return abort(401) + + if response.status_code == 200: + + form = SiteRegistrationForm(data=response.json()["content"]) + + if form.validate_on_submit(): + form_information = { + "name": form.name.data, + } + + edit_response = requests.put( + url_for("api.storage_site_edit", id=id, _external=True), + headers=get_internal_api_header(), + json=form_information, + ) + + + if edit_response.status_code == 200: + flash("Site Successfully Edited") + else: + flash("We have a problem: %s" % (edit_response.json())) + + return redirect(url_for("storage.view_site", id=id)) + #return redirect(url_for("storage.view_room", id=id)) + # return redirect(url_for("storage.view_building", id=id)) #DOESNT WORK ID DOESNT REFER TO BUILDING ID + + + return render_template( + "storage/site/edit.html", room=response.json()["content"], form=form + ) + + return abort(response.status_code) + +@storage.route("/site/LIMBSITE-/lock", methods=["GET", "POST"]) +@login_required +@check_if_admin +def lock_site(id): + + edit_response = requests.put( + url_for("api.storage_site_lock", id=id, _external=True), + headers=get_internal_api_header(), + #json=form_information, + ) + + if edit_response.status_code == 200: + if edit_response.json()["content"]: + flash("Site Successfully Locked") + else: + flash("Site Successfully Unlocked") + else: + flash("We have a problem: %s" % (edit_response.status_code)) + + return redirect(url_for("storage.view_site", id=id)) + + #return render_template( + # "storage/room/edit.html", room=response.json()["content"], form=form + #) + + return abort(response.status_code) + + + + diff --git a/services/web/app/storage/views/building.py b/services/web/app/storage/views/building.py index d9c61ce07..16d755a74 100644 --- a/services/web/app/storage/views/building.py +++ b/services/web/app/storage/views/building.py @@ -30,6 +30,7 @@ class Meta: model = Building id = masql.auto_field() + is_locked = masql.auto_field() name = masql.auto_field() site_id = masql.auto_field() @@ -44,6 +45,7 @@ class Meta: id = masql.auto_field() name = masql.auto_field() + is_locked = masql.auto_field() site = ma.Nested(BasicSiteSchema) rooms = ma.Nested(BasicRoomSchema, many=True) author = ma.Nested(BasicUserAccountSchema) diff --git a/services/web/app/storage/views/lts.py b/services/web/app/storage/views/lts.py index f1e8a6b2c..ece6ff018 100644 --- a/services/web/app/storage/views/lts.py +++ b/services/web/app/storage/views/lts.py @@ -110,6 +110,7 @@ class Meta: id = masql.auto_field() alias = masql.auto_field() + is_locked = masql.auto_field() uuid = masql.auto_field() serial_number = masql.auto_field() manufacturer = masql.auto_field() @@ -133,6 +134,7 @@ class Meta: model = ColdStorage alias = masql.auto_field() + is_locked = masql.auto_field() serial_number = masql.auto_field() manufacturer = masql.auto_field() comments = masql.auto_field() diff --git a/services/web/app/storage/views/rack.py b/services/web/app/storage/views/rack.py index db05d231f..31726ebe2 100644 --- a/services/web/app/storage/views/rack.py +++ b/services/web/app/storage/views/rack.py @@ -84,6 +84,7 @@ class Meta: created_on = ma.Date() entity_to_storage_instances = ma.Nested(ViewSampleToSampleRackSchema, many=True) shelf = ma.Nested(ShelfViewSchema) + is_locked = masql.auto_field() _links = ma.Hyperlinks( { diff --git a/services/web/app/storage/views/room.py b/services/web/app/storage/views/room.py index 9919fa117..d1e5624da 100644 --- a/services/web/app/storage/views/room.py +++ b/services/web/app/storage/views/room.py @@ -29,6 +29,7 @@ class Meta: id = masql.auto_field() name = masql.auto_field() + is_locked = masql.auto_field() author = ma.Nested(BasicUserAccountSchema, many=False) created_on = ma.Date() @@ -43,6 +44,7 @@ class Meta: id = masql.auto_field() name = masql.auto_field() + is_locked = masql.auto_field() building_id = masql.auto_field() author = ma.Nested(BasicUserAccountSchema, many=False) created_on = ma.Date() diff --git a/services/web/app/storage/views/site.py b/services/web/app/storage/views/site.py index 7a49e924b..7a0812092 100644 --- a/services/web/app/storage/views/site.py +++ b/services/web/app/storage/views/site.py @@ -31,6 +31,7 @@ class Meta: model = SiteInformation id = masql.auto_field() + is_locked = masql.auto_field() miabis_id = masql.auto_field() acronym = masql.auto_field() name = masql.auto_field() @@ -44,3 +45,24 @@ class Meta: site_schema = SiteSchema() sites_schema = SiteSchema(many=True) + +class BasicSiteSchema(masql.SQLAlchemySchema): + class Meta: + model = SiteInformation + + id = masql.auto_field() + name = masql.auto_field() + + +basic_site_schema = BasicSiteSchema() +basic_sites_schema = BasicSiteSchema(many=True) + +class NewSiteSchema(masql.SQLAlchemySchema): + class Meta: + model = SiteInformation + + id = masql.auto_field() + name = masql.auto_field() + +new_site_schema = NewSiteSchema() + diff --git a/services/web/app/templates/admin/sites/new.html b/services/web/app/templates/admin/sites/new.html index bc0d7230f..cd5065cf1 100644 --- a/services/web/app/templates/admin/sites/new.html +++ b/services/web/app/templates/admin/sites/new.html @@ -41,7 +41,17 @@

{{ form_field(form.country) }} {{ form_field(form.post_code) }} - {{ form_field(form.submit, with_label=False)}} + diff --git a/services/web/app/templates/sample/add/step_one.html b/services/web/app/templates/sample/add/step_one.html index 7faf5c5da..96b4c9a85 100644 --- a/services/web/app/templates/sample/add/step_one.html +++ b/services/web/app/templates/sample/add/step_one.html @@ -85,7 +85,7 @@

Consent Form Template Found!

{% if collection_protocol_count == 0 %}

😔

-

No Suitable Acquisition ProtocolFound

+

No Suitable Acquisition Protocol Found

There doesn't seem to be any acquisition protocols available.

diff --git a/services/web/app/templates/storage/building/new.html b/services/web/app/templates/storage/building/new.html index d5354d8b5..2040ce32a 100644 --- a/services/web/app/templates/storage/building/new.html +++ b/services/web/app/templates/storage/building/new.html @@ -26,7 +26,17 @@

{{ form.csrf_token }} {{ form_field(form.name) }} - {{ form_field(form.submit) }} +

diff --git a/services/web/app/templates/storage/building/view.html b/services/web/app/templates/storage/building/view.html index 2a3ca3f7e..d5ca18bc5 100644 --- a/services/web/app/templates/storage/building/view.html +++ b/services/web/app/templates/storage/building/view.html @@ -16,10 +16,10 @@

- + LIMBSIT-{{ building.site.id }}: - {{ building.site.name }} - + {{ building.site.name }} + LIMBBUILD-{{ building.id }}: {{ building.name }}

@@ -46,12 +46,65 @@

{% endif %} + {% if current_user.is_admin == true %} + {% endif %} + + {% if not building.is_locked %} +
+ +
+ + + + {% endif %}
@@ -82,24 +135,24 @@

Rooms

- - - - - + + + + + - {% for room in building.rooms %} - - - - - - {% endfor %} + {% for room in building.rooms %} + + + + + + {% endfor %}
RoomUploaderCreated On
RoomUploaderCreated On
- - LIMBROOM-{{ room.id }}: {{ room.name }} - - {{ render_author_for_table(room.author) }}{{ room.created_on }}
+ + LIMBROOM-{{ room.id }}: {{ room.name }} + + {{ render_author_for_table(room.author) }}{{ room.created_on }}
@@ -109,4 +162,6 @@

Rooms

{% block javascript %} + + {% endblock %} \ No newline at end of file diff --git a/services/web/app/templates/storage/index.html b/services/web/app/templates/storage/index.html index 37da5d8c7..455a80d9d 100644 --- a/services/web/app/templates/storage/index.html +++ b/services/web/app/templates/storage/index.html @@ -42,7 +42,6 @@

Sample Storage Portal

Buildings -
@@ -81,8 +80,6 @@

Cold Storage Temperatures

- - diff --git a/services/web/app/templates/storage/lts/edit.html b/services/web/app/templates/storage/lts/edit.html index e6e342462..353dc8b9a 100644 --- a/services/web/app/templates/storage/lts/edit.html +++ b/services/web/app/templates/storage/lts/edit.html @@ -37,13 +37,17 @@

{{ form_field(form.comments) }} - {{ form_field(form.room_id) }} - -{# {{ form_field(form.temperature) }}#} -{# {{ form_field(form.type) }}#} - {{ form_field(form.manufacturer) }} - {{ form_field(form.serial_number) }} - {{ form_field(form.submit) }} + @@ -53,4 +57,4 @@

{% block javascript %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/services/web/app/templates/storage/lts/new.html b/services/web/app/templates/storage/lts/new.html index 63c428a15..a96f4af5f 100644 --- a/services/web/app/templates/storage/lts/new.html +++ b/services/web/app/templates/storage/lts/new.html @@ -35,7 +35,17 @@

LIMBROOM-{{ro {{ form_field(form.manufacturer) }} {{ form_field(form.comments) }} - {{ form_field(form.submit) }} + diff --git a/services/web/app/templates/storage/lts/view.html b/services/web/app/templates/storage/lts/view.html index e126df61b..8b76ed8a4 100644 --- a/services/web/app/templates/storage/lts/view.html +++ b/services/web/app/templates/storage/lts/view.html @@ -51,12 +51,109 @@

{% endif %} + {% if current_user.is_admin == true %}
- + {%else%} + {% endif %} + +
+ + + + + {% endif %} + + {% if not cs.is_locked %} +
+
+ + + + {% endif %} +
@@ -197,5 +294,6 @@

{% block javascript %} + {% endblock %} \ No newline at end of file diff --git a/services/web/app/templates/storage/rack/edit.html b/services/web/app/templates/storage/rack/edit.html index 0d2311089..a6f22e5d9 100644 --- a/services/web/app/templates/storage/rack/edit.html +++ b/services/web/app/templates/storage/rack/edit.html @@ -6,6 +6,7 @@ {% block title %}Editing LIMBRACK-{{ rack.id }}{% endblock %} {% block body %} +
diff --git a/services/web/app/templates/storage/rack/new/from_file/step_two.html b/services/web/app/templates/storage/rack/new/from_file/step_two.html index eef69b02d..e49a8f3fd 100644 --- a/services/web/app/templates/storage/rack/new/from_file/step_two.html +++ b/services/web/app/templates/storage/rack/new/from_file/step_two.html @@ -84,7 +84,17 @@

- {{ form_field(form.submit) }} + diff --git a/services/web/app/templates/storage/rack/new/manual/new.html b/services/web/app/templates/storage/rack/new/manual/new.html index 682f4c202..ae7629497 100644 --- a/services/web/app/templates/storage/rack/new/manual/new.html +++ b/services/web/app/templates/storage/rack/new/manual/new.html @@ -39,7 +39,17 @@

{{ form_field(form.colours) }} {{ form_field(form.description) }} - {{ form_field(form.submit) }} + diff --git a/services/web/app/templates/storage/rack/sample_to_rack.html b/services/web/app/templates/storage/rack/sample_to_rack.html index 806fd1a09..11958883b 100644 --- a/services/web/app/templates/storage/rack/sample_to_rack.html +++ b/services/web/app/templates/storage/rack/sample_to_rack.html @@ -34,7 +34,17 @@

{{ form_field(form.entered_by) }} - {{ form_field(form.submit) }} + diff --git a/services/web/app/templates/storage/rack/view.html b/services/web/app/templates/storage/rack/view.html index f5d056c6b..ff0ad3e23 100644 --- a/services/web/app/templates/storage/rack/view.html +++ b/services/web/app/templates/storage/rack/view.html @@ -4,7 +4,7 @@ {% endblock %} -{% block title %}LIMBRACK-{{ id }}{% endblock %} +{% block title %}LIMBRACK-{{ rack.id }}{% endblock %} {% block body %} @@ -31,23 +31,68 @@

Loading Rack Information

- LIMBRACK-{{ id }} + LIMBRACK-{{ rack.id }}

- + + + {% endif %} +
@@ -197,4 +242,6 @@ + + {% endblock %} \ No newline at end of file diff --git a/services/web/app/templates/storage/room/edit.html b/services/web/app/templates/storage/room/edit.html index a3ef0b03f..c37cb0215 100644 --- a/services/web/app/templates/storage/room/edit.html +++ b/services/web/app/templates/storage/room/edit.html @@ -24,8 +24,19 @@

{{ form.csrf_token }} - {{ form_field(form.name) }} - {{ form_field(form.submit) }} + {{ form_field(form.name)}} + +
diff --git a/services/web/app/templates/storage/room/new.html b/services/web/app/templates/storage/room/new.html index 4011cf85d..72290d9c5 100644 --- a/services/web/app/templates/storage/room/new.html +++ b/services/web/app/templates/storage/room/new.html @@ -22,7 +22,17 @@

LIMBBUILD-{{
{{ form.csrf_token }} {{ form_field(form.name) }} - {{ form_field(form.submit) }} +

diff --git a/services/web/app/templates/storage/room/view.html b/services/web/app/templates/storage/room/view.html index 3fbfcac89..861f5633f 100644 --- a/services/web/app/templates/storage/room/view.html +++ b/services/web/app/templates/storage/room/view.html @@ -23,49 +23,125 @@

- -
@@ -119,4 +195,6 @@

{{storage.temp}}

{% block javascript %} -{% endblock %} \ No newline at end of file + + +{% endblock %} diff --git a/services/web/app/templates/storage/shelf/edit.html b/services/web/app/templates/storage/shelf/edit.html index b3cc76a44..8cd5726d5 100644 --- a/services/web/app/templates/storage/shelf/edit.html +++ b/services/web/app/templates/storage/shelf/edit.html @@ -27,7 +27,17 @@

{{ form.csrf_token() }} {{ form_field(form.name) }} {{ form_field(form.description) }} - {{ form_field(form.submit) }} +

diff --git a/services/web/app/templates/storage/shelf/new.html b/services/web/app/templates/storage/shelf/new.html index d778871fb..9b1dd1da2 100644 --- a/services/web/app/templates/storage/shelf/new.html +++ b/services/web/app/templates/storage/shelf/new.html @@ -29,7 +29,17 @@

{{ form.csrf_token() }} {{ form_field(form.name) }} {{ form_field(form.description) }} - {{ form_field(form.submit) }} +

diff --git a/services/web/app/templates/storage/shelf/rack_to_shelf.html b/services/web/app/templates/storage/shelf/rack_to_shelf.html index 75b329a52..6fbc5140d 100644 --- a/services/web/app/templates/storage/shelf/rack_to_shelf.html +++ b/services/web/app/templates/storage/shelf/rack_to_shelf.html @@ -15,8 +15,8 @@

- LIMBSHF-{{ shelf.id }} - + LIMBSHF-{{ shelf.id }} + Assign Rack

@@ -26,8 +26,18 @@

{{ form.csrf_token }} - - {{ form_field(form.racks) }} +
+
+ {{ form_field(form.racks) }} +
+ +
{{ form_field(form.date) }} @@ -38,7 +48,17 @@

{{ form_field(form.entered_by) }} - {{ form_field(form.submit) }} +
diff --git a/services/web/app/templates/storage/shelf/sample_to_shelf.html b/services/web/app/templates/storage/shelf/sample_to_shelf.html index 377043d1c..204630768 100644 --- a/services/web/app/templates/storage/shelf/sample_to_shelf.html +++ b/services/web/app/templates/storage/shelf/sample_to_shelf.html @@ -41,7 +41,17 @@

{{ form_field(form.entered_by) }} - {{ form_field(form.submit) }} + diff --git a/services/web/app/templates/storage/shelf/view.html b/services/web/app/templates/storage/shelf/view.html index 212f9792f..f3c862c0d 100644 --- a/services/web/app/templates/storage/shelf/view.html +++ b/services/web/app/templates/storage/shelf/view.html @@ -31,13 +31,19 @@

Loading Shelf Information

- LIMBCS- - LIMBSHF-{{ id }}

+ + + LIMBCS-{{ shelf.storage_id }} + +
+ + LIMBSHF-{{ shelf.id }}

@@ -120,4 +171,6 @@

Stored Sample Racks

{% block javascript %} + + {% endblock %} \ No newline at end of file diff --git a/services/web/app/templates/storage/site/edit.html b/services/web/app/templates/storage/site/edit.html new file mode 100644 index 000000000..1aa33ee3d --- /dev/null +++ b/services/web/app/templates/storage/site/edit.html @@ -0,0 +1,55 @@ +{% extends "template.html" %} + +{% block head %} + +{% endblock %} + +{% block title %}Edit Site{% endblock %} + +{% block body %} + +{% endblock %} + +{% block javascript %} + +{% endblock %} \ No newline at end of file diff --git a/services/web/app/templates/storage/site/new.html b/services/web/app/templates/storage/site/new.html index b8f0b6730..2dd5ee594 100644 --- a/services/web/app/templates/storage/site/new.html +++ b/services/web/app/templates/storage/site/new.html @@ -28,7 +28,17 @@

SitesN {{ form_field(form.city) }} {{ form_field(form.country) }} {{ form_field(form.post_code) }} - {{ form_field(form.submit) }} +

diff --git a/services/web/app/templates/storage/site/view.html b/services/web/app/templates/storage/site/view.html index 56c294a50..06738c75b 100644 --- a/services/web/app/templates/storage/site/view.html +++ b/services/web/app/templates/storage/site/view.html @@ -25,24 +25,86 @@

@@ -98,4 +160,6 @@
LIMBBUILD-{{ building.id }}: {{ buil {% block javascript %} + + {% endblock %} \ No newline at end of file diff --git a/services/web/app/templates/template.html b/services/web/app/templates/template.html index b766848ea..d79762162 100644 --- a/services/web/app/templates/template.html +++ b/services/web/app/templates/template.html @@ -39,11 +39,13 @@ href="{{ url_for('static', filename='node_modules/datatables.net-dt/css/jquery.dataTables.min.css') }}" rel="stylesheet"> + - {% block head %} {% endblock %} diff --git a/services/web/package.json b/services/web/package.json index 43a1ef34e..66c178e6a 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -18,7 +18,7 @@ "datatables.net-buttons": "1.6.5", "datatables.net-buttons-bs4": "1.6.5", "datatables.net-searchpanes-dt": "1.0.1", - "datatables.net-select": "1.3.1", + "datatables.net-select": "1.3.3", "jquery": ">=3.4.0", "jstree": "3.3.10", "moment": "2.29.1",